← All changes
|
jetpack_vendor/automattic/jetpack-backup/src/class-rest-controller.php
+80
-6
13.8.3
→
16.3-a.7
View file →
| @@ -9,9 +9,9 @@ | ||
| 9 | 9 | // After changing this file, consider increasing the version number ("VXXX") in all the files using this namespace, in |
| 10 | 10 | // order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide |
| 11 | 11 | // to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin |
| 12 | 12 | // are installed, or in some other cases). |
| 13 | -namespace Automattic\Jetpack\Backup\V0004; | |
| 13 | +namespace Automattic\Jetpack\Backup\V0005; | |
| 14 | 14 | |
| 15 | 15 | use Automattic\Jetpack\Connection\Client; |
| 16 | 16 | use Automattic\Jetpack\Connection\Rest_Authentication; |
| 17 | 17 | use Automattic\Jetpack\Sync\Actions as Sync_Actions; |
| @@ -19,9 +19,8 @@ | ||
| 19 | 19 | use Jetpack_Options; |
| 20 | 20 | use WP_Error; |
| 21 | 21 | use WP_REST_Request; |
| 22 | 22 | use WP_REST_Server; |
| 23 | -// phpcs:ignore WordPress.Utils.I18nTextDomainFixer.MissingArgs | |
| 24 | 23 | use function esc_html__; |
| 25 | 24 | use function get_comment; |
| 26 | 25 | use function get_comment_meta; |
| 27 | 26 | use function get_metadata; |
| @@ -34,9 +33,11 @@ | ||
| 34 | 33 | use function is_wp_error; |
| 35 | 34 | use function register_rest_route; |
| 36 | 35 | use function rest_authorization_required_code; |
| 37 | 36 | use function rest_ensure_response; |
| 37 | +use function wp_cache_flush; | |
| 38 | 38 | use function wp_remote_retrieve_response_code; |
| 39 | +use function wp_using_ext_object_cache; | |
| 39 | 40 | |
| 40 | 41 | /** |
| 41 | 42 | * Registers the REST routes for Backup. |
| 42 | 43 | */ |
| @@ -231,8 +232,19 @@ | ||
| 231 | 232 | 'callback' => __CLASS__ . '::get_site_backup_preflight', |
| 232 | 233 | 'permission_callback' => __NAMESPACE__ . '\Jetpack_Backup::backups_permissions_callback', |
| 233 | 234 | ) |
| 234 | 235 | ); |
| 236 | + | |
| 237 | + // Flush the object cache, which a database restore leaves stale. | |
| 238 | + register_rest_route( | |
| 239 | + 'jetpack/v4', | |
| 240 | + '/site/cache/flush', | |
| 241 | + array( | |
| 242 | + 'methods' => WP_REST_Server::CREATABLE, | |
| 243 | + 'callback' => __CLASS__ . '::flush_object_cache', | |
| 244 | + 'permission_callback' => __CLASS__ . '::backup_permissions_callback', | |
| 245 | + ) | |
| 246 | + ); | |
| 235 | 247 | } |
| 236 | 248 | |
| 237 | 249 | /** |
| 238 | 250 | * The Backup endpoints should only be available via site-level authentication. |
| @@ -583,9 +595,13 @@ | ||
| 583 | 595 | null, |
| 584 | 596 | 'wpcom' |
| 585 | 597 | ); |
| 586 | 598 | |
| 587 | - if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { | |
| 599 | + // Cast: `wp_remote_retrieve_response_code()` hands back whatever the | |
| 600 | + // transport put there, and a numeric-string `'200'` fails this | |
| 601 | + // strict comparison — so a perfectly good answer is discarded and | |
| 602 | + // the route reports that the site has no rewindable event to undo. | |
| 603 | + if ( 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { | |
| 588 | 604 | return null; |
| 589 | 605 | } |
| 590 | 606 | |
| 591 | 607 | $body = json_decode( $response['body'], true ); |
| @@ -701,9 +717,14 @@ | ||
| 701 | 717 | |
| 702 | 718 | /** |
| 703 | 719 | * Fetch backup preflight status |
| 704 | 720 | * |
| 705 | - * @return array | |
| 721 | + * The `array` this used to advertise was never a shape it could return; | |
| 722 | + * both branches below hand back an object. Corrected because Phan reads | |
| 723 | + * it, and a caller that believed it would be calling array offsets on a | |
| 724 | + * `WP_REST_Response`. | |
| 725 | + * | |
| 726 | + * @return \WP_REST_Response|WP_Error The preflight payload, or a WP_Error if WordPress.com refused or could not be reached. | |
| 706 | 727 | */ |
| 707 | 728 | public static function get_site_backup_preflight() { |
| 708 | 729 | $blog_id = Jetpack_Options::get_option( 'id' ); |
| 709 | 730 | |
| @@ -722,19 +743,72 @@ | ||
| 722 | 743 | array( 'status' => 500 ) |
| 723 | 744 | ); |
| 724 | 745 | } |
| 725 | 746 | |
| 726 | - $response_code = wp_remote_retrieve_response_code( $response ); | |
| 747 | + // Cast and then clamp, and this route needs both more than any | |
| 748 | + // other in the package. `wp_remote_retrieve_response_code()` hands | |
| 749 | + // back whatever the transport put there, so an uncast `'200'` fails | |
| 750 | + // the comparison below — and this is the one place that then | |
| 751 | + // forwards the status it just read straight into `data.status`. | |
| 752 | + // WordPress runs that through `absint()`, so the error envelope is | |
| 753 | + // served as HTTP 200: `apiFetch` resolves, nothing throws, and a | |
| 754 | + // failure arrives at the caller looking like a successful preflight. | |
| 755 | + // | |
| 756 | + // The clamp covers what the cast cannot. `(int)` is total, so an | |
| 757 | + // absent or unparseable code becomes `0` and `'2 Bad'` becomes `2`, | |
| 758 | + // and neither is a status `status_header()` can emit. The same | |
| 759 | + // reasoning, written out at length, is on | |
| 760 | + // `REST\Rest_Controller::upstream_error()`; it is open-coded here | |
| 761 | + // rather than borrowed because that helper also attaches | |
| 762 | + // WordPress.com's own reason under a `wpcom` key, which would change | |
| 763 | + // this route's response shape for callers we do not control. | |
| 764 | + $response_code = (int) wp_remote_retrieve_response_code( $response ); | |
| 727 | 765 | if ( 200 !== $response_code ) { |
| 728 | 766 | return new WP_Error( |
| 729 | 767 | 'http_error_fetch_preflight', |
| 730 | 768 | wp_remote_retrieve_response_message( $response ), |
| 731 | - array( 'status' => $response_code ) | |
| 769 | + array( 'status' => $response_code >= 400 && $response_code <= 599 ? $response_code : 500 ) | |
| 732 | 770 | ); |
| 733 | 771 | } |
| 734 | 772 | |
| 735 | 773 | $body = json_decode( $response['body'], true ); |
| 736 | 774 | return rest_ensure_response( $body ); |
| 775 | + } | |
| 776 | + | |
| 777 | + /** | |
| 778 | + * Flush the object cache. | |
| 779 | + * | |
| 780 | + * A database restore writes MySQL directly and never tells WordPress, so | |
| 781 | + * a site with a persistent cache keeps serving pre-restore rows until | |
| 782 | + * something busts it. | |
| 783 | + * | |
| 784 | + * @access public | |
| 785 | + * @static | |
| 786 | + * | |
| 787 | + * @return \WP_REST_Response Whether the cache was flushed, carrying a `reason` whenever it was not. | |
| 788 | + */ | |
| 789 | + public static function flush_object_cache() { | |
| 790 | + if ( ! wp_using_ext_object_cache() ) { | |
| 791 | + return rest_ensure_response( | |
| 792 | + array( | |
| 793 | + 'flushed' => false, | |
| 794 | + 'reason' => 'no_ext_object_cache', | |
| 795 | + ) | |
| 796 | + ); | |
| 797 | + } | |
| 798 | + | |
| 799 | + // Core documents false as the only failure signal, so a drop-in whose | |
| 800 | + // flush() returns nothing must not be reported as a failed flush. | |
| 801 | + if ( false === wp_cache_flush() ) { | |
| 802 | + return rest_ensure_response( | |
| 803 | + array( | |
| 804 | + 'flushed' => false, | |
| 805 | + 'reason' => 'flush_failed', | |
| 806 | + ) | |
| 807 | + ); | |
| 808 | + } | |
| 809 | + | |
| 810 | + return rest_ensure_response( array( 'flushed' => true ) ); | |
| 737 | 811 | } |
| 738 | 812 | |
| 739 | 813 | /** |
| 740 | 814 | * Fetch option row by option name. |