Files
WP-Site-Sync/includes/class-rest-controller.php
2025-12-14 16:58:52 -06:00

246 lines
7.1 KiB
PHP

<?php
namespace SiteSync;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST controller for Site Sync plugin.
*
* Handles REST API endpoints for synchronization, media, and authentication.
*/
class REST_Controller {
private const ROUTE_NAMESPACE = 'site-sync/v1';
/**
* Settings instance.
*
* @var Settings
*/
private $settings;
/**
* Sync_Engine instance.
*
* @var Sync_Engine
*/
private $engine;
/**
* REST_Controller constructor.
*
* @param Settings $settings Settings instance.
* @param Sync_Engine $engine Sync_Engine instance.
*/
public function __construct( Settings $settings, Sync_Engine $engine ) {
$this->settings = $settings;
$this->engine = $engine;
}
/**
* Registers REST API hooks for the Site Sync plugin.
*
* @return void
*/
public function hooks(): void {
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
}
/**
* Registers all REST API routes for the Site Sync plugin.
*
* @return void
*/
public function register_routes(): void {
register_rest_route(
self::ROUTE_NAMESPACE,
'/handshake',
array(
'methods' => 'GET',
'callback' => array( $this, 'handshake' ),
'permission_callback' => '__return_true',
)
);
register_rest_route(
self::ROUTE_NAMESPACE,
'/inbox',
array(
'methods' => 'POST',
'callback' => array( $this, 'receive_inbox' ),
'permission_callback' => array( $this, 'check_auth' ),
)
);
register_rest_route(
self::ROUTE_NAMESPACE,
'/outbox',
array(
'methods' => 'GET',
'callback' => array( $this, 'send_outbox' ),
'permission_callback' => array( $this, 'check_auth' ),
)
);
register_rest_route(
self::ROUTE_NAMESPACE,
'/media',
array(
'methods' => 'POST',
'callback' => array( $this, 'handle_media' ),
'permission_callback' => array( $this, 'check_auth' ),
'args' => array(
'external_id' => array(
'required' => true,
'type' => 'string',
),
'mime_type' => array(
'required' => true,
'type' => 'string',
),
'filename' => array(
'required' => false,
'type' => 'string',
),
),
)
);
register_rest_route(
self::ROUTE_NAMESPACE,
'/media',
array(
'methods' => 'GET',
'callback' => array( $this, 'send_media' ),
'permission_callback' => array( $this, 'check_auth' ),
'args' => array(
'external_id' => array(
'required' => true,
'type' => 'string',
),
),
)
);
}
/**
* Handles the handshake endpoint for the Site Sync plugin.
*
* Returns basic site and plugin information for synchronization.
*
* @return WP_REST_Response
*/
public function handshake() {
$settings = $this->settings->ensure_defaults();
return new WP_REST_Response(
array(
'site_uuid' => $settings['site_uuid'],
'schema' => 'v1',
'site_url' => home_url(),
'version' => SITE_SYNC_VERSION,
'capabilities' => array(
'content' => true,
'taxonomies' => true,
'media' => true,
),
)
);
}
/**
* Handles the inbox endpoint for receiving synchronization payloads.
*
* @param WP_REST_Request $request The REST request object containing the payload.
* @return WP_REST_Response The response after processing the inbox payload.
*/
public function receive_inbox( WP_REST_Request $request ) {
$payload = $request->get_json_params() ? $request->get_json_params() : array();
$result = $this->engine->handle_inbox( $payload );
return new WP_REST_Response( $result );
}
/**
* Handles the outbox endpoint for sending synchronization payloads.
*
* @return WP_REST_Response The response containing the outbox payload.
*/
public function send_outbox() {
$result = $this->engine->provide_outbox( true );
$this->engine->commit_outbox( $result['cursor'], $result['meta']['counts'] ?? array() );
if ( isset( $result['meta'] ) ) {
unset( $result['meta'] );
}
return new WP_REST_Response( $result );
}
/**
* Handles the media endpoint for receiving and storing media files.
*
* @param WP_REST_Request $request The REST request object containing media data.
* @return WP_REST_Response|WP_Error The response after processing the media upload or WP_Error on failure.
*/
public function handle_media( WP_REST_Request $request ) {
$result = $this->engine->receive_media_stream( $request );
if ( is_wp_error( $result ) ) {
return $result;
}
return new WP_REST_Response(
array(
'status' => 'ok',
'stored' => $result,
)
);
}
/**
* Handles the media endpoint for sending media files.
*
* @param WP_REST_Request $request The REST request object containing the external_id parameter.
* @return WP_REST_Response|WP_Error The response containing the media file or WP_Error on failure.
*/
public function send_media( WP_REST_Request $request ) {
$externalId = $request->get_param( 'external_id' );
$media = $this->engine->get_media_file( $externalId );
if ( is_wp_error( $media ) ) {
return $media;
}
$response = new WP_REST_Response( $media['contents'] );
$response->set_status( 200 );
$response->header( 'Content-Type', $media['mime'] );
$response->header( 'Content-Disposition', 'attachment; filename="' . $media['filename'] . '"' );
$response->header( 'X-Site-Sync-Checksum', $media['checksum'] );
return $response;
}
/**
* Checks authentication for REST API requests.
*
* @param WP_REST_Request $request The REST request object.
* @return true|WP_Error True if authentication passes, WP_Error otherwise.
*/
public function check_auth( WP_REST_Request $request ) {
$result = Auth::verify( $request, $this->settings );
if ( is_wp_error( $result ) ) {
return $result;
}
return true;
}
}