Untitled

PHP Detected Guest 93 Views Size: 25.52 KB Posted on: Sep 8, 25 @ 1:59 PM
  1. <?php
  2. /*
  3.  * Paste $v3.2 2025/09/08 https://github.com/boxlabss/PASTE
  4.  * demo: https://paste.boxlabs.uk/
  5.  *
  6.  * https://phpaste.sourceforge.io/
  7.  *
  8.  * This program is free software; you can redistribute it and/or
  9.  * modify it under the terms of the GNU General Public License
  10.  * as published by the Free Software Foundation; either version 3
  11.  * of the License, or (at your option) any later version.
  12.  *
  13.  * This program is distributed in the hope that it will be useful,
  14.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16.  * GNU General Public License in LICENCE for more details.
  17.  */
  18. declare(strict_types=1);
  19.  
  20. require_once __DIR__ . '/includes/session.php';
  21. require_once __DIR__ . '/config.php';
  22. require_once __DIR__ . '/includes/functions.php';
  23. // Error handling
  24. paste_enable_themed_errors();
  25.  
  26. // Highlighter bootstrap + language lists
  27. require_once __DIR__ . '/includes/hlbootstrap.php';
  28. require_once __DIR__ . '/includes/list_languages.php';
  29.  
  30. ini_set('display_errors','0');
  31. ini_set('log_errors','1');
  32.  
  33. // Load paste by ID, decrypting as needed. Returns ['title','content','code'] or null
  34. function load_paste(PDO $pdo, int $id): ?array {
  35.     try {
  36.         $st = $pdo->prepare("SELECT id,title,content,code,encrypt FROM pastes WHERE id=? LIMIT 1");
  37.         $st->execute([$id]);
  38.         $r = $st->fetch();
  39.         if (!$r) return null;
  40.  
  41.         $title   = (string)$r['title'];
  42.         $content = (string)$r['content'];
  43.  
  44.         if ((string)$r['encrypt'] === "1" && defined('SECRET')) {
  45.             $title   = decrypt($title,   hex2bin(SECRET))   ?? $title;
  46.             $content = decrypt($content, hex2bin(SECRET))   ?? $content;
  47.         }
  48.  
  49.         // Stored content is HTML-escaped before encrypt; decode for diffing
  50.         $content = html_entity_decode($content, ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8');
  51.  
  52.         return [
  53.             'title'   => $title,
  54.             'content' => $content,
  55.             'code'    => (string)($r['code'] ?? 'text'),
  56.         ];
  57.     } catch (Throwable $e) {
  58.         error_log("diff.php load_paste($id): ".$e->getMessage());
  59.         return null;
  60.     }
  61. }
  62.  
  63. // Inline (word/char) diff > [leftHTML, rightHTML] with <span class="diff-inside-...">
  64. function inline_diff(string $a, string $b): array {
  65.     $split = static function(string $s): array {
  66.         preg_match_all('/\s+|[^\s]+/u', $s, $m);
  67.         return $m[0] ?: [$s];
  68.     };
  69.     $Aw = $split($a); $Bw = $split($b);
  70.     $useChar = (count($Aw) <= 4 || count($Bw) <= 4);
  71.     if ($useChar) {
  72.         $Aw = preg_split('//u', $a, -1, PREG_SPLIT_NO_EMPTY);
  73.         $Bw = preg_split('//u', $b, -1, PREG_SPLIT_NO_EMPTY);
  74.     }
  75.     $n=count($Aw); $m=count($Bw);
  76.     $L = array_fill(0,$n+1,array_fill(0,$m+1,0));
  77.     for($i=$n-1;$i>=0;$i--) for($j=$m-1;$j>=0;$j--) {
  78.         $L[$i][$j]=($Aw[$i]===$Bw[$j])?($L[$i+1][$j+1]+1):max($L[$i+1][$j],$L[$i][$j+1]);
  79.     }
  80.     $i=0;$j=0;$left='';$right='';
  81.     $esc = static fn($s)=>htmlspecialchars($s, ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8');
  82.     while($i<$n && $j<$m){
  83.         if($Aw[$i]===$Bw[$j]){ $left.=$esc($Aw[$i]); $right.=$esc($Bw[$j]); $i++; $j++; }
  84.         elseif($L[$i+1][$j] >= $L[$i][$j+1]){ $left.='<span class="diff-inside-del">'.$esc($Aw[$i]).'</span>'; $i++; }
  85.         else { $right.='<span class="diff-inside-add">'.$esc($Bw[$j]).'</span>'; $j++; }
  86.     }
  87.     while($i<$n){ $left.='<span class="diff-inside-del">'.$esc($Aw[$i]).'</span>'; $i++; }
  88.     while($j<$m){ $right.='<span class="diff-inside-add">'.$esc($Bw[$j]).'</span>'; $j++; }
  89.     return [$left,$right];
  90. }
  91.  
  92. /**
  93.  * Index-based diff at line level > opcodes referencing original arrays.
  94.  * Opcodes:
  95.  *   - ['op'=>'eq','ai'=>i,'bi'=>j]
  96.  *   - ['op'=>'del','ai'=>i]
  97.  *   - ['op'=>'add','bi'=>j]
  98.  *
  99.  *   1) xdiff accelerator (parse unified diff) if available
  100.  *   2) Myers O((N+M)D) fallback in pure PHP
  101.  */
  102. function diff_lines_idx(array $A, array $B, ?callable $normalizer=null): array {
  103.     $An = $normalizer ? array_map($normalizer, $A) : $A;
  104.     $Bn = $normalizer ? array_map($normalizer, $B) : $B;
  105.  
  106.     if (function_exists('xdiff_string_diff')) {
  107.         return _diff_idx_via_xdiff($An, $Bn);
  108.     }
  109.     return _diff_idx_via_myers($An, $Bn);
  110. }
  111.  
  112. /* ---------- Fast path: use xdiff to compute unified diff, parse to opcodes ---------- */
  113. function _diff_idx_via_xdiff(array $A, array $B): array {
  114.     $N = count($A); $M = count($B);
  115.  
  116.     // Join as text; add trailing newline to stabilize EOF handling
  117.     $left  = ($N ? implode("\n", $A) : '') . "\n";
  118.     $right = ($M ? implode("\n", $B) : '') . "\n";
  119.  
  120.     // Use context for stable headers + ' ' lines; non-minimal for speed
  121.     $ctx = 3;
  122.     $ud = xdiff_string_diff($left, $right, $ctx, false);
  123.     if ($ud === false) {
  124.         // Trivial fallback: align common prefix, then tail adds/dels
  125.         $ops = [];
  126.         $eq = min($N, $M);
  127.         for ($i=0; $i<$eq; $i++) $ops[] = ['op'=>'eq','ai'=>$i,'bi'=>$i];
  128.         for ($i=$eq; $i<$N; $i++) $ops[] = ['op'=>'del','ai'=>$i];
  129.         for ($j=$eq; $j<$M; $j++) $ops[] = ['op'=>'add','bi'=>$j];
  130.         return $ops;
  131.     }
  132.     if ($ud === '') {
  133.         $ops = [];
  134.         for ($i=0; $i<min($N,$M); $i++) $ops[] = ['op'=>'eq','ai'=>$i,'bi'=>$i];
  135.         for ($i=$M; $i<$N; $i++) $ops[] = ['op'=>'del','ai'=>$i];
  136.         for ($j=$N; $j<$M; $j++) $ops[] = ['op'=>'add','bi'=>$j];
  137.         return $ops;
  138.     }
  139.  
  140.     $ops = [];
  141.     $ai = 0; $bi = 0;
  142.  
  143.     $lines = preg_split("/\R/u", $ud);
  144.     foreach ($lines as $ln) {
  145.         if ($ln === '' && $ln !== '0') continue;
  146.  
  147.         // Hunk header: @@ -aStart,aLen +bStart,bLen @@
  148.         if (($ln[0] ?? '') === '@' &&
  149.             preg_match('/^@@\s+-([0-9]+)(?:,([0-9]+))?\s+\+([0-9]+)(?:,([0-9]+))?\s+@@/', $ln, $m)) {
  150.  
  151.             $startA = max(0, (int)$m[1] - 1);  // convert to 0-based
  152.             $startB = max(0, (int)$m[3] - 1);
  153.  
  154.             // Between hunks: move forward by equal lines only
  155.             $gap = min(max(0, $startA - $ai), max(0, $startB - $bi));
  156.             for ($k=0; $k<$gap; $k++) { $ops[] = ['op'=>'eq','ai'=>$ai,'bi'=>$bi]; $ai++; $bi++; }
  157.             continue;
  158.         }
  159.  
  160.         $tag = $ln[0] ?? '';
  161.         if ($tag === ' ') {                   // context (equal)
  162.             $ops[] = ['op'=>'eq','ai'=>$ai,'bi'=>$bi];  $ai++; $bi++;
  163.         } elseif ($tag === '-') {             // deletion
  164.             $ops[] = ['op'=>'del','ai'=>$ai];           $ai++;
  165.         } elseif ($tag === '+') {             // addition
  166.             $ops[] = ['op'=>'add','bi'=>$bi];                    $bi++;
  167.         } elseif ($tag === '\\') {
  168.             // "\ No newline at end of file" > ignore
  169.         } else {
  170.             // headers '---' / '+++' or noise > ignore
  171.         }
  172.     }
  173.  
  174.     // Trailing equals after the last hunk
  175.     $tailEq = min($N - $ai, $M - $bi);
  176.     for ($k=0; $k<$tailEq; $k++) { $ops[] = ['op'=>'eq','ai'=>$ai,'bi'=>$bi]; $ai++; $bi++; }
  177.     for (; $ai<$N; $ai++) $ops[] = ['op'=>'del','ai'=>$ai];
  178.     for (; $bi<$M; $bi++) $ops[] = ['op'=>'add','bi'=>$bi];
  179.  
  180.     return $ops;
  181. }
  182.  
  183. /* ---------- Fallback: Myers O((N+M)D) with path reconstruction ---------- */
  184. function _diff_idx_via_myers(array $A, array $B): array {
  185.     $N = count($A); $M = count($B);
  186.  
  187.     if ($N === 0 && $M === 0) return [];
  188.     if ($N === 0) { $ops=[]; for($j=0;$j<$M;$j++) $ops[]=['op'=>'add','bi'=>$j]; return $ops; }
  189.     if ($M === 0) { $ops=[]; for($i=0;$i<$N;$i++) $ops[]=['op'=>'del','ai'=>$i]; return $ops; }
  190.  
  191.     $max = $N + $M;
  192.     $off = $max;
  193.     $V = array_fill(0, 2 * $max + 1, 0);
  194.     $trace = [];
  195.  
  196.     $Dend = 0;
  197.     for ($d = 0; $d <= $max; $d++) {
  198.         $trace[$d] = $V;
  199.  
  200.         for ($k = -$d; $k <= $d; $k += 2) {
  201.             if ($k == -$d || ($k != $d && $V[$off + $k - 1] < $V[$off + $k + 1])) {
  202.                 $x = $V[$off + $k + 1];       // down (insert B)
  203.             } else {
  204.                 $x = $V[$off + $k - 1] + 1;   // right (delete A)
  205.             }
  206.             $y = $x - $k;
  207.  
  208.             while ($x < $N && $y < $M && $A[$x] === $B[$y]) { $x++; $y++; }
  209.  
  210.             $V[$off + $k] = $x;
  211.  
  212.             if ($x >= $N && $y >= $M) {
  213.                 $trace[$d] = $V;
  214.                 $Dend = $d;
  215.                 break 2;
  216.             }
  217.         }
  218.     }
  219.  
  220.     $ops = [];
  221.     $x = $N; $y = $M;
  222.     for ($d = $Dend; $d > 0; $d--) {
  223.         $Vprev = $trace[$d-1];
  224.         $k  = $x - $y;
  225.  
  226.         $down = ($k == -$d) || ($k != $d && $Vprev[$off + $k - 1] < $Vprev[$off + $k + 1]);
  227.         $kPrev   = $down ? $k + 1 : $k - 1;
  228.         $xStart  = $down ? $Vprev[$off + $kPrev] : $Vprev[$off + $kPrev] + 1;
  229.         $yStart  = $xStart - $kPrev;
  230.  
  231.         while ($x > $xStart && $y > $yStart) { $x--; $y--; $ops[] = ['op'=>'eq','ai'=>$x,'bi'=>$y]; }
  232.  
  233.         if ($down) {
  234.             $yStart--;
  235.             $ops[] = ['op'=>'add','bi'=>$yStart];
  236.         } else {
  237.             $xStart--;
  238.             $ops[] = ['op'=>'del','ai'=>$xStart];
  239.         }
  240.  
  241.         $x = $xStart; $y = $yStart;
  242.     }
  243.  
  244.     while ($x > 0 && $y > 0) { $x--; $y--; $ops[] = ['op'=>'eq','ai'=>$x,'bi'=>$y]; }
  245.     while ($x > 0) { $x--; $ops[] = ['op'=>'del','ai'=>$x]; }
  246.     while ($y > 0) { $y--; $ops[] = ['op'=>'add','bi'=>$y]; }
  247.  
  248.     return array_reverse($ops);
  249. }
  250.  
  251. // Build side-by-side & unified row arrays from ops (index-based).
  252. function build_tables_idx(array $ops, array $leftLines, array $rightLines): array {
  253.     $side=[]; $uni=[]; $li=1; $ri=1;
  254.     foreach ($ops as $op) {
  255.         if ($op['op']==='eq') {
  256.             $L=(string)($leftLines[$op['ai']] ?? '');
  257.             $R=(string)($rightLines[$op['bi']] ?? '');
  258.             $side[]=['lno'=>$li,'rno'=>$ri,'lclass'=>'ctx','rclass'=>'ctx','lhtml'=>$L,'rhtml'=>$R,'l_intra'=>false,'r_intra'=>false];
  259.             $uni[] =['lno'=>$li,'rno'=>$ri,'class'=>'ctx','html'=>$L,'intra'=>false];
  260.             $li++; $ri++;
  261.         } elseif ($op['op']==='del') {
  262.             $L=(string)($leftLines[$op['ai']] ?? '');
  263.             $side[]=['lno'=>$li,'rno'=>'','lclass'=>'del','rclass'=>'empty','lhtml'=>$L,'rhtml'=>'','l_intra'=>false,'r_intra'=>false];
  264.             $uni[] =['lno'=>$li,'rno'=>'','class'=>'del','html'=>$L,'intra'=>false];
  265.             $li++;
  266.         } else { // add
  267.             $R=(string)($rightLines[$op['bi']] ?? '');
  268.             $side[]=['lno'=>'','rno'=>$ri,'lclass'=>'empty','rclass'=>'add','lhtml'=>'','rhtml'=>$R,'l_intra'=>false,'r_intra'=>false];
  269.             $uni[] =['lno'=>'','rno'=>$ri,'class'=>'add','html'=>$R,'intra'=>false];
  270.             $ri++;
  271.         }
  272.     }
  273.     return [$side,$uni];
  274. }
  275.  
  276. // Apply inline word/char diff across adjacent del/add in side-by-side rows.
  277. function apply_inline_sxs(array &$sideRows): void {
  278.     for ($i=0; $i<count($sideRows)-1; $i++) {
  279.         $a=$sideRows[$i]; $b=$sideRows[$i+1];
  280.         if ($a['lclass']==='del' && $b['rclass']==='add') {
  281.             [$L,$R] = inline_diff((string)$a['lhtml'], (string)$b['rhtml']);
  282.             $sideRows[$i]['lhtml']=$L; $sideRows[$i]['l_intra']=true;
  283.             $sideRows[$i+1]['rhtml']=$R; $sideRows[$i+1]['r_intra']=true;
  284.             $i++;
  285.         }
  286.     }
  287. }
  288.  
  289. // Proper unified .diff (xdiff if present; else POSIX-ish fallback)
  290. function unified_diff_download(string $left, string $right, string $nameA='a', string $nameB='b', int $ctx=3): string {
  291.     if (function_exists('xdiff_string_diff')) {
  292.         // Use non-minimal for speed; we rewrite headers below
  293.         $ud = xdiff_string_diff($left, $right, $ctx, false);
  294.         if ($ud !== false) {
  295.             $ts = gmdate('Y-m-d H:i:s O');
  296.             $hdr = "--- {$nameA}\t{$ts}\n+++ {$nameB}\t{$ts}\n";
  297.             $ud = preg_replace('~^--- .*\R\+\+\+ .*\R~', $hdr, $ud, 1);
  298.             return $ud;
  299.         }
  300.     }
  301.  
  302.     // Manual fallback
  303.     $aOrig = preg_split("/\R/u", $left);
  304.     $bOrig = preg_split("/\R/u", $right);
  305.     if ($aOrig === false) $aOrig = [$left];
  306.     if ($bOrig === false) $bOrig = [$right];
  307.  
  308.     $ops = diff_lines_idx($aOrig, $bOrig, null);
  309.  
  310.     $hunks = [];
  311.     $flush = static function (&$hunks, &$buf) {
  312.         if (empty($buf['lines'])) return;
  313.         $oldLen = max(0, $buf['oldLen']);
  314.         $newLen = max(0, $buf['newLen']);
  315.         $h = "@@ -{$buf['oldStart']}" . ($oldLen===1?'':",$oldLen")
  316.            . " +{$buf['newStart']}" . ($newLen===1?'':",$newLen") . " @@\n";
  317.         $h .= implode('', $buf['lines']);
  318.         $hunks[] = $h;
  319.         $buf = ['oldStart'=>0,'newStart'=>0,'oldLen'=>0,'newLen'=>0,'lines'=>[],'open'=>false];
  320.     };
  321.  
  322.     $buf = ['oldStart'=>0,'newStart'=>0,'oldLen'=>0,'newLen'=>0,'lines'=>[],'open'=>false];
  323.     $ctxAhead = 0;
  324.     $ai=1; $bi=1;
  325.  
  326.     $grab_context = static function($aOrig, $bOrig, $ai, $bi, $ctx) use (&$buf) {
  327.         $startA = max(1, $ai - $ctx);
  328.         $startB = max(1, $bi - $ctx);
  329.         $buf['oldStart'] = $startA;
  330.         $buf['newStart'] = $startB;
  331.         for ($k=0; $k<($ai-$startA); $k++) {
  332.             $buf['lines'][] = ' ' . rtrim((string)$aOrig[$startA-1+$k], "\r") . "\n";
  333.         }
  334.         $buf['oldLen'] += ($ai-$startA);
  335.         $buf['newLen'] += ($bi-$startB);
  336.     };
  337.  
  338.     foreach ($ops as $op) {
  339.         if ($op['op'] === 'eq') {
  340.             if ($buf['open']) {
  341.                 if ($ctxAhead < 3) {
  342.                     $line = rtrim((string)$aOrig[$op['ai']], "\r");
  343.                     $buf['lines'][] = ' ' . $line . "\n";
  344.                     $buf['oldLen']++; $buf['newLen']++; $ctxAhead++;
  345.                 } else {
  346.                     $flush($hunks, $buf);
  347.                     $ctxAhead = 0;
  348.                 }
  349.             }
  350.             $ai++; $bi++;
  351.         } elseif ($op['op'] === 'del') {
  352.             if (!$buf['open']) { $buf['open']=true; $ctxAhead=0; $grab_context($aOrig, $bOrig, $ai, $bi, 3); }
  353.             $line = rtrim((string)$aOrig[$op['ai']], "\r");
  354.             $buf['lines'][] = '-' . $line . "\n";
  355.             $buf['oldLen']++; $ai++;
  356.         } else { // add
  357.             if (!$buf['open']) { $buf['open']=true; $ctxAhead=0; $grab_context($aOrig, $bOrig, $ai, $bi, 3); }
  358.             $line = rtrim((string)$bOrig[$op['bi']], "\r");
  359.             $buf['lines'][] = '+' . $line . "\n";
  360.             $buf['newLen']++; $bi++;
  361.         }
  362.     }
  363.     if ($buf['open']) $flush($hunks, $buf);
  364.  
  365.     $ts = gmdate('Y-m-d H:i:s O');
  366.     $out  = "--- {$nameA}\t{$ts}\n+++ {$nameB}\t{$ts}\n";
  367.     $out .= implode('', $hunks);
  368.     return $out;
  369. }
  370.  
  371. // Render a single line with highlight.php if available (else plain-escaped).
  372. function hl_render_line(string $text, string $lang='text'): string {
  373.     global $highlighter;
  374.     static $hl = null;
  375.  
  376.     $esc = static fn($s)=>htmlspecialchars($s, ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8');
  377.  
  378.     if (($highlighter ?? 'geshi') === 'highlight') {
  379.         if ($hl === null) $hl = make_highlighter();
  380.         if ($hl) {
  381.             try {
  382.                 if ($lang && !in_array(strtolower($lang), ['autodetect','text','plaintext'], true)) {
  383.                     $res = $hl->highlight($lang, $text);
  384.                     return '<span class="hljs">'.$res->value.'</span>';
  385.                 }
  386.                 $res = $hl->highlightAuto($text);
  387.                 return '<span class="hljs">'.$res->value.'</span>';
  388.             } catch (Throwable $e) { /* fall through */ }
  389.         }
  390.     }
  391.     return $esc($text);
  392. }
  393.  
  394. /* =========================================================
  395.  * Page inputs
  396.  * =======================================================*/
  397.  
  398. $left  = '';
  399. $right = '';
  400. $leftLabel  = 'Old';
  401. $rightLabel = 'New';
  402.  
  403. /* ---------- Minimal site/bootstrap so header/footer look right ---------- */
  404. $mod_rewrite = $mod_rewrite ?? '0';
  405. $baseurl     = $baseurl     ?? '/';
  406. $site_name   = $site_name   ?? '';
  407. $title       = 'Diff';
  408. $ges_style   = '';          // keep themes CSS-only
  409. $ads_1 = $ads_2 = $text_ads = ''; // optional ad slots
  410.  
  411. try {
  412.     // site_info
  413.     $stmt = $pdo->query("SELECT * FROM site_info WHERE id='1'");
  414.     if ($stmt) {
  415.         $si = $stmt->fetch() ?: [];
  416.         $title     = trim($si['title'] ?? $title);
  417.         $des       = trim($si['des'] ?? '');
  418.         $baseurl   = rtrim(trim($si['baseurl'] ?? $baseurl), '/') . '/';
  419.         $keyword   = trim($si['keyword'] ?? '');
  420.         $site_name = trim($si['site_name'] ?? $site_name);
  421.         $email     = trim($si['email'] ?? '');
  422.         $twit      = trim($si['twit'] ?? '');
  423.         $face      = trim($si['face'] ?? '');
  424.         $gplus     = trim($si['gplus'] ?? '');
  425.         $ga        = trim($si['ga'] ?? '');
  426.         $additional_scripts = trim($si['additional_scripts'] ?? '');
  427.         if (isset($si['mod_rewrite']) && $si['mod_rewrite'] !== '') {
  428.             $mod_rewrite = (string)$si['mod_rewrite'];
  429.         }
  430.     }
  431.  
  432.     // interface
  433.     $stmt = $pdo->query("SELECT * FROM interface WHERE id='1'");
  434.     if ($stmt) {
  435.         $iface = $stmt->fetch() ?: [];
  436.         $default_lang  = trim($iface['lang']  ?? 'en.php');
  437.         $default_theme = trim($iface['theme'] ?? 'default');
  438.         if (is_file(__DIR__ . "/langs/$default_lang")) {
  439.             require_once __DIR__ . "/langs/$default_lang";
  440.         }
  441.     } else {
  442.         $default_theme = $default_theme ?? 'default';
  443.     }
  444.  
  445.     // permissions
  446.     $stmt = $pdo->query("SELECT * FROM site_permissions WHERE id='1'");
  447.     if ($stmt) {
  448.         $perm = $stmt->fetch() ?: [];
  449.         $disableguest = trim($perm['disableguest'] ?? 'off');
  450.         $siteprivate  = trim($perm['siteprivate']  ?? 'off');
  451.     }
  452.  
  453.     // ads (optional)
  454.     $stmt = $pdo->query("SELECT * FROM ads WHERE id='1'");
  455.     if ($stmt) {
  456.         $ads  = $stmt->fetch() ?: [];
  457.         $text_ads = trim($ads['text_ads'] ?? '');
  458.         $ads_1    = trim($ads['ads_1'] ?? '');
  459.         $ads_2    = trim($ads['ads_2'] ?? '');
  460.     }
  461. } catch (Throwable $e) {
  462.     // keep sane defaults, but don't break the page
  463.     error_log('diff.php bootstrap: ' . $e->getMessage());
  464.     $default_theme = $default_theme ?? 'default';
  465. }
  466.  
  467. /* ---------- Paste IDs from query (supports ?a & ?b) ---------- */
  468. $lid = isset($_GET['a']) ? (int)$_GET['a'] : (isset($_GET['left_id']) ? (int)$_GET['left_id'] : 0);
  469. $rid = isset($_GET['b']) ? (int)$_GET['b'] : (isset($_GET['right_id']) ? (int)$_GET['right_id'] : 0);
  470.  
  471. if ($lid) { $p = load_paste($pdo, $lid); if ($p){ $left=$p['content'];  $leftLabel='Paste #'.$lid; } }
  472. if ($rid) { $p = load_paste($pdo, $rid); if ($p){ $right=$p['content']; $rightLabel='Paste #'.$rid; } }
  473.  
  474. /* ---------- POST inputs (compare / download keeps buffers) ---------- */
  475. if ($_SERVER['REQUEST_METHOD']==='POST') {
  476.     $left       = (string)($_POST['left_text']  ?? $left);
  477.     $right      = (string)($_POST['right_text'] ?? $right);
  478.     $leftLabel  = trim((string)($_POST['left_label']  ?? $leftLabel))  ?: $leftLabel;
  479.     $rightLabel = trim((string)($_POST['right_label'] ?? $rightLabel)) ?: $rightLabel;
  480. }
  481.  
  482. /* ---------- Language engine + maps ---------- */
  483. $engine = function_exists('paste_current_engine') ? paste_current_engine() : ($highlighter ?? 'geshi');
  484.  
  485. if ($engine === 'highlight') {
  486.     $langs         = highlight_supported_languages();
  487.     $language_map  = highlight_language_map($langs);
  488.     $alias_map     = highlight_alias_map($langs);
  489.     $popular_langs = paste_popular_formats_highlight();
  490. } else {
  491.     $language_map  = geshi_language_map();
  492.     $alias_map     = geshi_alias_map($language_map);
  493.     $popular_langs = paste_popular_formats_geshi();
  494. }
  495.  
  496. /* ---------- Picked languages ---------- */
  497. $lang_left  = strtolower((string)($_POST['left_lang']  ?? $_GET['left_lang']  ?? 'autodetect'));
  498. $lang_right = strtolower((string)($_POST['right_lang'] ?? $_GET['right_lang'] ?? 'autodetect'));
  499. $lang_left  = $alias_map[$lang_left]  ?? 'autodetect';
  500. $lang_right = $alias_map[$lang_right] ?? 'autodetect';
  501.  
  502. $lang_left_label  = $language_map[$lang_left]  ?? ucfirst($lang_left);
  503. $lang_right_label = $language_map[$lang_right] ?? ucfirst($lang_right);
  504.  
  505. /* ---------- highlight.php default style (no picker) ---------- */
  506. if (($highlighter ?? 'geshi') === 'highlight') {
  507.     $hl_style = $hl_style ?? 'hybrid.css'; // header.php reads this
  508. }
  509.  
  510. /* ---------- View options ---------- */
  511. $view_mode = ($_GET['view'] ?? 'side') === 'unified' ? 'unified' : 'side';
  512. $wrap      = isset($_GET['wrap'])      ? (int)$_GET['wrap']      : 0;
  513. $lineno    = isset($_GET['lineno'])    ? (int)$_GET['lineno']    : 1;
  514.  
  515. /* ---------- Ignore trailing whitespace (toggle via ?ignore_ws=1) ---------- */
  516. $ignore_ws = isset($_GET['ignore_ws']) ? (int)$_GET['ignore_ws'] : 0;
  517. $normalizer = $ignore_ws ? static fn($s) => rtrim($s, " \t") : null;
  518.  
  519. /* ---------- Persisted split percentage ---------- */
  520. $split_pct = 50.0;
  521. if (isset($_POST['split_pct'])) {
  522.     $split_pct = (float)$_POST['split_pct'];
  523. } elseif (isset($_COOKIE['diffSplitPct'])) {
  524.     $split_pct = (float)$_COOKIE['diffSplitPct'];
  525. }
  526. $split_pct = max(20.0, min(80.0, (float)$split_pct));
  527. setcookie('diffSplitPct', (string)$split_pct, [
  528.     'expires'  => time() + 60*60*24*30,
  529.     'path'     => '/',
  530.     'secure'   => !empty($_SERVER['HTTPS']),
  531.     'httponly' => false,
  532.     'samesite' => 'Lax',
  533. ]);
  534.  
  535. /* ---------- Download unified diff ---------- */
  536. if (isset($_GET['download']) && $_GET['download'] === '1') {
  537.     $nameA = $leftLabel  ?: 'Old';
  538.     $nameB = $rightLabel ?: 'New';
  539.     $ud = unified_diff_download($left, $right, $nameA, $nameB, 3);
  540.     // Surface engine in headers for debug
  541.     header('X-Diff-Engine: '.(function_exists('xdiff_string_diff') ? 'xdiff' : 'myers'));
  542.     header('X-Diff-Ignore-WS: '.($ignore_ws ? '1':'0'));
  543.     header('Content-Type: text/x-diff; charset=utf-8');
  544.     header('Content-Disposition: attachment; filename="paste.diff"');
  545.     echo $ud;
  546.     exit;
  547. }
  548.  
  549. /* ---------- Compute opcodes ---------- */
  550. $leftLines  = preg_split("/\R/u", $left);
  551. $rightLines = preg_split("/\R/u", $right);
  552. if ($leftLines === false)  $leftLines  = [$left];
  553. if ($rightLines === false) $rightLines = [$right];
  554.  
  555. $ops = diff_lines_idx($leftLines, $rightLines, $normalizer);
  556.  
  557. /* ---------- change counts & exposure ----------
  558.  * Treat an adjacent del-run followed by an add-run (or vice-versa) as "mods".
  559.  * Each paired line counts as 1 change in the total.
  560.  * Returns: [$adds, $dels, $mods, $total, $no_changes]
  561.  */
  562. function compute_change_counts(array $ops): array {
  563.     $adds = 0; $dels = 0; $mods = 0;
  564.     $n = count($ops);
  565.     for ($i = 0; $i < $n; ) {
  566.         $op = $ops[$i]['op'] ?? 'eq';
  567.         if ($op !== 'add' && $op !== 'del') { $i++; continue; }
  568.  
  569.         // First run (all adds or all dels)
  570.         $t1 = $op;
  571.         $c1 = 0;
  572.         $j  = $i;
  573.         while ($j < $n && ($ops[$j]['op'] ?? 'eq') === $t1) { $c1++; $j++; }
  574.  
  575.         // Optional immediately-adjacent opposite run
  576.         $t2 = ($t1 === 'add') ? 'del' : 'add';
  577.         $c2 = 0;
  578.         $k  = $j;
  579.         while ($k < $n && ($ops[$k]['op'] ?? 'eq') === $t2) { $c2++; $k++; }
  580.  
  581.         // Pair min(c1,c2) as modifications
  582.         $pair = min($c1, $c2);
  583.         $mods += $pair;
  584.  
  585.         if ($t1 === 'add') {
  586.             $adds += $c1 - $pair;
  587.             $dels += $c2 - $pair;
  588.         } else {
  589.             $dels += $c1 - $pair;
  590.             $adds += $c2 - $pair;
  591.         }
  592.  
  593.         // Advance past both runs
  594.         $i = ($c2 > 0) ? $k : $j;
  595.     }
  596.  
  597.     $total = $adds + $dels + $mods;   // modified lines count as 1
  598.     $no_changes = ($total === 0);
  599.     return [$adds, $dels, $mods, $total, $no_changes];
  600. }
  601.  
  602. /* ---------- change counts (mods collapse -/+ into 1) ---------- */
  603. [$adds, $dels, $mods, $changed_total, $no_changes] = compute_change_counts($ops);
  604.  
  605. header('X-Diff-No-Changes: ' . ($no_changes ? '1' : '0'));
  606. header('X-Diff-Change-Add: ' . $adds);
  607. header('X-Diff-Change-Del: ' . $dels);
  608. header('X-Diff-Change-Mod: ' . $mods);
  609. header('X-Diff-Change-Total: ' . $changed_total);
  610.  
  611. $GLOBALS['diff_no_changes']     = $no_changes;
  612. $GLOBALS['diff_changes_add']    = $adds;
  613. $GLOBALS['diff_changes_del']    = $dels;
  614. $GLOBALS['diff_changes_mod']    = $mods;         // available if you want a separate badge
  615. $GLOBALS['diff_changes_total']  = $changed_total; // theme uses this for ±T
  616.  
  617. /* ---------- Build tables server-side ---------- */
  618. [$sideRows, $uniRows] = build_tables_idx($ops, $leftLines, $rightLines);
  619.  
  620. /* ---------- Limit expensive inline diff pass for very large diffs ---------- */
  621. $perform_inline = true;
  622. $totalBytes = strlen($left) + strlen($right);
  623. if (count($sideRows) > 4000 || $totalBytes > 4*1024*1024) {
  624.     $perform_inline = false;
  625. }
  626. if ($perform_inline) {
  627.     apply_inline_sxs($sideRows);
  628. }
  629.  
  630. /* ---------- Expose engine + toggle info to theme and headers ---------- */
  631. $engine_is_xdiff = function_exists('xdiff_string_diff');
  632. $engine_label    = $engine_is_xdiff ? 'xdiff' : 'myers';
  633. header('X-Diff-Engine: '.$engine_label);
  634. header('X-Diff-Ignore-WS: '.($ignore_ws ? '1':'0'));
  635.  
  636. // Convenience strings the theme can show in the toolbar:
  637. $diff_engine_badge = '<span class="badge bg-secondary" title="Diff engine">'.$engine_label.'</span>';
  638. $ignore_ws_on      = (bool)$ignore_ws;
  639.  
  640. // Build toggle URL for ignore_ws (preserve other query params)
  641. $qs = $_GET;
  642. $qs['ignore_ws'] = $ignore_ws ? 0 : 1;
  643. $ignore_ws_toggle_url = strtok($_SERVER['REQUEST_URI'], '?') . '?' . http_build_query($qs);
  644.  
  645. /* ---------- Render theme ---------- */
  646. $themeDir = 'theme/' . htmlspecialchars($default_theme ?? 'default', ENT_QUOTES, 'UTF-8');
  647.  
  648. // expose split pct to the view if needed by JS
  649. $GLOBALS['split_pct'] = $split_pct;
  650.  
  651. // badges/toggle url for the theme
  652. $GLOBALS['diff_engine_badge']  = $diff_engine_badge;
  653. $GLOBALS['ignore_ws_on']       = $ignore_ws_on;
  654. $GLOBALS['ignore_ws_toggle']   = $ignore_ws_toggle_url;
  655.  
  656. require_once $themeDir . '/header.php';
  657. require_once $themeDir . '/diff.php';
  658. require_once $themeDir . '/footer.php';

Raw Paste

Comments 0
Login to post a comment.
  • No comments yet. Be the first.
Login to post a comment. Login or Register
We use cookies. To comply with GDPR in the EU and the UK we have to show you these.

We use cookies and similar technologies to keep this website functional (including spam protection via Google reCAPTCHA or Cloudflare Turnstile), and — with your consent — to measure usage and show ads. See Privacy.