get_server_key() ][] = $client; } foreach ( $groups as $group ) { foreach ( array_chunk( $group, self::MAX_ITEMS ) as $chunk ) { self::refresh_chunk( $chunk ); } } delete_site_transient( self::LOCK ); if ( $reinject ) { self::reinject( $clients ); } } /** * Send one batched request for products sharing a server. * * @param SE_License_SDK_Client[] $clients Clients. * * @return void */ private static function refresh_chunk( array $clients ) { $sender = reset( $clients ); // Server known to be down: don't even try, and don't let any product // retry before the breaker closes. $retry_in = $sender->get_server_retry_in(); if ( $retry_in > 0 ) { foreach ( $clients as $client ) { $client->updater()->store_update_failure( $retry_in ); } return; } // This server told us recently it has no batch route: go straight to // per-product requests instead of wasting one on the batch every time. $no_batch_key = 'se_sdk_nobatch_' . $sender->get_server_key(); if ( get_site_transient( $no_batch_key ) ) { foreach ( $clients as $client ) { $client->updater()->refresh_single(); } return; } $items = []; foreach ( $clients as $client ) { $items[ self::item_key( $client ) ] = $client->get_request_identity(); } $response = $sender->request( [ 'route' => 'check-updates', 'body' => array_merge( $sender->get_admin_info(), [ 'items' => $items ] ), 'timeout' => 15, ] ); if ( ! empty( $response['success'] ) && isset( $response['data']['results'] ) && is_array( $response['data']['results'] ) ) { $results = $response['data']['results']; // Site-wide instructions (pause_background) from the server. $sender->apply_server_directives( $response['data'] ); foreach ( $clients as $client ) { $result = $results[ self::item_key( $client ) ] ?? null; if ( is_array( $result ) && ! empty( $result['success'] ) && isset( $result['data'] ) && is_array( $result['data'] ) ) { $data = $result['data']; // A batch-level next_check_in applies to items that don't set their own. if ( ! isset( $data['next_check_in'] ) && isset( $response['data']['next_check_in'] ) ) { $data['next_check_in'] = $response['data']['next_check_in']; } $client->updater()->store_update_response( [ 'success' => true, 'data' => $data ] ); } else { $client->updater()->store_update_response( [ 'success' => false ] ); } } return; } if ( ! empty( $response['transport_error'] ) ) { // The breaker is open now; wait it out. $retry_in = $sender->get_server_retry_in(); foreach ( $clients as $client ) { $client->updater()->store_update_failure( $retry_in ); } return; } // The server answered but has no batch route (older license-management // addon): fall back to one request per product. If one of those fails // at transport level the breaker opens and the rest return at once. if ( 'rest_no_route' === ( $response['code'] ?? '' ) ) { set_site_transient( $no_batch_key, 1, DAY_IN_SECONDS ); } foreach ( $clients as $client ) { $client->updater()->refresh_single(); } } /** * Re-save WordPress's update transients so our (now cached) rows are * injected by the pre_set filters. * * @param SE_License_SDK_Client[] $clients Refreshed clients. * * @return void */ private static function reinject( array $clients ) { $types = []; foreach ( $clients as $client ) { $types[ $client->isPlugin() ? 'update_plugins' : 'update_themes' ] = true; } foreach ( array_keys( $types ) as $transient ) { $value = get_site_transient( $transient ); // When WordPress has no transient yet it builds one on its next check, // and our filter reads the fresh cache then. if ( is_object( $value ) ) { set_site_transient( $transient, $value ); } } } /** * Every registered product with updates enabled. * * @return SE_License_SDK_Client[] */ private static function get_update_clients(): array { if ( ! class_exists( 'SE_License_SDK', false ) ) { return []; } $clients = []; foreach ( SE_License_SDK::get_all_registered() as $client ) { if ( $client instanceof SE_License_SDK_Client && $client->maybe_init_update() ) { $clients[] = $client; } } return $clients; } /** * Products whose update cache (including a failure marker) has expired. * * @return SE_License_SDK_Client[] */ private static function get_due_clients(): array { return array_values( array_filter( self::get_update_clients(), function ( SE_License_SDK_Client $client ) { return $client->updater()->needs_refresh(); } ) ); } /** * Key a product by inside the batch payload. * * @param SE_License_SDK_Client $client Client. * * @return string */ private static function item_key( SE_License_SDK_Client $client ): string { return sanitize_key( $client->getSlug() ); } } // End of file SE_License_SDK_Update_Batch.php.