- 1<?php
- 2/*
- 3 * Paste $v3.2 2025/09/07 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 */
- 18declare(strict_types=1);
- 19
- 20require_once __DIR__ . '/includes/session.php';
- 21require_once __DIR__ . '/config.php';
- 22require_once __DIR__ . '/includes/functions.php';
- 23// Error handling
- 24paste_enable_themed_errors();
- 25
- 26// Highlighter bootstrap + language lists
- 27require_once __DIR__ . '/includes/hlbootstrap.php';
- 28require_once __DIR__ . '/includes/list_languages.php';
- 29
- 30ini_set('display_errors','0');
- 31ini_set('log_errors','1');
- 32date_default_timezone_set('UTC');
- 33
- 34// Load paste by ID, decrypting as needed. Returns ['title','content','code'] or null
- 35function load_paste(PDO $pdo, int $id): ?array {
- 36 try {
- 37 $st = $pdo->prepare("SELECT id,title,content,code,encrypt FROM pastes WHERE id=? LIMIT 1");
- 38 $st->execute([$id]);
- 39 $r = $st->fetch();
- 40 if (!$r) return null;
- 41
- 42 $title = (string)$r['title'];
- 43 $content = (string)$r['content'];
- 44
- 45 if ((string)$r['encrypt'] === "1" && defined('SECRET')) {
- 46 $title = decrypt($title, hex2bin(SECRET)) ?? $title;
- 47 $content = decrypt($content, hex2bin(SECRET)) ?? $content;
- 48 }
- 49
- 50 // Stored content is HTML-escaped before encrypt; decode for diffing
- 51 $content = html_entity_decode($content, ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8');
- 52
- 53 return [
- 54 'title' => $title,
- 55 'content' => $content,
- 56 'code' => (string)($r['code'] ?? 'text'),
- 57 ];
- 58 } catch (Throwable $e) {
- 59 error_log("diff.php load_paste($id): ".$e->getMessage());
- 60 return null;
- 61 }
- 62}
- 63
- 64// Inline (word/char) diff > [leftHTML, rightHTML] with <span class="diff-inside-...">
- 65function inline_diff(string $a, string $b): array {
- 66 $split = static function(string $s): array {
- 67 preg_match_all('/\s+|[^\s]+/u', $s, $m);
- 68 return $m[0] ?: [$s];
- 69 };
- 70 $Aw = $split($a); $Bw = $split($b);
- 71 $useChar = (count($Aw) <= 4 || count($Bw) <= 4);
- 72 if ($useChar) {
- 73 $Aw = preg_split('//u', $a, -1, PREG_SPLIT_NO_EMPTY);
- 74 $Bw = preg_split('//u', $b, -1, PREG_SPLIT_NO_EMPTY);
- 75 }
- 76 $n=count($Aw); $m=count($Bw);
- 77 $L = array_fill(0,$n+1,array_fill(0,$m+1,0));
- 78 for($i=$n-1;$i>=0;$i--) for($j=$m-1;$j>=0;$j--) {
- 79 $L[$i][$j]=($Aw[$i]===$Bw[$j])?($L[$i+1][$j+1]+1):max($L[$i+1][$j],$L[$i][$j+1]);
- 80 }
- 81 $i=0;$j=0;$left='';$right='';
- 82 $esc = static fn($s)=>htmlspecialchars($s, ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8');
- 83 while($i<$n && $j<$m){
- 84 if($Aw[$i]===$Bw[$j]){ $left.=$esc($Aw[$i]); $right.=$esc($Bw[$j]); $i++; $j++; }
- 85 elseif($L[$i+1][$j] >= $L[$i][$j+1]){ $left.='<span class="diff-inside-del">'.$esc($Aw[$i]).'</span>'; $i++; }
- 86 else { $right.='<span class="diff-inside-add">'.$esc($Bw[$j]).'</span>'; $j++; }
- 87 }
- 88 while($i<$n){ $left.='<span class="diff-inside-del">'.$esc($Aw[$i]).'</span>'; $i++; }
- 89 while($j<$m){ $right.='<span class="diff-inside-add">'.$esc($Bw[$j]).'</span>'; $j++; }
- 90 return [$left,$right];
- 91}
- 92
- 93/**
- 94 * Index-based diff at line level > opcodes referencing original arrays.
- 95 * Opcodes:
- 96 * - ['op'=>'eq','ai'=>i,'bi'=>j]
- 97 * - ['op'=>'del','ai'=>i]
- 98 * - ['op'=>'add','bi'=>j]
- 99 *
- 100 * 1) xdiff accelerator (parse unified diff) if available
- 101 * 2) Myers O((N+M)D) fallback in pure PHP
- 102 */
- 103function diff_lines_idx(array $A, array $B, ?callable $normalizer=null): array {
- 104 $An = $normalizer ? array_map($normalizer, $A) : $A;
- 105 $Bn = $normalizer ? array_map($normalizer, $B) : $B;
- 106
- 107 if (function_exists('xdiff_string_diff')) {
- 108 return _diff_idx_via_xdiff($An, $Bn);
- 109 }
- 110 return _diff_idx_via_myers($An, $Bn);
- 111}
- 112
- 113/* ---------- Fast path: use xdiff to compute unified diff, parse to opcodes ---------- */
- 114function _diff_idx_via_xdiff(array $A, array $B): array {
- 115 $N = count($A); $M = count($B);
- 116
- 117 // Join as text; ensure trailing newline to keep line counts exact
- 118 $left = ($N ? implode("\n", $A) : '') . "\n";
- 119 $right = ($M ? implode("\n", $B) : '') . "\n";
- 120
- 121 // 0-context unified diff, non-minimal for speed
- 122 $ud = xdiff_string_diff($left, $right, 0, false);
- 123 if ($ud === false) {
- 124 // Fallback to trivial equality handling
- 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<$N && $i<$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 if (preg_match('/^@@\s+-([0-9]+)(?:,([0-9]+))?\s+\+([0-9]+)(?:,([0-9]+))?\s+@@/', $ln, $m)) {
- 148 $startA = max(0, (int)$m[1] - 1); // 0-based
- 149 $startB = max(0, (int)$m[3] - 1);
- 150 // Emit equal block before this hunk (between current cursors and hunk start)
- 151 $eq = min(max(0, $startA - $ai), max(0, $startB - $bi));
- 152 for ($k=0; $k<$eq; $k++) { $ops[] = ['op'=>'eq','ai'=>$ai,'bi'=>$bi]; $ai++; $bi++; }
- 153 continue;
- 154 }
- 155
- 156 $tag = $ln[0] ?? '';
- 157 if ($tag === ' ') { $ops[] = ['op'=>'eq', 'ai'=>$ai, 'bi'=>$bi]; $ai++; $bi++; }
- 158 elseif ($tag === '-') { $ops[] = ['op'=>'del', 'ai'=>$ai]; $ai++; }
- 159 elseif ($tag === '+') { $ops[] = ['op'=>'add', 'bi'=>$bi]; $bi++; }
- 160 else {
- 161 // headers '---', '+++', empty, etc > ignore
- 162 }
- 163 }
- 164
- 165 // Trailing equals after last hunk
- 166 $tailEq = min($N - $ai, $M - $bi);
- 167 for ($k=0; $k<$tailEq; $k++) { $ops[] = ['op'=>'eq','ai'=>$ai,'bi'=>$bi]; $ai++; $bi++; }
- 168 for (; $ai<$N; $ai++) $ops[] = ['op'=>'del','ai'=>$ai];
- 169 for (; $bi<$M; $bi++) $ops[] = ['op'=>'add','bi'=>$bi];
- 170
- 171 return $ops;
- 172}
- 173
- 174/* ---------- Fallback: Myers O((N+M)D) with path reconstruction ---------- */
- 175function _diff_idx_via_myers(array $A, array $B): array {
- 176 $N = count($A); $M = count($B);
- 177
- 178 if ($N === 0 && $M === 0) return [];
- 179 if ($N === 0) { $ops=[]; for($j=0;$j<$M;$j++) $ops[]=['op'=>'add','bi'=>$j]; return $ops; }
- 180 if ($M === 0) { $ops=[]; for($i=0;$i<$N;$i++) $ops[]=['op'=>'del','ai'=>$i]; return $ops; }
- 181
- 182 $max = $N + $M;
- 183 $off = $max;
- 184 $V = array_fill(0, 2 * $max + 1, 0);
- 185 $trace = [];
- 186
- 187 $Dend = 0;
- 188 for ($d = 0; $d <= $max; $d++) {
- 189 $trace[$d] = $V;
- 190
- 191 for ($k = -$d; $k <= $d; $k += 2) {
- 192 if ($k == -$d || ($k != $d && $V[$off + $k - 1] < $V[$off + $k + 1])) {
- 193 $x = $V[$off + $k + 1]; // down (insert B)
- 194 } else {
- 195 $x = $V[$off + $k - 1] + 1; // right (delete A)
- 196 }
- 197 $y = $x - $k;
- 198
- 199 while ($x < $N && $y < $M && $A[$x] === $B[$y]) { $x++; $y++; }
- 200
- 201 $V[$off + $k] = $x;
- 202
- 203 if ($x >= $N && $y >= $M) {
- 204 $trace[$d] = $V;
- 205 $Dend = $d;
- 206 break 2;
- 207 }
- 208 }
- 209 }
- 210
- 211 $ops = [];
- 212 $x = $N; $y = $M;
- 213 for ($d = $Dend; $d > 0; $d--) {
- 214 $Vprev = $trace[$d-1];
- 215 $k = $x - $y;
- 216
- 217 $down = ($k == -$d) || ($k != $d && $Vprev[$off + $k - 1] < $Vprev[$off + $k + 1]);
- 218 $kPrev = $down ? $k + 1 : $k - 1;
- 219 $xStart = $down ? $Vprev[$off + $kPrev] : $Vprev[$off + $kPrev] + 1;
- 220 $yStart = $xStart - $kPrev;
- 221
- 222 while ($x > $xStart && $y > $yStart) { $x--; $y--; $ops[] = ['op'=>'eq','ai'=>$x,'bi'=>$y]; }
- 223
- 224 if ($down) {
- 225 $yStart--;
- 226 $ops[] = ['op'=>'add','bi'=>$yStart];
- 227 } else {
- 228 $xStart--;
- 229 $ops[] = ['op'=>'del','ai'=>$xStart];
- 230 }
- 231
- 232 $x = $xStart; $y = $yStart;
- 233 }
- 234
- 235 while ($x > 0 && $y > 0) { $x--; $y--; $ops[] = ['op'=>'eq','ai'=>$x,'bi'=>$y]; }
- 236 while ($x > 0) { $x--; $ops[] = ['op'=>'del','ai'=>$x]; }
- 237 while ($y > 0) { $y--; $ops[] = ['op'=>'add','bi'=>$y]; }
- 238
- 239 return array_reverse($ops);
- 240}
- 241
- 242// Build side-by-side & unified row arrays from ops (index-based).
- 243function build_tables_idx(array $ops, array $leftLines, array $rightLines): array {
- 244 $side=[]; $uni=[]; $li=1; $ri=1;
- 245 foreach ($ops as $op) {
- 246 if ($op['op']==='eq') {
- 247 $L=(string)($leftLines[$op['ai']] ?? '');
- 248 $R=(string)($rightLines[$op['bi']] ?? '');
- 249 $side[]=['lno'=>$li,'rno'=>$ri,'lclass'=>'ctx','rclass'=>'ctx','lhtml'=>$L,'rhtml'=>$R,'l_intra'=>false,'r_intra'=>false];
- 250 $uni[] =['lno'=>$li,'rno'=>$ri,'class'=>'ctx','html'=>$L,'intra'=>false];
- 251 $li++; $ri++;
- 252 } elseif ($op['op']==='del') {
- 253 $L=(string)($leftLines[$op['ai']] ?? '');
- 254 $side[]=['lno'=>$li,'rno'=>'','lclass'=>'del','rclass'=>'empty','lhtml'=>$L,'rhtml'=>'','l_intra'=>false,'r_intra'=>false];
- 255 $uni[] =['lno'=>$li,'rno'=>'','class'=>'del','html'=>$L,'intra'=>false];
- 256 $li++;
- 257 } else { // add
- 258 $R=(string)($rightLines[$op['bi']] ?? '');
- 259 $side[]=['lno'=>'','rno'=>$ri,'lclass'=>'empty','rclass'=>'add','lhtml'=>'','rhtml'=>$R,'l_intra'=>false,'r_intra'=>false];
- 260 $uni[] =['lno'=>'','rno'=>$ri,'class'=>'add','html'=>$R,'intra'=>false];
- 261 $ri++;
- 262 }
- 263 }
- 264 return [$side,$uni];
- 265}
- 266
- 267// Apply inline word/char diff across adjacent del/add in side-by-side rows.
- 268function apply_inline_sxs(array &$sideRows): void {
- 269 for ($i=0; $i<count($sideRows)-1; $i++) {
- 270 $a=$sideRows[$i]; $b=$sideRows[$i+1];
- 271 if ($a['lclass']==='del' && $b['rclass']==='add') {
- 272 [$L,$R] = inline_diff((string)$a['lhtml'], (string)$b['rhtml']);
- 273 $sideRows[$i]['lhtml']=$L; $sideRows[$i]['l_intra']=true;
- 274 $sideRows[$i+1]['rhtml']=$R; $sideRows[$i+1]['r_intra']=true;
- 275 $i++;
- 276 }
- 277 }
- 278}
- 279
- 280// Proper unified .diff (xdiff if present; else POSIX-ish fallback)
- 281function unified_diff_download(string $left, string $right, string $nameA='a', string $nameB='b', int $ctx=3): string {
- 282 if (function_exists('xdiff_string_diff')) {
- 283 // Use non-minimal for speed; we rewrite headers below
- 284 $ud = xdiff_string_diff($left, $right, $ctx, false);
- 285 if ($ud !== false) {
- 286 $ts = gmdate('Y-m-d H:i:s O');
- 287 $hdr = "--- {$nameA}\t{$ts}\n+++ {$nameB}\t{$ts}\n";
- 288 $ud = preg_replace('~^--- .*\R\+\+\+ .*\R~', $hdr, $ud, 1);
- 289 return $ud;
- 290 }
- 291 }
- 292
- 293 // Manual fallback
- 294 $aOrig = preg_split("/\R/u", $left);
- 295 $bOrig = preg_split("/\R/u", $right);
- 296 if ($aOrig === false) $aOrig = [$left];
- 297 if ($bOrig === false) $bOrig = [$right];
- 298
- 299 $ops = diff_lines_idx($aOrig, $bOrig, null);
- 300
- 301 $hunks = [];
- 302 $flush = static function (&$hunks, &$buf) {
- 303 if (empty($buf['lines'])) return;
- 304 $oldLen = max(0, $buf['oldLen']);
- 305 $newLen = max(0, $buf['newLen']);
- 306 $h = "@@ -{$buf['oldStart']}" . ($oldLen===1?'':",$oldLen")
- 307 . " +{$buf['newStart']}" . ($newLen===1?'':",$newLen") . " @@\n";
- 308 $h .= implode('', $buf['lines']);
- 309 $hunks[] = $h;
- 310 $buf = ['oldStart'=>0,'newStart'=>0,'oldLen'=>0,'newLen'=>0,'lines'=>[],'open'=>false];
- 311 };
- 312
- 313 $buf = ['oldStart'=>0,'newStart'=>0,'oldLen'=>0,'newLen'=>0,'lines'=>[],'open'=>false];
- 314 $ctxAhead = 0;
- 315 $ai=1; $bi=1;
- 316
- 317 $grab_context = static function($aOrig, $bOrig, $ai, $bi, $ctx) use (&$buf) {
- 318 $startA = max(1, $ai - $ctx);
- 319 $startB = max(1, $bi - $ctx);
- 320 $buf['oldStart'] = $startA;
- 321 $buf['newStart'] = $startB;
- 322 for ($k=0; $k<($ai-$startA); $k++) {
- 323 $buf['lines'][] = ' ' . rtrim((string)$aOrig[$startA-1+$k], "\r") . "\n";
- 324 }
- 325 $buf['oldLen'] += ($ai-$startA);
- 326 $buf['newLen'] += ($bi-$startB);
- 327 };
- 328
- 329 foreach ($ops as $op) {
- 330 if ($op['op'] === 'eq') {
- 331 if ($buf['open']) {
- 332 if ($ctxAhead < 3) {
- 333 $line = rtrim((string)$aOrig[$op['ai']], "\r");
- 334 $buf['lines'][] = ' ' . $line . "\n";
- 335 $buf['oldLen']++; $buf['newLen']++; $ctxAhead++;
- 336 } else {
- 337 $flush($hunks, $buf);
- 338 $ctxAhead = 0;
- 339 }
- 340 }
- 341 $ai++; $bi++;
- 342 } elseif ($op['op'] === 'del') {
- 343 if (!$buf['open']) { $buf['open']=true; $ctxAhead=0; $grab_context($aOrig, $bOrig, $ai, $bi, 3); }
- 344 $line = rtrim((string)$aOrig[$op['ai']], "\r");
- 345 $buf['lines'][] = '-' . $line . "\n";
- 346 $buf['oldLen']++; $ai++;
- 347 } else { // add
- 348 if (!$buf['open']) { $buf['open']=true; $ctxAhead=0; $grab_context($aOrig, $bOrig, $ai, $bi, 3); }
- 349 $line = rtrim((string)$bOrig[$op['bi']], "\r");
- 350 $buf['lines'][] = '+' . $line . "\n";
- 351 $buf['newLen']++; $bi++;
- 352 }
- 353 }
- 354 if ($buf['open']) $flush($hunks, $buf);
- 355
- 356 $ts = gmdate('Y-m-d H:i:s O');
- 357 $out = "--- {$nameA}\t{$ts}\n+++ {$nameB}\t{$ts}\n";
- 358 $out .= implode('', $hunks);
- 359 return $out;
- 360}
- 361
- 362// Render a single line with highlight.php if available (else plain-escaped).
- 363function hl_render_line(string $text, string $lang='text'): string {
- 364 global $highlighter;
- 365 static $hl = null;
- 366
- 367 $esc = static fn($s)=>htmlspecialchars($s, ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8');
- 368
- 369 if (($highlighter ?? 'geshi') === 'highlight') {
- 370 if ($hl === null) $hl = make_highlighter();
- 371 if ($hl) {
- 372 try {
- 373 if ($lang && !in_array(strtolower($lang), ['autodetect','text','plaintext'], true)) {
- 374 $res = $hl->highlight($lang, $text);
- 375 return '<span class="hljs">'.$res->value.'</span>';
- 376 }
- 377 $res = $hl->highlightAuto($text);
- 378 return '<span class="hljs">'.$res->value.'</span>';
- 379 } catch (Throwable $e) { /* fall through */ }
- 380 }
- 381 }
- 382 return $esc($text);
- 383}
- 384
- 385/* =========================================================
- 386 * Page inputs
- 387 * =======================================================*/
- 388
- 389$left = '';
- 390$right = '';
- 391$leftLabel = 'Old';
- 392$rightLabel = 'New';
- 393
- 394/* ---------- Minimal site/bootstrap so header/footer look right ---------- */
- 395$mod_rewrite = $mod_rewrite ?? '0';
- 396$baseurl = $baseurl ?? '/';
- 397$site_name = $site_name ?? '';
- 398$title = 'Diff';
- 399$ges_style = ''; // keep themes CSS-only
- 400$ads_1 = $ads_2 = $text_ads = ''; // optional ad slots
- 401
- 402try {
- 403 // site_info
- 404 $stmt = $pdo->query("SELECT * FROM site_info WHERE id='1'");
- 405 if ($stmt) {
- 406 $si = $stmt->fetch() ?: [];
- 407 $title = trim($si['title'] ?? $title);
- 408 $des = trim($si['des'] ?? '');
- 409 $baseurl = rtrim(trim($si['baseurl'] ?? $baseurl), '/') . '/';
- 410 $keyword = trim($si['keyword'] ?? '');
- 411 $site_name = trim($si['site_name'] ?? $site_name);
- 412 $email = trim($si['email'] ?? '');
- 413 $twit = trim($si['twit'] ?? '');
- 414 $face = trim($si['face'] ?? '');
- 415 $gplus = trim($si['gplus'] ?? '');
- 416 $ga = trim($si['ga'] ?? '');
- 417 $additional_scripts = trim($si['additional_scripts'] ?? '');
- 418 if (isset($si['mod_rewrite']) && $si['mod_rewrite'] !== '') {
- 419 $mod_rewrite = (string)$si['mod_rewrite'];
- 420 }
- 421 }
- 422
- 423 // interface
- 424 $stmt = $pdo->query("SELECT * FROM interface WHERE id='1'");
- 425 if ($stmt) {
- 426 $iface = $stmt->fetch() ?: [];
- 427 $default_lang = trim($iface['lang'] ?? 'en.php');
- 428 $default_theme = trim($iface['theme'] ?? 'default');
- 429 if (is_file(__DIR__ . "/langs/$default_lang")) {
- 430 require_once __DIR__ . "/langs/$default_lang";
- 431 }
- 432 } else {
- 433 $default_theme = $default_theme ?? 'default';
- 434 }
- 435
- 436 // permissions
- 437 $stmt = $pdo->query("SELECT * FROM site_permissions WHERE id='1'");
- 438 if ($stmt) {
- 439 $perm = $stmt->fetch() ?: [];
- 440 $disableguest = trim($perm['disableguest'] ?? 'off');
- 441 $siteprivate = trim($perm['siteprivate'] ?? 'off');
- 442 }
- 443
- 444 // ads (optional)
- 445 $stmt = $pdo->query("SELECT * FROM ads WHERE id='1'");
- 446 if ($stmt) {
- 447 $ads = $stmt->fetch() ?: [];
- 448 $text_ads = trim($ads['text_ads'] ?? '');
- 449 $ads_1 = trim($ads['ads_1'] ?? '');
- 450 $ads_2 = trim($ads['ads_2'] ?? '');
- 451 }
- 452} catch (Throwable $e) {
- 453 // keep sane defaults, but don't break the page
- 454 error_log('diff.php bootstrap: ' . $e->getMessage());
- 455 $default_theme = $default_theme ?? 'default';
- 456}
- 457
- 458/* ---------- Paste IDs from query (supports ?a & ?b) ---------- */
- 459$lid = isset($_GET['a']) ? (int)$_GET['a'] : (isset($_GET['left_id']) ? (int)$_GET['left_id'] : 0);
- 460$rid = isset($_GET['b']) ? (int)$_GET['b'] : (isset($_GET['right_id']) ? (int)$_GET['right_id'] : 0);
- 461
- 462if ($lid) { $p = load_paste($pdo, $lid); if ($p){ $left=$p['content']; $leftLabel='Paste #'.$lid; } }
- 463if ($rid) { $p = load_paste($pdo, $rid); if ($p){ $right=$p['content']; $rightLabel='Paste #'.$rid; } }
- 464
- 465/* ---------- POST inputs (compare / download keeps buffers) ---------- */
- 466if ($_SERVER['REQUEST_METHOD']==='POST') {
- 467 $left = (string)($_POST['left_text'] ?? $left);
- 468 $right = (string)($_POST['right_text'] ?? $right);
- 469 $leftLabel = trim((string)($_POST['left_label'] ?? $leftLabel)) ?: $leftLabel;
- 470 $rightLabel = trim((string)($_POST['right_label'] ?? $rightLabel)) ?: $rightLabel;
- 471}
- 472
- 473/* ---------- Language engine + maps ---------- */
- 474$engine = function_exists('paste_current_engine') ? paste_current_engine() : ($highlighter ?? 'geshi');
- 475
- 476if ($engine === 'highlight') {
- 477 $langs = highlight_supported_languages();
- 478 $language_map = highlight_language_map($langs);
- 479 $alias_map = highlight_alias_map($langs);
- 480 $popular_langs = paste_popular_formats_highlight();
- 481} else {
- 482 $language_map = geshi_language_map();
- 483 $alias_map = geshi_alias_map($language_map);
- 484 $popular_langs = paste_popular_formats_geshi();
- 485}
- 486
- 487/* ---------- Picked languages ---------- */
- 488$lang_left = strtolower((string)($_POST['left_lang'] ?? $_GET['left_lang'] ?? 'autodetect'));
- 489$lang_right = strtolower((string)($_POST['right_lang'] ?? $_GET['right_lang'] ?? 'autodetect'));
- 490$lang_left = $alias_map[$lang_left] ?? 'autodetect';
- 491$lang_right = $alias_map[$lang_right] ?? 'autodetect';
- 492
- 493$lang_left_label = $language_map[$lang_left] ?? ucfirst($lang_left);
- 494$lang_right_label = $language_map[$lang_right] ?? ucfirst($lang_right);
- 495
- 496/* ---------- highlight.php default style (no picker) ---------- */
- 497if (($highlighter ?? 'geshi') === 'highlight') {
- 498 $hl_style = $hl_style ?? 'hybrid.css'; // header.php reads this
- 499}
- 500
- 501/* ---------- View options ---------- */
- 502$view_mode = ($_GET['view'] ?? 'side') === 'unified' ? 'unified' : 'side';
- 503$wrap = isset($_GET['wrap']) ? (int)$_GET['wrap'] : 0;
- 504$lineno = isset($_GET['lineno']) ? (int)$_GET['lineno'] : 1;
- 505
- 506/* ---------- Ignore trailing whitespace (toggle via ?ignore_ws=1) ---------- */
- 507$ignore_ws = isset($_GET['ignore_ws']) ? (int)$_GET['ignore_ws'] : 0;
- 508$normalizer = $ignore_ws ? static fn($s) => rtrim($s, " \t") : null;
- 509
- 510/* ---------- Persisted split percentage ---------- */
- 511$split_pct = 50.0;
- 512if (isset($_POST['split_pct'])) {
- 513 $split_pct = (float)$_POST['split_pct'];
- 514} elseif (isset($_COOKIE['diffSplitPct'])) {
- 515 $split_pct = (float)$_COOKIE['diffSplitPct'];
- 516}
- 517$split_pct = max(20.0, min(80.0, (float)$split_pct));
- 518setcookie('diffSplitPct', (string)$split_pct, [
- 519 'expires' => time() + 60*60*24*30,
- 520 'path' => '/',
- 521 'secure' => !empty($_SERVER['HTTPS']),
- 522 'httponly' => false,
- 523 'samesite' => 'Lax',
- 524]);
- 525
- 526/* ---------- Download unified diff ---------- */
- 527if (isset($_GET['download']) && $_GET['download'] === '1') {
- 528 $nameA = $leftLabel ?: 'Old';
- 529 $nameB = $rightLabel ?: 'New';
- 530 $ud = unified_diff_download($left, $right, $nameA, $nameB, 3);
- 531 // Surface engine in headers for debug
- 532 header('X-Diff-Engine: '.(function_exists('xdiff_string_diff') ? 'xdiff' : 'myers'));
- 533 header('X-Diff-Ignore-WS: '.($ignore_ws ? '1':'0'));
- 534 header('Content-Type: text/x-diff; charset=utf-8');
- 535 header('Content-Disposition: attachment; filename="paste.diff"');
- 536 echo $ud;
- 537 exit;
- 538}
- 539
- 540/* ---------- Compute opcodes ---------- */
- 541$leftLines = preg_split("/\R/u", $left);
- 542$rightLines = preg_split("/\R/u", $right);
- 543if ($leftLines === false) $leftLines = [$left];
- 544if ($rightLines === false) $rightLines = [$right];
- 545
- 546$ops = diff_lines_idx($leftLines, $rightLines, $normalizer);
- 547
- 548/* ---------- Build tables server-side ---------- */
- 549[$sideRows, $uniRows] = build_tables_idx($ops, $leftLines, $rightLines);
- 550
- 551/* ---------- Limit expensive inline diff pass for very large diffs ---------- */
- 552$perform_inline = true;
- 553$totalBytes = strlen($left) + strlen($right);
- 554if (count($sideRows) > 4000 || $totalBytes > 4*1024*1024) {
- 555 $perform_inline = false;
- 556}
- 557if ($perform_inline) {
- 558 apply_inline_sxs($sideRows);
- 559}
- 560
- 561/* ---------- Expose engine + toggle info to theme and headers ---------- */
- 562$engine_is_xdiff = function_exists('xdiff_string_diff');
- 563$engine_label = $engine_is_xdiff ? 'xdiff' : 'myers';
- 564header('X-Diff-Engine: '.$engine_label);
- 565header('X-Diff-Ignore-WS: '.($ignore_ws ? '1':'0'));
- 566
- 567// Convenience strings the theme can show in the toolbar:
- 568$diff_engine_badge = '<span class="badge bg-secondary" title="Diff engine">'.$engine_label.'</span>';
- 569$ignore_ws_on = (bool)$ignore_ws;
- 570
- 571// Build toggle URL for ignore_ws (preserve other query params)
- 572$qs = $_GET;
- 573$qs['ignore_ws'] = $ignore_ws ? 0 : 1;
- 574$ignore_ws_toggle_url = strtok($_SERVER['REQUEST_URI'], '?') . '?' . http_build_query($qs);
- 575
- 576/* ---------- Render theme ---------- */
- 577$themeDir = 'theme/' . htmlspecialchars($default_theme ?? 'default', ENT_QUOTES, 'UTF-8');
- 578
- 579// expose split pct to the view if needed by JS
- 580$GLOBALS['split_pct'] = $split_pct;
- 581
- 582// Also expose new goodies for the toolbar (the theme may choose to use them)
- 583$GLOBALS['diff_engine_badge'] = $diff_engine_badge;
- 584$GLOBALS['ignore_ws_on'] = $ignore_ws_on;
- 585$GLOBALS['ignore_ws_toggle'] = $ignore_ws_toggle_url;
- 586
- 587require_once $themeDir . '/header.php';
- 588require_once $themeDir . '/diff.php';
- 589require_once $themeDir . '/footer.php';
Raw Paste