Untitled

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