212 lines
6.7 KiB
PHP
212 lines
6.7 KiB
PHP
<?php
|
|
|
|
namespace SiteSync;
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Handles the persistent state for the Site Sync plugin, including sync checkpoints, tombstones, and mappings.
|
|
*/
|
|
class State {
|
|
public const OPTION_KEY = 'site_sync_state';
|
|
private const TOMBSTONE_LIMIT = 200;
|
|
|
|
/**
|
|
* Default state structure for Site Sync plugin.
|
|
*
|
|
* @var array
|
|
*/
|
|
private $defaults = array(
|
|
'last_sent' => array(
|
|
'posts' => array(
|
|
'modified' => null,
|
|
'id' => 0,
|
|
),
|
|
'terms' => array(
|
|
'modified' => null,
|
|
'id' => 0,
|
|
),
|
|
'media' => array(
|
|
'modified' => null,
|
|
'id' => 0,
|
|
),
|
|
),
|
|
'last_received' => array(
|
|
'posts' => array(
|
|
'modified' => null,
|
|
'id' => 0,
|
|
),
|
|
'terms' => array(
|
|
'modified' => null,
|
|
'id' => 0,
|
|
),
|
|
'media' => array(
|
|
'modified' => null,
|
|
'id' => 0,
|
|
),
|
|
),
|
|
'last_counts' => array(
|
|
'sent' => array(
|
|
'posts' => 0,
|
|
'terms' => 0,
|
|
'media' => 0,
|
|
'tombstones' => 0,
|
|
),
|
|
'received' => array(
|
|
'applied' => 0,
|
|
'skipped' => 0,
|
|
'errors' => 0,
|
|
),
|
|
),
|
|
'mappings' => array(
|
|
'posts' => array(), // phpcs:ignore local_id => array( 'peer' => peer_id, 'peer_site' => site_uuid )
|
|
'terms' => array(),
|
|
'media' => array(),
|
|
),
|
|
'last_deleted' => array(
|
|
'posts' => 0,
|
|
'terms' => 0,
|
|
'media' => 0,
|
|
),
|
|
'tombstones' => array(),
|
|
'last_run' => null,
|
|
'last_error' => null,
|
|
);
|
|
|
|
/**
|
|
* Retrieves the current persistent state for the Site Sync plugin.
|
|
*
|
|
* @return array The current state array merged with defaults.
|
|
*/
|
|
public function get(): array {
|
|
$stored = get_option( self::OPTION_KEY, array() );
|
|
$data = is_array( $stored ) ? $stored : array();
|
|
|
|
return array_merge( $this->defaults, $data );
|
|
}
|
|
|
|
/**
|
|
* Updates the persistent state with the provided data.
|
|
*
|
|
* @param array $data The data to merge into the current state.
|
|
*/
|
|
public function update( array $data ): void {
|
|
$state = $this->get();
|
|
$state = array_merge( $state, $data );
|
|
update_option( self::OPTION_KEY, $state );
|
|
}
|
|
|
|
/**
|
|
* Records the results of a sync run, including summary counts and errors.
|
|
*
|
|
* @param array $summary An array containing 'counts' and optionally 'error' from the sync run.
|
|
*/
|
|
public function record_run( array $summary ): void {
|
|
$state = $this->get();
|
|
$state['last_run'] = current_time( 'mysql' );
|
|
$state['last_error'] = $summary['error'] ?? null;
|
|
|
|
if ( isset( $summary['counts'] ) && is_array( $summary['counts'] ) ) {
|
|
$state['last_counts'] = $this->merge_counts( $summary['counts'] );
|
|
}
|
|
|
|
update_option( self::OPTION_KEY, $state );
|
|
}
|
|
|
|
/**
|
|
* Reset checkpoints and tombstone queue to force a full resend on next sync.
|
|
*/
|
|
public function reset(): void {
|
|
$state = $this->get();
|
|
$state['last_sent'] = $this->defaults['last_sent'];
|
|
$state['last_received'] = $this->defaults['last_received'];
|
|
$state['tombstones'] = array();
|
|
$state['last_counts'] = $this->defaults['last_counts'];
|
|
$state['last_error'] = null;
|
|
update_option( self::OPTION_KEY, $state );
|
|
}
|
|
|
|
/**
|
|
* Adds a tombstone item to the queue, ensuring no duplicates based on external_id and type.
|
|
*
|
|
* @param array $item The tombstone item to enqueue. Should contain 'external_id', 'type', and optionally 'taxonomy'.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function enqueue_tombstone( array $item ): void {
|
|
// Avoid duplicates: check if same external_id/type already queued.
|
|
$state = $this->get();
|
|
if ( ! empty( $item['external_id'] ) && ! empty( $item['type'] ) ) {
|
|
$existing = array_filter(
|
|
$state['tombstones'],
|
|
function ( $t ) use ( $item ) {
|
|
return isset( $t['external_id'], $t['type'] )
|
|
&& $t['external_id'] === $item['external_id']
|
|
&& $t['type'] === $item['type']
|
|
&& ( ! isset( $item['taxonomy'] ) || ( $t['taxonomy'] ?? '' ) === ( $item['taxonomy'] ?? '' ) );
|
|
}
|
|
);
|
|
if ( ! empty( $existing ) ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
$state['tombstones'][] = $item;
|
|
if ( count( $state['tombstones'] ) > self::TOMBSTONE_LIMIT ) {
|
|
$state['tombstones'] = array_slice( $state['tombstones'], -self::TOMBSTONE_LIMIT );
|
|
}
|
|
update_option( self::OPTION_KEY, $state );
|
|
}
|
|
|
|
/**
|
|
* Removes and returns up to $limit tombstone items from the queue.
|
|
*
|
|
* @param int $limit The maximum number of tombstone items to consume.
|
|
* @return array The consumed tombstone items.
|
|
*/
|
|
public function consume_tombstones( int $limit = 50 ): array {
|
|
$state = $this->get();
|
|
$queue = $state['tombstones'] ?? array();
|
|
$items = array_slice( $queue, 0, $limit );
|
|
$state['tombstones'] = array_slice( $queue, $limit );
|
|
update_option( self::OPTION_KEY, $state );
|
|
|
|
return $items;
|
|
}
|
|
|
|
/**
|
|
* Returns up to $limit tombstone items from the queue without removing them.
|
|
*
|
|
* @param int $limit The maximum number of tombstone items to return.
|
|
* @return array The tombstone items.
|
|
*/
|
|
public function peek_tombstones( int $limit = 50 ): array {
|
|
$state = $this->get();
|
|
$queue = $state['tombstones'] ?? array();
|
|
|
|
return array_slice( $queue, 0, $limit );
|
|
}
|
|
|
|
/**
|
|
* Merges provided counts with the default counts, ensuring all values are integers.
|
|
*
|
|
* @param array $counts The counts to merge, containing 'sent' and 'received' arrays.
|
|
* @return array The merged counts array.
|
|
*/
|
|
private function merge_counts( array $counts ): array {
|
|
$defaults = $this->defaults['last_counts'];
|
|
$sent = $counts['sent'] ?? array();
|
|
$received = $counts['received'] ?? array();
|
|
|
|
$mergedSent = array_merge( $defaults['sent'], array_map( 'intval', $sent ) );
|
|
$mergedReceived = array_merge( $defaults['received'], array_map( 'intval', $received ) );
|
|
|
|
return array(
|
|
'sent' => $mergedSent,
|
|
'received' => $mergedReceived,
|
|
);
|
|
}
|
|
}
|