add file transport implementation #2

Open
keith wants to merge 30 commits from feature/url-transformer-plan into main
2 changed files with 161 additions and 12 deletions
Showing only changes of commit 27919ff11f - Show all commits
+104 -9
View File
@@ -5,29 +5,39 @@ namespace WPContentSync\Settings;
final class Settings {
private const LOGGING_LEVELS = array( 'error', 'warning', 'info', 'debug' );
private const CONFLICT_STRATEGIES = array( 'last_write_wins', 'manual_review' );
private const DIRECTIONS = array( 'push', 'pull' );
private const CONTENT_TYPES = array( 'posts', 'terms', 'media', 'custom_post_types' );
private const MIN_LOGS = 10;
private const MAX_LOGS = 1000;
/**
* @var array<int, array{name: string, source_url: string, destination_url: string}>
* @var array<int, array{name: string, source_url: string, destination_url: string, username: string, application_password: string, default_direction: string, content_types: array<int, string>, url_mappings: array<int, array{source: string, destination: string}>}>
*/
private array $sync_pairs;
private string $logging_level;
private bool $automatic_url_replacement;
private string $conflict_strategy;
private int $log_retention;
private bool $debug_logging;
/**
* @param array<int, array{name: string, source_url: string, destination_url: string}> $sync_pairs Sync pairs.
* @param array<int, array{name: string, source_url: string, destination_url: string, username: string, application_password: string, default_direction: string, content_types: array<int, string>, url_mappings: array<int, array{source: string, destination: string}>}> $sync_pairs Sync pairs.
*/
private function __construct(
array $sync_pairs,
string $logging_level,
bool $automatic_url_replacement,
string $conflict_strategy
string $conflict_strategy,
int $log_retention,
bool $debug_logging
) {
$this->sync_pairs = $sync_pairs;
$this->logging_level = $logging_level;
$this->automatic_url_replacement = $automatic_url_replacement;
$this->conflict_strategy = $conflict_strategy;
$this->log_retention = $log_retention;
$this->debug_logging = $debug_logging;
}
/**
@@ -54,12 +64,16 @@ final class Settings {
self::sanitizeSyncPairs( $data['sync_pairs'] ?? array() ),
$logging_level,
$automatic_url_replacement,
$conflict_strategy
$conflict_strategy,
self::sanitizeLogRetention( $data['log_retention'] ?? 200 ),
array_key_exists( 'debug_logging', $data )
? self::sanitizeBoolean( $data['debug_logging'] )
: false
);
}
/**
* @return array<int, array{name: string, source_url: string, destination_url: string}>
* @return array<int, array{name: string, source_url: string, destination_url: string, username: string, application_password: string, default_direction: string, content_types: array<int, string>, url_mappings: array<int, array{source: string, destination: string}>}>
*/
public function syncPairs(): array {
return $this->sync_pairs;
@@ -77,6 +91,14 @@ final class Settings {
return $this->conflict_strategy;
}
public function logRetention(): int {
return $this->log_retention;
}
public function debugLoggingEnabled(): bool {
return $this->debug_logging;
}
/**
* @return array<string, mixed>
*/
@@ -86,6 +108,8 @@ final class Settings {
'logging_level' => $this->logging_level,
'automatic_url_replacement' => $this->automatic_url_replacement,
'conflict_strategy' => $this->conflict_strategy,
'log_retention' => $this->log_retention,
'debug_logging' => $this->debug_logging,
);
}
@@ -114,7 +138,7 @@ final class Settings {
/**
* @param mixed $pairs Raw sync pairs.
* @return array<int, array{name: string, source_url: string, destination_url: string}>
* @return array<int, array{name: string, source_url: string, destination_url: string, username: string, application_password: string, default_direction: string, content_types: array<int, string>, url_mappings: array<int, array{source: string, destination: string}>}>
*/
private static function sanitizeSyncPairs( $pairs ): array {
if ( ! is_array( $pairs ) ) {
@@ -131,15 +155,86 @@ final class Settings {
$name = sanitize_text_field( (string) ( $pair['name'] ?? '' ) );
$source_url = esc_url_raw( (string) ( $pair['source_url'] ?? '' ) );
$destination_url = esc_url_raw( (string) ( $pair['destination_url'] ?? '' ) );
$username = sanitize_text_field( (string) ( $pair['username'] ?? '' ) );
$password = sanitize_text_field( (string) ( $pair['application_password'] ?? '' ) );
$direction = self::sanitizeChoice( $pair['default_direction'] ?? 'push', self::DIRECTIONS, 'push' );
$content_types = self::sanitizeContentTypes( $pair['content_types'] ?? self::CONTENT_TYPES );
$url_mappings = self::sanitizeUrlMappings( $pair['url_mappings'] ?? array() );
if ( '' === $name || '' === $source_url || '' === $destination_url ) {
continue;
}
$sanitized[] = array(
'name' => $name,
'source_url' => $source_url,
'destination_url' => $destination_url,
'name' => $name,
'source_url' => $source_url,
'destination_url' => $destination_url,
'username' => $username,
'application_password' => $password,
'default_direction' => $direction,
'content_types' => $content_types,
'url_mappings' => $url_mappings,
);
}
return $sanitized;
}
/**
* @param mixed $value Raw log retention.
*/
private static function sanitizeLogRetention( $value ): int {
return min( self::MAX_LOGS, max( self::MIN_LOGS, (int) $value ) );
}
/**
* @param mixed $content_types Raw content type list.
* @return array<int, string>
*/
private static function sanitizeContentTypes( $content_types ): array {
if ( ! is_array( $content_types ) ) {
return self::CONTENT_TYPES;
}
$sanitized = array();
foreach ( $content_types as $content_type ) {
$content_type = sanitize_text_field( (string) $content_type );
if ( in_array( $content_type, self::CONTENT_TYPES, true ) && ! in_array( $content_type, $sanitized, true ) ) {
$sanitized[] = $content_type;
}
}
return $sanitized;
}
/**
* @param mixed $mappings Raw URL mappings.
* @return array<int, array{source: string, destination: string}>
*/
private static function sanitizeUrlMappings( $mappings ): array {
if ( ! is_array( $mappings ) ) {
return array();
}
$sanitized = array();
foreach ( $mappings as $mapping ) {
if ( ! is_array( $mapping ) ) {
continue;
}
$source = esc_url_raw( (string) ( $mapping['source'] ?? '' ) );
$destination = esc_url_raw( (string) ( $mapping['destination'] ?? '' ) );
if ( '' === $source || '' === $destination ) {
continue;
}
$sanitized[] = array(
'source' => $source,
'destination' => $destination,
);
}
+57 -3
View File
@@ -57,6 +57,53 @@ class SettingsTest extends TestCase {
self::assertTrue( $settings->automaticUrlReplacementEnabled() );
}
public function test_it_sanitizes_full_admin_workflow_settings(): void {
$settings = Settings::fromArray(
array(
'sync_pairs' => array(
array(
'name' => '<b>Production to Staging</b>',
'source_url' => 'https://example.test/',
'destination_url' => 'https://staging.example.test/',
'username' => '<script>codex</script>',
'application_password' => 'secret app password',
'default_direction' => 'push',
'content_types' => array( 'posts', 'terms', 'media', 'bad_type' ),
'url_mappings' => array(
array(
'source' => 'https://example.test',
'destination' => 'https://staging.example.test',
),
),
),
),
'log_retention' => '50',
'debug_logging' => '1',
)
);
$pairs = $settings->syncPairs();
self::assertSame( 'Production to Staging', $pairs[0]['name'] );
self::assertSame( 'https://example.test/', $pairs[0]['source_url'] );
self::assertSame( 'https://staging.example.test/', $pairs[0]['destination_url'] );
self::assertSame( 'codex', $pairs[0]['username'] );
self::assertSame( 'secret app password', $pairs[0]['application_password'] );
self::assertSame( 'push', $pairs[0]['default_direction'] );
self::assertSame( array( 'posts', 'terms', 'media' ), $pairs[0]['content_types'] );
self::assertSame(
array(
array(
'source' => 'https://example.test',
'destination' => 'https://staging.example.test',
),
),
$pairs[0]['url_mappings']
);
self::assertSame( 50, $settings->logRetention() );
self::assertTrue( $settings->debugLoggingEnabled() );
}
public function test_it_serializes_to_array(): void {
$settings = Settings::fromArray(
array(
@@ -74,14 +121,21 @@ class SettingsTest extends TestCase {
array(
'sync_pairs' => array(
array(
'name' => 'Staging',
'source_url' => 'https://example.test',
'destination_url' => 'https://staging.example.test',
'name' => 'Staging',
'source_url' => 'https://example.test',
'destination_url' => 'https://staging.example.test',
'username' => '',
'application_password' => '',
'default_direction' => 'push',
'content_types' => array( 'posts', 'terms', 'media', 'custom_post_types' ),
'url_mappings' => array(),
),
),
'logging_level' => 'warning',
'automatic_url_replacement' => true,
'conflict_strategy' => 'last_write_wins',
'log_retention' => 200,
'debug_logging' => false,
),
$settings->toArray()
);