3600, 'twicedaily' => 43200, 'daily' => 86400, 'weekly' => 604800, ]; public function __construct( public readonly bool $enabled, public readonly int $interval, public readonly bool $allTables, public readonly ?string $dir, public readonly int $keepLocal, public readonly int $keepRemote, public readonly string $dropboxAppKey, public readonly string $dropboxAppSecret, public readonly string $dropboxRefreshToken, public readonly ?string $dropboxPath, public readonly string $connectAppKey = '', public readonly string $connectToken = '', public readonly bool $intervalLocked = false, ) { } public static function fromConstants(): self { $values = []; foreach (self::NAMES as $name) { if (defined('FRESHET_BACKUP_' . $name)) { $values[$name] = constant('FRESHET_BACKUP_' . $name); } } if (!isset($values['INTERVAL']) && function_exists('get_option')) { $values['INTERVAL_CHOICE'] = get_option(self::INTERVAL_OPTION, null); } return self::fromArray($values); } /** @param array $v keyed by the constant name without its FRESHET_BACKUP_ prefix */ public static function fromArray(array $v): self { $connectToken = (string) self::text($v['CONNECT_TOKEN'] ?? null); $choice = is_numeric($v['INTERVAL_CHOICE'] ?? null) ? (int) $v['INTERVAL_CHOICE'] : 0; return new self( // A connection made on the screen switches backups on, unless a constant says otherwise. array_key_exists('ENABLED', $v) ? self::bool($v['ENABLED']) : $connectToken !== '', array_key_exists('INTERVAL', $v) || !in_array($choice, self::INTERVAL_CHOICES, true) ? self::interval($v['INTERVAL'] ?? null) : $choice, self::bool($v['ALL_TABLES'] ?? false), self::text($v['DIR'] ?? null), self::count($v['KEEP_LOCAL'] ?? null, self::DEFAULT_KEEP_LOCAL), self::count($v['KEEP_REMOTE'] ?? null, self::DEFAULT_KEEP_REMOTE), (string) self::text($v['DROPBOX_APP_KEY'] ?? null), (string) self::text($v['DROPBOX_APP_SECRET'] ?? null), (string) self::text($v['DROPBOX_REFRESH_TOKEN'] ?? null), self::text($v['DROPBOX_PATH'] ?? null), (string) self::text($v['CONNECT_APP_KEY'] ?? null), $connectToken, array_key_exists('INTERVAL', $v), ); } /** The same settings with another interval: what the screen's choice becomes before the next load reads it back. */ public function withInterval(int $seconds): self { return new self(...['interval' => $seconds] + get_object_vars($this)); } /** True when the three FRESHET_BACKUP_DROPBOX_* constants (your own app) are all set. */ public function dropboxConfigured(): bool { return $this->dropboxAppKey !== '' && $this->dropboxAppSecret !== '' && $this->dropboxRefreshToken !== ''; } /** True when the screen's Connect button has put a token into wp-config.php for this host. */ public function connected(): bool { return $this->connectAppKey !== '' && $this->connectToken !== ''; } /** * How this site signs in to Dropbox, or null when it does not. The constants * for your own app take precedence over a connection made on the screen. * * @return array{source: string, key: string, secret: string, token: string}|null */ public function dropbox(): ?array { if ($this->dropboxConfigured()) { return ['source' => 'constants', 'key' => $this->dropboxAppKey, 'secret' => $this->dropboxAppSecret, 'token' => $this->dropboxRefreshToken]; } if ($this->connected()) { return ['source' => 'connected', 'key' => $this->connectAppKey, 'secret' => '', 'token' => $this->connectToken]; } return null; } /** The Dropbox folder, normalised to a leading slash and no trailing one ("" is the app folder's root). */ public function dropboxFolder(string $host): string { $path = $this->dropboxPath ?? '/' . $host; $path = trim(str_replace('\\', '/', $path), '/'); return $path === '' ? '' : '/' . $path; } private static function bool(mixed $v): bool { return filter_var($v, FILTER_VALIDATE_BOOLEAN); } private static function text(mixed $v): ?string { if (!is_scalar($v)) { return null; } $v = trim((string) $v); return $v === '' ? null : $v; } private static function interval(mixed $v): int { if (is_string($v) && isset(self::NAMED_INTERVALS[strtolower($v)])) { return self::NAMED_INTERVALS[strtolower($v)]; } if (is_numeric($v) && (int) $v > 0) { return max(self::MIN_INTERVAL, (int) $v); } return self::DEFAULT_INTERVAL; } private static function count(mixed $v, int $default): int { return is_numeric($v) && (int) $v >= 1 ? (int) $v : $default; } }