← All changes
|
includes/rest-api/controller/version2/class-mainwp-rest-global-batch-controller.php
+30
-28
6.2
→
6.3
View file →
| @@ -44,13 +44,12 @@ | ||
| 44 | 44 | * Controller names handled by the batch endpoint. |
| 45 | 45 | * |
| 46 | 46 | * @var array |
| 47 | 47 | */ |
| 48 | - protected $controller_names = array( 'sites', 'clients', 'tags' ); | |
| 48 | + protected $controller_names = array( 'sites', 'clients', 'costs', 'tags' ); | |
| 49 | 49 | |
| 50 | 50 | /** |
| 51 | - * Actions the dispatch below reads per group, each marked with the shape it reads its items in: | |
| 52 | - * an 'item' action passes every item on as a request body, an 'id' action casts every item to an int. | |
| 51 | + * Keep the allowed actions aligned with dispatch so supported work is not rejected or dropped. | |
| 53 | 52 | * |
| 54 | 53 | * @var array |
| 55 | 54 | */ |
| 56 | 55 | const GROUP_ACTIONS = array( |
| @@ -55,8 +54,10 @@ | ||
| 55 | 54 | */ |
| 56 | 55 | const GROUP_ACTIONS = array( |
| 57 | 56 | 'sites' => array( |
| 58 | 57 | 'create' => 'item', |
| 58 | + 'update' => 'item', | |
| 59 | + 'delete' => 'id', | |
| 59 | 60 | 'sync' => 'id', |
| 60 | 61 | 'reconnect' => 'id', |
| 61 | 62 | 'disconnect' => 'id', |
| 62 | 63 | 'suspend' => 'id', |
| @@ -68,11 +69,20 @@ | ||
| 68 | 69 | 'non-mainwp-changes' => 'id', |
| 69 | 70 | ), |
| 70 | 71 | 'clients' => array( |
| 71 | 72 | 'create' => 'item', |
| 73 | + 'update' => 'item', | |
| 74 | + 'delete' => 'id', | |
| 72 | 75 | ), |
| 76 | + 'costs' => array( | |
| 77 | + 'create' => 'item', | |
| 78 | + 'update' => 'item', | |
| 79 | + 'delete' => 'id', | |
| 80 | + ), | |
| 73 | 81 | 'tags' => array( |
| 74 | 82 | 'create' => 'item', |
| 83 | + 'update' => 'item', | |
| 84 | + 'delete' => 'id', | |
| 75 | 85 | ), |
| 76 | 86 | ); |
| 77 | 87 | |
| 78 | 88 | /** |
| @@ -154,11 +164,10 @@ | ||
| 154 | 164 | if ( empty( $body_groups ) || ! is_array( $body_groups ) ) { |
| 155 | 165 | $body_groups = (array) $request->get_body_params(); |
| 156 | 166 | } |
| 157 | 167 | |
| 158 | - // Groups the batch endpoint cannot dispatch (updates has no batch-capable create handler, | |
| 159 | - // costs has no controller at all, anything else is unknown) are reported once each instead | |
| 160 | - // of being dropped without a word or failing per item with a 405 from the core stub. | |
| 168 | + // Unsupported groups would otherwise be dropped silently or fail per item with a 405 | |
| 169 | + // from the core stub, as updates has no batch-capable create handler. | |
| 161 | 170 | // |
| 162 | 171 | // The names come from the same merged params the limit check and the dispatch read, so a |
| 163 | 172 | // group sent only in the query string is answered rather than counted in silence. A group is |
| 164 | 173 | // always an array there, so scalar params (per_page, the auth keys) name none. The body is |
| @@ -233,8 +242,18 @@ | ||
| 233 | 242 | |
| 234 | 243 | $controller_obj = $this->get_batch_controller( $con_name ); |
| 235 | 244 | |
| 236 | 245 | if ( false === $controller_obj ) { |
| 246 | + if ( ! empty( $items[ $con_name ] ) ) { | |
| 247 | + $response[ $con_name ] = array( | |
| 248 | + 'error' => array( | |
| 249 | + 'code' => 'rest_batch_group_not_supported', | |
| 250 | + /* translators: %s: batch group name */ | |
| 251 | + 'message' => sprintf( __( 'The %s group is not supported by the batch endpoint.', 'mainwp' ), $con_name ), | |
| 252 | + 'data' => array( 'status' => 400 ), | |
| 253 | + ), | |
| 254 | + ); | |
| 255 | + } | |
| 237 | 256 | continue; |
| 238 | 257 | } |
| 239 | 258 | |
| 240 | 259 | if ( ! empty( $items[ $con_name ]['create'] ) ) { |
| @@ -524,11 +543,10 @@ | ||
| 524 | 543 | * Check that a group carries only what the dispatch below can read. |
| 525 | 544 | * |
| 526 | 545 | * The dispatch indexes the group by action name and then walks each action list, so a scalar in |
| 527 | 546 | * either place would reach a foreach over something that is not a list, a JSON list carries none |
| 528 | - * of the names it indexes by, and an action it does not read is silently dropped. Items are | |
| 529 | - * checked too: a create item is passed on as a request body and an id item is cast to an int, so | |
| 530 | - * neither can be an arbitrary value. | |
| 547 | + * of the names it indexes by, and an action it does not read is silently dropped. The per-item | |
| 548 | + * helpers validate bodies and IDs so the response identifies each invalid item. | |
| 531 | 549 | * |
| 532 | 550 | * @param string $group_name Group name. |
| 533 | 551 | * @param mixed $group_items Group value taken from the request. |
| 534 | 552 | * @return bool |
| @@ -560,23 +578,8 @@ | ||
| 560 | 578 | // empty action dispatches nothing, and range() cannot answer for a count of zero. |
| 561 | 579 | if ( array() !== $action_items && array_keys( $action_items ) !== range( 0, count( $action_items ) - 1 ) ) { |
| 562 | 580 | return false; |
| 563 | 581 | } |
| 564 | - | |
| 565 | - foreach ( $action_items as $item ) { | |
| 566 | - if ( 'item' === $actions[ $action_name ] ) { | |
| 567 | - if ( ! is_array( $item ) ) { | |
| 568 | - return false; | |
| 569 | - } | |
| 570 | - } elseif ( ! ( is_int( $item ) && $item > 0 ) && ! ( is_string( $item ) && ctype_digit( $item ) && (int) $item > 0 && ltrim( $item, '0' ) === (string) (int) $item ) ) { | |
| 571 | - // A fractional or exponent string is numeric, and the (int) cast below would read | |
| 572 | - // it as a whole id the caller never asked for, so only digits are accepted. Zero | |
| 573 | - // is refused with them: every site action skips an id that casts to 0, so the | |
| 574 | - // batch would answer with neither an operation nor an error for that item. A digit | |
| 575 | - // string past PHP_INT_MAX casts to PHP_INT_MAX, a different id than the one sent. | |
| 576 | - return false; | |
| 577 | - } | |
| 578 | - } | |
| 579 | 582 | } |
| 580 | 583 | |
| 581 | 584 | return true; |
| 582 | 585 | } |
| @@ -590,12 +593,11 @@ | ||
| 590 | 593 | protected function check_batch_limit( $items ) { //phpcs:ignore -- NOSONAR complex function. |
| 591 | 594 | $limit = apply_filters( 'mainwp_rest_batch_items_limit', 100, $this->get_normalized_rest_base() ); |
| 592 | 595 | $total = 0; |
| 593 | 596 | |
| 594 | - // The updates and costs groups are rejected as a whole by batch_items(), but their items | |
| 595 | - // still count toward the cap so an oversized request is refused before anything else is | |
| 596 | - // dispatched. | |
| 597 | - $count_names = array_merge( $this->controller_names, array( 'updates', 'costs' ) ); | |
| 597 | + // The updates group is rejected as a whole by batch_items(), but its items still count | |
| 598 | + // toward the cap so an oversized request is refused before anything else is dispatched. | |
| 599 | + $count_names = array_merge( $this->controller_names, array( 'updates' ) ); | |
| 598 | 600 | |
| 599 | 601 | foreach ( $count_names as $con_name ) { |
| 600 | 602 | if ( ! empty( $items[ $con_name ] ) && is_countable( $items[ $con_name ] ) ) { |
| 601 | 603 | if ( ! empty( $items[ $con_name ]['create'] ) && is_countable( $items[ $con_name ]['create'] ) ) { |