223 lines
6.5 KiB
PHP
223 lines
6.5 KiB
PHP
<?php
|
|
|
|
namespace SiteSync;
|
|
|
|
use WP_Error;
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Handles HTTP transport operations for SiteSync, including GET, POST, and media transfers.
|
|
*/
|
|
class Transport {
|
|
/**
|
|
* SiteSync settings instance.
|
|
*
|
|
* @var Settings
|
|
*/
|
|
private $settings;
|
|
|
|
/**
|
|
* Transport constructor.
|
|
*
|
|
* @param Settings $settings SiteSync settings instance.
|
|
*/
|
|
public function __construct( Settings $settings ) {
|
|
$this->settings = $settings;
|
|
}
|
|
|
|
/**
|
|
* Perform a GET request to the peer.
|
|
*
|
|
* @param string $path The API path to request from the peer.
|
|
*/
|
|
public function get( string $path ) {
|
|
$url = $this->build_url( $path );
|
|
$headers = $this->build_headers( '' );
|
|
|
|
$response = wp_remote_get(
|
|
$url,
|
|
array(
|
|
'headers' => $headers,
|
|
'timeout' => $this->timeout( 180 ),
|
|
)
|
|
);
|
|
|
|
return $this->parse_response( $response );
|
|
}
|
|
|
|
/**
|
|
* Perform a POST request with JSON body to the peer.
|
|
*
|
|
* @param string $path The API path to request from the peer.
|
|
* @param array $body The data to send as the JSON body.
|
|
*/
|
|
public function post( string $path, array $body ) {
|
|
$payload = wp_json_encode( $body );
|
|
$url = $this->build_url( $path );
|
|
$headers = array_merge(
|
|
$this->build_headers( $payload ),
|
|
array( 'Content-Type' => 'application/json' )
|
|
);
|
|
|
|
$response = wp_remote_post(
|
|
$url,
|
|
array(
|
|
'headers' => $headers,
|
|
'body' => $payload,
|
|
'timeout' => $this->timeout( 180 ),
|
|
)
|
|
);
|
|
|
|
return $this->parse_response( $response );
|
|
}
|
|
|
|
/**
|
|
* Perform a POST with raw body (for media uploads) — scaffold for future use.
|
|
*
|
|
* @param string $path The API path to request from the peer.
|
|
* @param mixed $body The raw body to send (e.g., file contents).
|
|
* @param array $extraHeaders Additional headers to include in the request.
|
|
*/
|
|
public function post_stream( string $path, $body, array $extraHeaders = array() ) {
|
|
$url = $this->build_url( $path );
|
|
$payload = $body;
|
|
$headers = array_merge(
|
|
$this->build_headers( is_string( $payload ) ? $payload : '' ),
|
|
$extraHeaders
|
|
);
|
|
|
|
$response = wp_remote_post(
|
|
$url,
|
|
array(
|
|
'headers' => $headers,
|
|
'body' => $payload,
|
|
'timeout' => $this->timeout( 180 ),
|
|
)
|
|
);
|
|
|
|
return $this->parse_response( $response );
|
|
}
|
|
|
|
/**
|
|
* Fetch binary media from peer (no JSON decoding).
|
|
*
|
|
* @param string $externalId The external media ID to fetch.
|
|
*/
|
|
public function fetch_media_by_external_id( string $externalId ) {
|
|
$path = '/wp-json/site-sync/v1/media?external_id=' . rawurlencode( $externalId );
|
|
$url = $this->build_url( $path );
|
|
$headers = $this->build_headers( '' );
|
|
|
|
$response = wp_remote_get(
|
|
$url,
|
|
array(
|
|
'headers' => $headers,
|
|
'timeout' => $this->timeout( 180 ),
|
|
)
|
|
);
|
|
|
|
if ( is_wp_error( $response ) ) {
|
|
return $response;
|
|
}
|
|
|
|
$code = wp_remote_retrieve_response_code( $response );
|
|
if ( $code !== 200 ) {
|
|
return new WP_Error(
|
|
'site_sync_media_fetch_failed',
|
|
sprintf( 'Media fetch failed with HTTP %d', $code ),
|
|
array( 'body' => wp_remote_retrieve_body( $response ) )
|
|
);
|
|
}
|
|
|
|
$headersOut = wp_remote_retrieve_headers( $response );
|
|
|
|
return array(
|
|
'body' => wp_remote_retrieve_body( $response ),
|
|
'headers' => $headersOut,
|
|
'checksum' => is_array( $headersOut ) && isset( $headersOut['x-site-sync-checksum'] ) ? $headersOut['x-site-sync-checksum'] : null,
|
|
'code' => $code,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Run a handshake to verify connectivity and identity.
|
|
*/
|
|
public function handshake() {
|
|
return $this->get( '/wp-json/site-sync/v1/handshake' );
|
|
}
|
|
|
|
/**
|
|
* Build the full URL to the peer for a given API path.
|
|
*
|
|
* @param string $path The API path to append to the peer URL.
|
|
* @return string The full URL.
|
|
*/
|
|
private function build_url( string $path ): string {
|
|
$peer = rtrim( $this->settings->get( 'peer_url', '' ), '/' );
|
|
$path = '/' . ltrim( $path, '/' );
|
|
|
|
return $peer . $path;
|
|
}
|
|
|
|
/**
|
|
* Build the HTTP headers required for SiteSync authentication.
|
|
*
|
|
* @param string $body The request body to be signed.
|
|
* @return array The array of headers.
|
|
*/
|
|
private function build_headers( string $body ): array {
|
|
$siteKey = $this->settings->get( 'site_uuid' );
|
|
$secret = $this->settings->get( 'shared_key' );
|
|
$timestamp = (string) time();
|
|
$signature = Auth::build_signature( $siteKey, $timestamp, $body, $secret );
|
|
|
|
return array(
|
|
'X-Site-Sync-Key' => $siteKey,
|
|
'X-Site-Sync-Timestamp' => $timestamp,
|
|
'X-Site-Sync-Signature' => $signature,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Parse the HTTP response and return decoded data or WP_Error on failure.
|
|
*
|
|
* @param mixed $response The response from wp_remote_get or wp_remote_post.
|
|
* @return mixed|WP_Error Decoded response data, raw body, or WP_Error on error.
|
|
*/
|
|
private function parse_response( $response ) {
|
|
if ( is_wp_error( $response ) ) {
|
|
return $response;
|
|
}
|
|
|
|
$code = wp_remote_retrieve_response_code( $response );
|
|
$body = wp_remote_retrieve_body( $response );
|
|
$data = json_decode( $body, true );
|
|
|
|
if ( $code >= 200 && $code < 300 ) {
|
|
return $data ?? $body;
|
|
}
|
|
|
|
return new WP_Error(
|
|
'site_sync_http_error',
|
|
sprintf( 'HTTP %d from peer', $code ),
|
|
array( 'body' => $body )
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get the HTTP timeout value, allowing for filtering.
|
|
*
|
|
* @param int $default The default timeout value in seconds.
|
|
* @return int The filtered timeout value, minimum 5 seconds.
|
|
*/
|
|
private function timeout( int $default ): int { // phpcs:ignore
|
|
$filtered = apply_filters( 'site_sync/http_timeout', $default );
|
|
$value = is_numeric( $filtered ) ? (int) $filtered : $default;
|
|
|
|
return max( 5, $value );
|
|
}
|
|
}
|