✨feature: Initial functional push
This commit is contained in:
211
includes/class-state.php
Normal file
211
includes/class-state.php
Normal file
@@ -0,0 +1,211 @@
|
||||
<?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,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user