$hasUpdate, 'force' => $hasUpdate && $wantsForce && $canInstall, 'enabled' => $enabled, 'current_version' => $current, 'latest_version' => $latest, 'min_version' => $minVersion, 'title' => (string) ($config['title'] ?? ''), 'notes' => (string) ($config['notes'] ?? ''), 'platform' => self::normalizePlatform($platform), 'arch' => self::normalizeArch($arch), 'package' => $canInstall ? $package : null, 'can_install' => $hasUpdate && $canInstall, ]; } public static function compareVersion(string $left, string $right): int { return self::versionParts($left) <=> self::versionParts($right); } public static function normalizeVersion(string $version): string { $version = trim($version); if ($version === '') { return ''; } if (!preg_match('/^\d+(?:\.\d+){0,3}$/', $version)) { return ''; } $parts = array_map(static fn(string $part): int => (int) $part, explode('.', $version)); $parts = array_pad(array_slice($parts, 0, 3), 3, 0); return implode('.', $parts); } public static function packageKey(string $platform, string $arch): string { $os = self::normalizePlatform($platform); $cpu = self::normalizeArch($arch); if ($os === '' || $cpu === '') { return ''; } $key = $os . '_' . $cpu; return in_array($key, self::PLATFORMS, true) ? $key : ''; } public static function normalizePlatform(string $platform): string { $value = strtolower(trim($platform)); return match ($value) { 'windows', 'win', 'win32', 'win64' => 'windows', 'macos', 'mac', 'darwin', 'osx' => 'macos', default => '', }; } public static function normalizeArch(string $arch): string { $value = strtolower(trim($arch)); return match ($value) { 'x64', 'amd64', 'x86_64', 'x86-64' => 'x64', 'arm64', 'aarch64' => 'arm64', default => '', }; } /** * @return array */ private static function loadStored(): array { $packages = ConfigService::get(self::CONFIG_TYPE, 'packages', []); if (!is_array($packages)) { $packages = []; } return [ 'enabled' => self::asFlag(ConfigService::get(self::CONFIG_TYPE, 'enabled', 1)), 'latest_version' => (string) (ConfigService::get(self::CONFIG_TYPE, 'latest_version', '') ?? ''), 'min_version' => (string) (ConfigService::get(self::CONFIG_TYPE, 'min_version', '') ?? ''), 'force_update' => self::asFlag(ConfigService::get(self::CONFIG_TYPE, 'force_update', 0)), 'title' => (string) (ConfigService::get(self::CONFIG_TYPE, 'title', '') ?? ''), 'notes' => (string) (ConfigService::get(self::CONFIG_TYPE, 'notes', '') ?? ''), 'packages' => self::normalizePackages($packages, persist: false), ]; } /** * @param array $params * @return array */ public static function normalizeInput(array $params): array { $packages = $params['packages'] ?? []; if (!is_array($packages)) { $packages = []; } foreach (self::PLATFORMS as $key) { if (!isset($packages[$key]) || !is_array($packages[$key])) { $packages[$key] = [ 'url' => (string) ($params[$key . '_url'] ?? ''), 'sha256' => (string) ($params[$key . '_sha256'] ?? ''), 'size' => $params[$key . '_size'] ?? 0, 'filename' => (string) ($params[$key . '_filename'] ?? ''), ]; } } return [ 'enabled' => self::asFlag($params['enabled'] ?? 0), 'latest_version' => self::normalizeVersion((string) ($params['latest_version'] ?? '')), 'min_version' => self::normalizeVersion((string) ($params['min_version'] ?? '')), 'force_update' => self::asFlag($params['force_update'] ?? 0), 'title' => mb_substr(trim((string) ($params['title'] ?? '')), 0, 80), 'notes' => mb_substr(trim((string) ($params['notes'] ?? '')), 0, 4000), 'packages' => self::normalizePackages($packages, persist: true), ]; } /** * @param array $config * @return array */ private static function present(array $config): array { $packages = []; foreach (self::PLATFORMS as $key) { $packages[$key] = self::publicPackage($config['packages'][$key] ?? []); } $config['packages'] = $packages; return $config; } /** * @param array $packages * @return array> */ private static function normalizePackages(array $packages, bool $persist): array { $normalized = []; foreach (self::PLATFORMS as $key) { $row = is_array($packages[$key] ?? null) ? $packages[$key] : []; $url = trim((string) ($row['url'] ?? '')); if ($persist && $url !== '') { $url = FileService::setFileUrl($url); } $sha256 = strtolower(trim((string) ($row['sha256'] ?? ''))); $filename = trim((string) ($row['filename'] ?? '')); $size = (int) ($row['size'] ?? 0); if ($persist) { $filled = self::fillLocalPackageMeta($url, $sha256, $size, $filename); $url = $filled['url']; $sha256 = $filled['sha256']; $size = $filled['size']; $filename = $filled['filename']; } $normalized[$key] = [ 'url' => $url, 'sha256' => $sha256, 'size' => max(0, $size), 'filename' => mb_substr($filename, 0, 180), ]; } return $normalized; } /** * @param array $row * @return array{url:string,sha256:string,size:int,filename:string} */ private static function publicPackage(array $row): array { $plain = self::plainPackage($row); $plain['url'] = $plain['url'] === '' ? '' : FileService::getFileUrl($plain['url']); return $plain; } /** * @param array $row * @return array{url:string,sha256:string,size:int,filename:string} */ private static function plainPackage(array $row): array { return [ 'url' => trim((string) ($row['url'] ?? '')), 'sha256' => strtolower(trim((string) ($row['sha256'] ?? ''))), 'size' => max(0, (int) ($row['size'] ?? 0)), 'filename' => (string) ($row['filename'] ?? ''), ]; } /** * @return array{url:string,sha256:string,size:int,filename:string} */ private static function emptyPackage(): array { return ['url' => '', 'sha256' => '', 'size' => 0, 'filename' => '']; } /** * @return array{url:string,sha256:string,size:int,filename:string} */ private static function fillLocalPackageMeta(string $url, string $sha256, int $size, string $filename): array { $relative = $url; if ($relative !== '' && !preg_match('#^https?://#i', $relative)) { $path = public_path() . ltrim(str_replace('\\', '/', $relative), '/'); if (is_file($path)) { if ($sha256 === '' || !preg_match('/^[a-f0-9]{64}$/', $sha256)) { $sha256 = hash_file('sha256', $path) ?: $sha256; } if ($size <= 0) { $size = (int) filesize($path); } if ($filename === '') { $filename = basename($path); } } } return [ 'url' => $url, 'sha256' => strtolower($sha256), 'size' => $size, 'filename' => $filename, ]; } /** * @return array{0:int,1:int,2:int} */ private static function versionParts(string $version): array { $normalized = self::normalizeVersion($version); if ($normalized === '') { return [0, 0, 0]; } return array_map('intval', explode('.', $normalized)); } private static function asFlag(mixed $value): int { if (is_bool($value)) { return $value ? 1 : 0; } return in_array((string) $value, ['1', 'true', 'on', 'yes'], true) ? 1 : 0; } }