Untitled

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