File transfer

This commit is contained in:
2026-06-11 20:33:44 -05:00
parent 2cb1d58264
commit b362357bcb
58 changed files with 5096 additions and 4 deletions

View File

@@ -0,0 +1,116 @@
<?php
/**
* Diff Service — generates unified diffs between two text contents.
*
* Uses SebastianBergmann\Diff if available, falls back to simple line diff.
*
* @author Ball Studios <https://git.balls.studio>
* @license MIT
*/
namespace Pterodactyl\Addons\AdvancedAdmin\Services\Revisions;
class DiffService
{
/**
* Generate a unified diff between old and new content.
* Returns array of lines with type: 'context' | 'added' | 'removed' | 'header'
*/
public function diff(string $oldContent, string $newContent, string $fileName = 'file'): array
{
if (class_exists(\SebastianBergmann\Diff\Differ::class)) {
return $this->diffViaSebastian($oldContent, $newContent, $fileName);
}
return $this->diffNative($oldContent, $newContent);
}
/**
* Compute a structured diff using SebastianBergmann\Diff.
*/
private function diffViaSebastian(string $old, string $new, string $fileName): array
{
$differ = new \SebastianBergmann\Diff\Differ(
new \SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder("--- a/{$fileName}\n+++ b/{$fileName}\n")
);
$rawDiff = $differ->diff($old, $new);
return $this->parseUnifiedDiff($rawDiff);
}
/**
* Simple pure-PHP line-level diff (fallback).
*/
private function diffNative(string $old, string $new): array
{
$oldLines = explode("\n", $old);
$newLines = explode("\n", $new);
$result = [];
// LCS-based diff
$lcs = $this->lcs($oldLines, $newLines);
$i = 0; $j = 0; $k = 0;
while ($i < count($oldLines) || $j < count($newLines)) {
if ($k < count($lcs) && $i < count($oldLines) && $oldLines[$i] === $lcs[$k] && $j < count($newLines) && $newLines[$j] === $lcs[$k]) {
$result[] = ['type' => 'context', 'content' => $oldLines[$i], 'line_old' => $i + 1, 'line_new' => $j + 1];
$i++; $j++; $k++;
} elseif ($j < count($newLines) && ($k >= count($lcs) || $newLines[$j] !== $lcs[$k])) {
$result[] = ['type' => 'added', 'content' => $newLines[$j], 'line_new' => $j + 1];
$j++;
} else {
$result[] = ['type' => 'removed', 'content' => $oldLines[$i], 'line_old' => $i + 1];
$i++;
}
}
return $result;
}
private function parseUnifiedDiff(string $diff): array
{
$lines = explode("\n", $diff);
$result = [];
$oldLine = 0; $newLine = 0;
foreach ($lines as $line) {
if (str_starts_with($line, '---') || str_starts_with($line, '+++')) {
$result[] = ['type' => 'header', 'content' => $line];
} elseif (str_starts_with($line, '@@')) {
// Parse @@ -old +new @@
preg_match('/@@ -(\d+).*\+(\d+)/', $line, $m);
$oldLine = (int) ($m[1] ?? 0);
$newLine = (int) ($m[2] ?? 0);
$result[] = ['type' => 'header', 'content' => $line];
} elseif (str_starts_with($line, '-')) {
$result[] = ['type' => 'removed', 'content' => substr($line, 1), 'line_old' => $oldLine++];
} elseif (str_starts_with($line, '+')) {
$result[] = ['type' => 'added', 'content' => substr($line, 1), 'line_new' => $newLine++];
} elseif (str_starts_with($line, ' ')) {
$result[] = ['type' => 'context', 'content' => substr($line, 1), 'line_old' => $oldLine++, 'line_new' => $newLine++];
}
}
return $result;
}
/**
* Longest Common Subsequence (for native diff).
*/
private function lcs(array $a, array $b): array
{
$m = count($a); $n = count($b);
$dp = array_fill(0, $m + 1, array_fill(0, $n + 1, 0));
for ($i = 1; $i <= $m; $i++) {
for ($j = 1; $j <= $n; $j++) {
$dp[$i][$j] = $a[$i-1] === $b[$j-1]
? $dp[$i-1][$j-1] + 1
: max($dp[$i-1][$j], $dp[$i][$j-1]);
}
}
$result = []; $i = $m; $j = $n;
while ($i > 0 && $j > 0) {
if ($a[$i-1] === $b[$j-1]) { array_unshift($result, $a[$i-1]); $i--; $j--; }
elseif ($dp[$i-1][$j] > $dp[$i][$j-1]) { $i--; }
else { $j--; }
}
return $result;
}
}