$hl_style = $t . '.css';
}
}
}
// Map some GeSHi-style names to highlight.js ids
function map_to_hl_lang(string $code): string {
static $map = [
'text' => 'plaintext',
'html5' => 'xml',
'html4strict' => 'xml',
'php-brief' => 'php',
'pycon' => 'python',
'postgresql' => 'pgsql',
'dos' => 'dos',
'vb' => 'vbnet',
];
$code = strtolower($code);
return $map[$code] ?? $code;
}
// Wrap hljs tokens in a line-numbered
so togglev() works
function hl_wrap_with_lines(string $value, string $hlLang, array $highlight_lines): string {
$lines = explode("\n", $value);
$digits = max(2, strlen((string) count($lines))); // how many digits do we need?
$hlset = $highlight_lines ? array_flip($highlight_lines) : [];
$out = [];
$out[] = '';
$out[] = '';
foreach ($lines as $i => $lineHtml) {
$ln = $i + 1;
$cls = isset($hlset[$ln]) ? ' class="hljs-ln-line hljs-hl"' : ' class="hljs-ln-line"';
$out[] = '- ' . $ln . '' . $lineHtml . '
';
}
$out[] = '
';
return implode('', $out);
}
// Add a class to specific - lines in GeSHi output (no inline styles)
function geshi_add_line_highlight_class(string $html, array $highlight_lines, string $class = 'hljs-hl'): string {
if (!$highlight_lines) return $html;
$targets = array_flip($highlight_lines);
$i = 0;
return preg_replace_callback('/
- ]*)>/', static function($m) use (&$i, $targets, $class) {
$i++;
$attrs = $m[1];
if (!isset($targets[$i])) return '
- ';
if (preg_match('/\bclass="([^"]*)"/i', $attrs, $cm)) {
$new = trim($cm[1] . ' ' . $class);
$attrs = preg_replace('/\bclass="[^"]*"/i', 'class="' . htmlspecialchars($new, ENT_QUOTES, 'UTF-8') . '"', $attrs, 1);
} else {
$attrs .= ' class="' . htmlspecialchars($class, ENT_QUOTES, 'UTF-8') . '"';
}
return '
- ';
}, $html) ?? $html;
}
// ---------- Explain how autodetect made a decision ----------
function paste_build_explain(string $source, string $title, string $sample, string $lang): string {
$sample = (string) $sample;
if ($sample !== '') $sample = substr($sample, 0, 2048);
$lang = strtolower($lang);
$ext = strtolower((string) pathinfo((string)$title, PATHINFO_EXTENSION));
switch ($source) {
case 'php-tag':
$phpCount = preg_match_all('/<\?(php|=)/i', $sample, $m1);
return "Found PHP opening tag(s) (" . (int)$phpCount . ") such as '|-{3,}\s*$|\*\s|\d+\.\s|\[.+?\]\(.+?\))/m', $sample);
return "Markdown structure detected (headings/lists/links: ~{$h} signals). Rendered as Markdown.";
}
case 'heuristic': {
if (in_array($lang, ['yaml','yml'], true)) {
$kv = preg_match_all('/^[ \t\-]*[A-Za-z0-9_.-]+:\s/m', $sample);
$list = preg_match_all('/^[ \t]*-\s/m', $sample);
return "YAML heuristics: key:value pairs (~{$kv}) and list items (~{$list}).";
}
if (in_array($lang, ['sql','pgsql','mysql','tsql'], true)) {
$kw = preg_match_all('/\b(SELECT|INSERT|UPDATE|DELETE|CREATE|ALTER|FROM|WHERE|JOIN)\b/i', $sample);
return "SQL keywords matched (~{$kw}). Guessed {$lang}.";
}
if ($lang === 'makefile' || $lang === 'make') {
$t = preg_match_all('/^[A-Za-z0-9_.-]+:.*$/m', $sample);
return "Makefile targets detected (~{$t}).";
}
if ($lang === 'dos' || $lang === 'batch') {
$b = preg_match_all('/^\s*(?:@?echo|rem|set|call|goto)\b/i', $sample);
return "Batch/DOS commands matched (~{$b}).";
}
return "Heuristic rules matched characteristic tokens for {$lang}.";
}
case 'hljs':
case 'hljs-auto':
return "highlight.php auto-guess selected ‘{$lang}’ after other hints were inconclusive.";
case 'fallback':
return "Generic fallback selected a safe default (‘{$lang}’).";
case 'explicit':
return "Language was explicitly chosen by the author.";
default:
return ucfirst($source) . " → {$lang}.";
}
}
// ---------- autodetect: filename/extension hint ----------
function paste_pick_from_filename(?string $title, string $engine = 'highlight', $hl = null): ?string {
$title = (string) ($title ?? '');
$base = trim(basename($title));
if ($base === '') return null;
// No extension cases
if (strcasecmp($base, 'Makefile') === 0) return ($engine === 'geshi') ? 'make' : 'makefile';
if (strcasecmp($base, 'Dockerfile') === 0) return 'dockerfile';
$ext = strtolower((string) pathinfo($base, PATHINFO_EXTENSION));
if ($ext === '') return null;
static $ext2hl = [
'php'=>'php','phtml'=>'php','inc'=>'php',
'js'=>'javascript','mjs'=>'javascript','cjs'=>'javascript',
'ts'=>'typescript','tsx'=>'typescript','jsx'=>'javascript',
'py'=>'python','rb'=>'ruby','pl'=>'perl','pm'=>'perl','raku'=>'perl',
'sh'=>'bash','bash'=>'bash','zsh'=>'bash','ksh'=>'bash',
'ps1'=>'powershell',
'c'=>'c','h'=>'c','i'=>'c',
'cpp'=>'cpp','cxx'=>'cpp','cc'=>'cpp','hpp'=>'cpp','hxx'=>'cpp','hh'=>'cpp',
'cs'=>'csharp','java'=>'java','go'=>'go','rs'=>'rust','kt'=>'kotlin','kts'=>'kotlin',
'm'=>'objectivec','mm'=>'objectivec',
'json'=>'json','yml'=>'yaml','yaml'=>'yaml','ini'=>'ini','toml'=>'toml','properties'=>'properties',
'xml'=>'xml','xhtml'=>'xml','xq'=>'xquery','xqy'=>'xquery',
'html'=>'xml','htm'=>'xml','svg'=>'xml',
'md'=>'markdown','markdown'=>'markdown',
'sql'=>'sql','psql'=>'pgsql',
'bat'=>'dos','cmd'=>'dos','nginx'=>'nginx','conf'=>'ini','cfg'=>'ini','txt'=>'plaintext',
'make'=>'makefile','mk'=>'makefile','mak'=>'makefile','gradle'=>'gradle','dockerfile'=>'dockerfile'
];
$hlId = $ext2hl[$ext] ?? null;
if ($hlId === null) return null;
if ($engine === 'highlight') {
if ($hl && method_exists($hl, 'listLanguages')) {
$set = array_map('strtolower', (array)$hl->listLanguages());
if (in_array($hlId, $set, true)) return $hlId;
if ($hlId === 'pgsql' && in_array('sql', $set, true)) return 'sql';
if ($hlId === 'plaintext' && in_array('text', $set, true)) return 'text';
return null;
}
return $hlId;
}
// GeSHi: normalize if helper exists
if (function_exists('paste_normalize_lang')) {
$g = paste_normalize_lang($hlId, 'geshi', null);
return $g ?: null;
}
return null;
}
function is_probable_php_tag(string $code): bool {
return (bool) preg_match('/<\?(php|=)/i', $code);
}
// --- Safe themed error renderers (header -> errors -> footer) ---
function themed_error_render(string $msg, int $http_code = 404, bool $show_password_form = false): void {
global $default_theme, $lang, $baseurl, $site_name, $pdo, $mod_rewrite, $require_password, $paste_id;
$site_name = $site_name ?? '';
$p_title = $lang['error'] ?? 'Error';
$enablegoog = 'no';
$enablefb = 'no';
if (!headers_sent()) {
http_response_code($http_code);
header('Content-Type: text/html; charset=utf-8');
}
$require_password = $show_password_form;
$error = $msg;
$theme = 'theme/' . htmlspecialchars($default_theme ?? 'default', ENT_QUOTES, 'UTF-8');
require_once $theme . '/header.php';
require_once $theme . '/errors.php';
require_once $theme . '/footer.php';
exit;
}
function render_error_and_exit(string $msg, string $http = '404'): void {
$code = ($http === '403') ? 403 : 404;
themed_error_render($msg, $code, false);
}
function render_password_required_and_exit(string $msg): void {
themed_error_render($msg, 403, true);
}
// --- Inputs ---
$p_password = '';
$paste_id = null;
if (isset($_GET['id']) && $_GET['id'] !== '') {
$paste_id = (int) trim((string) $_GET['id']);
} elseif (isset($_POST['id']) && $_POST['id'] !== '') {
$paste_id = (int) trim((string) $_POST['id']);
}
try {
// site_info
$stmt = $pdo->query("SELECT * FROM site_info WHERE id='1'");
$si = $stmt->fetch() ?: [];
$title = trim($si['title'] ?? '');
$des = trim($si['des'] ?? '');
$baseurl = rtrim(trim($si['baseurl'] ?? ''), '/') . '/';
$keyword = trim($si['keyword'] ?? '');
$site_name = trim($si['site_name'] ?? '');
$email = trim($si['email'] ?? '');
$twit = trim($si['twit'] ?? '');
$face = trim($si['face'] ?? '');
$gplus = trim($si['gplus'] ?? '');
$ga = trim($si['ga'] ?? '');
$additional_scripts = trim($si['additional_scripts'] ?? '');
// allow DB to define mod_rewrite
if (isset($si['mod_rewrite']) && $si['mod_rewrite'] !== '') {
$mod_rewrite = (string) $si['mod_rewrite'];
}
// interface
$stmt = $pdo->query("SELECT * FROM interface WHERE id='1'");
$iface = $stmt->fetch() ?: [];
$default_lang = trim($iface['lang'] ?? 'en.php');
$default_theme = trim($iface['theme'] ?? 'default');
require_once("langs/$default_lang");
// ban check
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
if (is_banned($pdo, $ip)) {
render_error_and_exit($lang['banned'] ?? 'You are banned from this site.', '403');
}
// site permissions
$stmt = $pdo->query("SELECT * FROM site_permissions WHERE id='1'");
$perm = $stmt->fetch() ?: [];
$disableguest = trim($perm['disableguest'] ?? 'off');
$siteprivate = trim($perm['siteprivate'] ?? 'off');
if ($_SERVER['REQUEST_METHOD'] !== 'POST' && $siteprivate === "on") {
$privatesite = "on";
}
// page views (best effort)
$date = date('Y-m-d');
try {
$stmt = $pdo->prepare("SELECT id, tpage, tvisit FROM page_view WHERE date = ?");
$stmt->execute([$date]);
$pv = $stmt->fetch();
if ($pv) {
$page_view_id = (int) $pv['id'];
$tpage = (int) $pv['tpage'] + 1;
$tvisit = (int) $pv['tvisit'];
$stmt = $pdo->prepare("SELECT COUNT(*) FROM visitor_ips WHERE ip = ? AND visit_date = ?");
$stmt->execute([$ip, $date]);
if ((int) $stmt->fetchColumn() === 0) {
$tvisit++;
$stmt = $pdo->prepare("INSERT INTO visitor_ips (ip, visit_date) VALUES (?, ?)");
$stmt->execute([$ip, $date]);
}
$stmt = $pdo->prepare("UPDATE page_view SET tpage = ?, tvisit = ? WHERE id = ?");
$stmt->execute([$tpage, $tvisit, $page_view_id]);
} else {
$stmt = $pdo->prepare("INSERT INTO page_view (date, tpage, tvisit) VALUES (?, ?, ?)");
$stmt->execute([$date, 1, 1]);
$stmt = $pdo->prepare("INSERT INTO visitor_ips (ip, visit_date) VALUES (?, ?)");
$stmt->execute([$ip, $date]);
}
} catch (PDOException $e) {
error_log("Page view tracking error: " . $e->getMessage());
}
// ads
$stmt = $pdo->query("SELECT * FROM ads WHERE id='1'");
$ads = $stmt->fetch() ?: [];
$text_ads = trim($ads['text_ads'] ?? '');
$ads_1 = trim($ads['ads_1'] ?? '');
$ads_2 = trim($ads['ads_2'] ?? '');
// Guard ID
if (!$paste_id) {
render_error_and_exit($lang['notfound'] ?? 'Paste not found.');
}
// load paste
$stmt = $pdo->prepare("SELECT * FROM pastes WHERE id = ?");
$stmt->execute([$paste_id]);
if ($stmt->rowCount() === 0) {
render_error_and_exit($lang['notfound'] ?? 'Paste not found.');
}
$row = $stmt->fetch();
// paste fields
$p_title = (string) ($row['title'] ?? '');
$p_content = (string) ($row['content'] ?? '');
$p_visible = (string) ($row['visible'] ?? '0');
$p_code = (string) ($row['code'] ?? 'text');
$p_expiry = trim((string) ($row['expiry'] ?? 'NULL'));
$p_password = (string) ($row['password'] ?? 'NONE');
$p_member = (string) ($row['member'] ?? '');
$p_date = (string) ($row['date'] ?? '');
$p_encrypt = (string) ($row['encrypt'] ?? '0');
$p_views = getPasteViewCount($pdo, (int) $paste_id);
// ---- comments config for view (AFTER $p_password is known) ----
// Read site-wide flags from config.php (with safe defaults)
$comments_enabled = isset($comments_enabled) ? (bool)$comments_enabled : true;
$comments_require_login = isset($comments_require_login) ? (bool)$comments_require_login : true;
$comments_on_protected = isset($comments_on_protected) ? (bool)$comments_on_protected : false;
// Should we render the comments section for this paste?
$show_comments = $comments_enabled && ($comments_on_protected || $p_password === "NONE");
// Is the current user allowed to post a comment?
$can_comment = $show_comments && ( !$comments_require_login || isset($_SESSION['username']) );
$comment_error = '';
$comment_success = '';
// private?
if ($p_visible === "2") {
if (!isset($_SESSION['username']) || $p_member !== (string) ($_SESSION['username'] ?? '')) {
render_error_and_exit($lang['privatepaste'] ?? 'This is a private paste.', '403');
}
}
// expiry
if ($p_expiry !== "NULL" && $p_expiry !== "SELF") {
$input_time = (int) $p_expiry;
if ($input_time > 0 && $input_time < time()) {
render_error_and_exit($lang['expired'] ?? 'This paste has expired.');
}
}
// decrypt if needed
if ($p_encrypt === "1") {
if (!defined('SECRET')) {
render_error_and_exit(($lang['error'] ?? 'Error') . ': Missing SECRET.', '403');
}
$dec = decrypt($p_content, hex2bin(SECRET));
if ($dec === null || $dec === '') {
render_error_and_exit(($lang['error'] ?? 'Error') . ': Decryption failed.', '403');
}
$p_content = $dec;
}
$op_content = trim(htmlspecialchars_decode($p_content));
// download/raw/embed
if (isset($_GET['download'])) {
if ($p_password === "NONE" || (isset($_GET['password']) && password_verify((string) $_GET['password'], $p_password))) {
doDownload((int) $paste_id, $p_title, $op_content, $p_code);
exit;
}
render_password_required_and_exit(
isset($_GET['password'])
? ($lang['wrongpassword'] ?? 'Incorrect password.')
: ($lang['pwdprotected'] ?? 'This paste is password-protected.')
);
}
if (isset($_GET['raw'])) {
if ($p_password === "NONE" || (isset($_GET['password']) && password_verify((string) $_GET['password'], $p_password))) {
rawView((int) $paste_id, $p_title, $op_content, $p_code);
exit;
}
render_password_required_and_exit(
isset($_GET['password'])
? ($lang['wrongpassword'] ?? 'Incorrect password.')
: ($lang['pwdprotected'] ?? 'This paste is password-protected.')
);
}
if (isset($_GET['embed'])) {
if ($p_password === "NONE" || (isset($_GET['password']) && password_verify((string) $_GET['password'], $p_password))) {
// Embed view is standalone; we pass empty $ges_style as we don't inject CSS here.
embedView((int) $paste_id, $p_title, $p_content, $p_code, $title, $baseurl, $ges_style, $lang);
exit;
}
render_password_required_and_exit(
isset($_GET['password'])
? ($lang['wrongpassword'] ?? 'Incorrect password.')
: ($lang['pwdprotected'] ?? 'This paste is password-protected.')
);
}
// highlight extraction
$highlight = [];
$prefix = '!highlight!';
if ($prefix !== '') {
$lines = explode("\n", $p_content);
$p_content = '';
foreach ($lines as $idx => $line) {
if (strncmp($line, $prefix, strlen($prefix)) === 0) {
$highlight[] = $idx + 1;
$line = substr($line, strlen($prefix));
}
$p_content .= $line . "\n";
}
$p_content = rtrim($p_content);
}
// -------------- transform content --------------
$p_code_explain = ''; // will be filled when we detect
if ($p_code === "markdown") {
// ---------- Markdown (keep using Parsedown, safe) ----------
require_once $parsedown_path;
$Parsedown = new Parsedown();
$md_input = htmlspecialchars_decode($p_content);
// Disable raw HTML and sanitize URLs during Markdown rendering
if (method_exists($Parsedown, 'setSafeMode')) {
$Parsedown->setSafeMode(true);
if (method_exists($Parsedown, 'setMarkupEscaped')) {
$Parsedown->setMarkupEscaped(true);
}
} else {
// Fallback for very old Parsedown: escape raw HTML tags BEFORE parsing
$md_input = preg_replace_callback('/<[^>]*>/', static function($m){
return htmlspecialchars($m[0], ENT_QUOTES, 'UTF-8');
}, $md_input);
}
$rendered = $Parsedown->text($md_input);
$p_content = '
'.sanitize_allowlist_html($rendered).'
';
$p_code_effective = 'markdown';
$p_code_label = $geshiformats['markdown'] ?? 'Markdown';
$p_code_source = 'explicit';
$p_code_explain = 'Language was explicitly chosen by the author.';
} else {
// ---------- Code (choose engine) ----------
$code_input = htmlspecialchars_decode($p_content);
if (($highlighter ?? 'geshi') === 'highlight') {
// ---- Highlight.php ----
$hlId = map_to_hl_lang($p_code); // map geshi -> hljs ids
$use_auto = ($hlId === 'auto' || $hlId === 'autodetect' || $hlId === '' || $hlId === 'text');
try {
$hl = function_exists('make_highlighter') ? make_highlighter() : null;
if (!$hl) { throw new \RuntimeException('Highlighter not available'); }
// 1) Filename strong hint if auto
if ($use_auto) {
$fname = paste_pick_from_filename($p_title ?? '', 'highlight', $hl);
if ($fname) {
$langTry = $fname;
$p_code_source = 'filename';
$p_code_explain = paste_build_explain('filename', $p_title ?? '', $code_input, $langTry);
$res = $hl->highlight($langTry, $code_input);
$inner = $res->value ?: htmlspecialchars($code_input, ENT_QUOTES, 'UTF-8');
$p_content = hl_wrap_with_lines($inner, $langTry, $highlight);
$p_code_effective = $langTry;
$p_code_label = $geshiformats[$langTry] ?? (function_exists('paste_friendly_label') ? paste_friendly_label($langTry) : strtoupper($langTry));
goto HL_DONE;
}
}
// 2) PHP tag hard rule if auto
if ($use_auto && is_probable_php_tag($code_input)) {
$langTry = 'php';
$p_code_source = 'php-tag';
$p_code_explain = paste_build_explain('php-tag', $p_title ?? '', $code_input, $langTry);
$res = $hl->highlight($langTry, $code_input);
$inner = $res->value ?: htmlspecialchars($code_input, ENT_QUOTES, 'UTF-8');
$p_content = hl_wrap_with_lines($inner, $langTry, $highlight);
$p_code_effective = $langTry;
$p_code_label = $geshiformats[$langTry] ?? 'PHP';
goto HL_DONE;
}
if ($use_auto && function_exists('paste_autodetect_language')) {
// Unified autodetect
$det = paste_autodetect_language($code_input, 'highlight', $hl);
$langTry = $det['id'];
$p_code_effective = $langTry;
$p_code_label = $geshiformats[$langTry] ?? $det['label'];
$p_code_source = $det['source'];
$p_code_explain = $det['explain'] ?? '';
if ($p_code_explain === '') {
$p_code_explain = paste_build_explain($p_code_source, $p_title ?? '', $code_input, $langTry);
}
if ($langTry === 'markdown') {
// Render Markdown via Parsedown
require_once $parsedown_path;
$Parsedown = new Parsedown();
if (method_exists($Parsedown, 'setSafeMode')) {
$Parsedown->setSafeMode(true);
if (method_exists($Parsedown, 'setMarkupEscaped')) $Parsedown->setMarkupEscaped(true);
}
$rendered = $Parsedown->text($code_input);
$p_content = '' . sanitize_allowlist_html($rendered) . '
';
} else {
$res = $hl->highlight($langTry, $code_input);
$inner = $res->value ?: htmlspecialchars($code_input, ENT_QUOTES, 'UTF-8');
$p_content = hl_wrap_with_lines($inner, $langTry, $highlight);
}
} else {
// Explicit language requested OR fallback to built-in auto
$langTry = function_exists('paste_normalize_lang') ? paste_normalize_lang($hlId, 'highlight', $hl) : $hlId;
$p_code_source = 'explicit';
try {
$res = $hl->highlight($langTry, $code_input);
} catch (\Throwable $e) {
// Fallbacks: pgsql -> sql, otherwise shared autodetect
if ($langTry === 'pgsql') {
$res = $hl->highlight('sql', $code_input);
} elseif (function_exists('paste_autodetect_language')) {
$det = paste_autodetect_language($code_input, 'highlight', $hl);
$langTry = $det['id'];
$p_code_label = $geshiformats[$langTry] ?? $det['label'];
$p_code_source = $det['source'];
$p_code_explain = $det['explain'] ?? '';
if ($p_code_explain === '') {
$p_code_explain = paste_build_explain($p_code_source, $p_title ?? '', $code_input, $langTry);
}
if ($langTry === 'markdown') {
require_once $parsedown_path;
$Parsedown = new Parsedown();
if (method_exists($Parsedown, 'setSafeMode')) {
$Parsedown->setSafeMode(true);
if (method_exists($Parsedown, 'setMarkupEscaped')) $Parsedown->setMarkupEscaped(true);
}
$rendered = $Parsedown->text($code_input);
$p_content = '' . sanitize_allowlist_html($rendered) . '
';
$p_code_effective = 'markdown';
goto HL_DONE;
} else {
$res = $hl->highlight($langTry, $code_input);
}
} else {
// last resort: hljs auto
$p_code_source = 'hljs';
$res = $hl->highlightAuto($code_input);
$langTry = strtolower((string)($res->language ?? $langTry));
$p_code_explain = paste_build_explain('hljs', $p_title ?? '', $code_input, $langTry ?: 'plaintext');
}
}
$inner = $res->value ?: htmlspecialchars($code_input, ENT_QUOTES, 'UTF-8');
$p_content = hl_wrap_with_lines($inner, $langTry, $highlight);
$p_code_effective = $langTry;
$p_code_label = $geshiformats[$langTry] ?? (function_exists('paste_friendly_label') ? paste_friendly_label($langTry) : strtoupper($langTry));
}
HL_DONE: ;
} catch (\Throwable $t) {
// Last resort: plain escaped
$esc = htmlspecialchars($code_input, ENT_QUOTES, 'UTF-8');
$p_content = hl_wrap_with_lines($esc, 'plaintext', $highlight);
$p_code_effective = 'plaintext';
$p_code_label = $geshiformats['plaintext'] ?? 'Plain Text';
$p_code_source = $p_code_source ?? 'fallback';
$p_code_explain = $p_code_explain ?: paste_build_explain('fallback', $p_title ?? '', $code_input, 'plaintext');
}
} else {
// ---- GeSHi ----
$use_auto = ($p_code === 'auto' || $p_code === 'autodetect' || $p_code === '' || $p_code === 'text');
$lang_for_geshi = $p_code;
// 1) Filename hint if auto
if ($use_auto) {
$fname = paste_pick_from_filename($p_title ?? '', 'geshi', null);
if ($fname) {
$lang_for_geshi = $fname;
$p_code_effective = $lang_for_geshi;
$p_code_label = $geshiformats[$lang_for_geshi] ?? (function_exists('paste_friendly_label') ? paste_friendly_label($lang_for_geshi) : strtoupper($lang_for_geshi));
$p_code_source = 'filename';
$p_code_explain = paste_build_explain('filename', $p_title ?? '', $code_input, $lang_for_geshi);
}
}
// 2) PHP tag hard rule if auto and not already set by filename
if ($use_auto && empty($p_code_source) && is_probable_php_tag($code_input)) {
$lang_for_geshi = function_exists('paste_normalize_lang') ? paste_normalize_lang('php', 'geshi', null) : 'php';
$p_code_effective = $lang_for_geshi;
$p_code_label = $geshiformats[$lang_for_geshi] ?? 'PHP';
$p_code_source = 'php-tag';
$p_code_explain = paste_build_explain('php-tag', $p_title ?? '', $code_input, $lang_for_geshi);
}
if ($use_auto && empty($p_code_source) && function_exists('paste_autodetect_language')) {
$det = paste_autodetect_language($code_input, 'geshi', null);
$lang_for_geshi = $det['id']; // GeSHi id (mapped)
$p_code_effective = $lang_for_geshi;
$p_code_label = $det['label'];
$p_code_source = $det['source'];
$p_code_explain = $det['explain'] ?? '';
if ($p_code_explain === '') {
$p_code_explain = paste_build_explain($p_code_source, $p_title ?? '', $code_input, $lang_for_geshi);
}
if ($lang_for_geshi === 'markdown') {
// For Markdown, keep Parsedown path for consistent rendering
require_once $parsedown_path;
$Parsedown = new Parsedown();
if (method_exists($Parsedown, 'setSafeMode')) {
$Parsedown->setSafeMode(true);
if (method_exists($Parsedown, 'setMarkupEscaped')) $Parsedown->setMarkupEscaped(true);
}
$rendered = $Parsedown->text($code_input);
$p_content = '' . sanitize_allowlist_html($rendered) . '
';
goto GESHI_DONE;
}
} elseif ($use_auto && empty($p_code_source) && function_exists('paste_probable_markdown') && paste_probable_markdown($code_input)) {
// Minimal fallback
require_once $parsedown_path;
$Parsedown = new Parsedown();
if (method_exists($Parsedown, 'setSafeMode')) {
$Parsedown->setSafeMode(true);
if (method_exists($Parsedown, 'setMarkupEscaped')) $Parsedown->setMarkupEscaped(true);
}
$rendered = $Parsedown->text($code_input);
$p_content = '' . sanitize_allowlist_html($rendered) . '
';
$p_code_effective = 'markdown';
$p_code_label = 'Markdown';
$p_code_source = 'markdown';
$p_code_explain = "Markdown probability based on headings/lists/links; rendering as Markdown.";
goto GESHI_DONE;
}
$geshi = new GeSHi($code_input, $lang_for_geshi, $path);
// Use classes, not inline CSS; let theme CSS style everything
if (method_exists($geshi, 'enable_classes')) $geshi->enable_classes();
if (method_exists($geshi, 'set_header_type')) $geshi->set_header_type(GESHI_HEADER_DIV);
// Line numbers (NORMAL to avoid rollovers). No inline style.
if (method_exists($geshi, 'enable_line_numbers')) $geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
if (!empty($highlight) && method_exists($geshi, 'highlight_lines_extra')) {
$geshi->highlight_lines_extra($highlight);
}
// force plain integer formatting
if (method_exists($geshi, 'set_line_number_format')) {
$geshi->set_line_number_format('%d', 0);
}
// Parse HTML (class-based markup)
$p_content = $geshi->parse_code();
// Add a class to the requested lines so theme CSS can style them
if (!empty($highlight)) {
$p_content = geshi_add_line_highlight_class($p_content, $highlight, 'hljs-hl');
}
// No stylesheet injection here; theme CSS handles it.
$ges_style = '';
// Effective values for UI
$p_code_effective = $lang_for_geshi;
if (!isset($p_code_label)) {
$p_code_label = $geshiformats[$p_code_effective] ?? (function_exists('paste_friendly_label') ? paste_friendly_label($p_code_effective) : strtoupper($p_code_effective));
}
GESHI_DONE: ;
}
}
// ======= New comment submit (PRG) — supports parent_id for replies =======
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add_comment') {
if (!$paste_id) {
$comment_error = 'Invalid paste.';
} elseif (!$comments_enabled) {
$comment_error = $lang['comments_off'] ?? 'Comments are disabled.';
} elseif (!$show_comments) {
$comment_error = $lang['comments_blocked_here'] ?? 'Comments are not available for this paste.';
} elseif ($comments_require_login && !isset($_SESSION['username'])) {
$comment_error = $lang['commentlogin'] ?? 'You must be logged in to comment.';
} elseif (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'] ?? '', (string)$_POST['csrf_token'])) {
$comment_error = $lang['invalidtoken'] ?? 'Invalid CSRF token.';
} else {
$uid = 0;
$uname = 'Guest';
if (isset($_SESSION['username'])) {
$uname = (string)$_SESSION['username'];
// fetch id to store as FK
try {
$q = $pdo->prepare("SELECT id FROM users WHERE username = ?");
$q->execute([$uname]);
$uid = (int)($q->fetchColumn() ?: 0);
} catch (Throwable $e) { $uid = 0; }
}
$parent_id = null;
if (isset($_POST['parent_id']) && $_POST['parent_id'] !== '') {
$parent_id = (int)$_POST['parent_id'];
}
// Backward/forward compatible call to addPasteComment()
$okId = null;
try {
if (function_exists('addPasteComment')) {
$rf = new ReflectionFunction('addPasteComment');
if ($rf->getNumberOfParameters() >= 7) {
// Signature with parent_id
$okId = addPasteComment(
$pdo,
(int)$paste_id,
$uid ?: null,
$uname,
$_SERVER['REMOTE_ADDR'] ?? '0.0.0.0',
(string)($_POST['comment_body'] ?? ''),
$parent_id
);
} else {
// no parent support
$okId = addPasteComment(
$pdo,
(int)$paste_id,
$uid ?: null,
$uname,
$_SERVER['REMOTE_ADDR'] ?? '0.0.0.0',
(string)($_POST['comment_body'] ?? '')
);
}
}
} catch (Throwable $e) {
error_log('add_comment error: ' . $e->getMessage());
$okId = null;
}
if ($okId) {
// Avoid resubmit on refresh
$to = ($mod_rewrite == '1')
? $baseurl . $paste_id . '#comments'
: $baseurl . 'paste.php?id=' . (int)$paste_id . '#comments';
header('Location: ' . $to);
exit;
}
$comment_error = 'Could not add comment.';
}
}
// ======= Delete comment (PRG) — hard delete; replies removed via FK CASCADE) =======
// (Allowed even if comments are currently disabled — so users can still remove their content.)
if ($_SERVER['REQUEST_METHOD'] === 'POST'
&& isset($_POST['action']) && $_POST['action'] === 'delete_comment') {
$isAjax = (
(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest')
|| (isset($_POST['ajax']) && $_POST['ajax'] === '1')
);
$err = '';
do {
if (empty($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'] ?? '', (string)$_POST['csrf_token'])) {
$err = $lang['invalidtoken'] ?? 'Invalid CSRF token.'; break;
}
if (!isset($_SESSION['username'])) {
$err = $lang['commentlogin'] ?? 'Log in required.'; break;
}
// Ensure current paste context (accept id from GET OR POST for robustness)
if (!$paste_id) {
$paste_id = (isset($_POST['id']) && $_POST['id'] !== '') ? (int)$_POST['id'] : $paste_id;
}
$cid = isset($_POST['comment_id']) ? (int)$_POST['comment_id'] : 0;
if ($cid <= 0 || !$paste_id) { $err = 'Invalid comment.'; break; }
// Resolve current user id
$uid = 0;
try {
$q = $pdo->prepare("SELECT id FROM users WHERE username = ?");
$q->execute([$_SESSION['username']]);
$uid = (int)($q->fetchColumn() ?: 0);
} catch (Throwable $e) {}
if (!userOwnsComment($pdo, $cid, $uid, (string)$_SESSION['username'])) {
$err = $lang['forbidden'] ?? 'Not allowed.'; break;
}
$ok = false;
if (function_exists('deleteComment')) {
$ok = (bool) deleteComment($pdo, $cid);
} else {
// Guard by paste_id to avoid cross-paste deletes
$stmt = $pdo->prepare("DELETE FROM paste_comments WHERE id = ? AND paste_id = ?");
$ok = $stmt->execute([$cid, (int)$paste_id]);
}
if (!$ok) { $err = $lang['error'] ?? 'Delete failed.'; }
} while (false);
if ($isAjax) {
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'success' => ($err === ''),
'message' => $err,
]);
exit;
}
// Redirect back to #comments (PRG). If error, carry as c_err=
$to = ($mod_rewrite == '1')
? $baseurl . (int)$paste_id . '#comments'
: $baseurl . 'paste.php?id=' . (int)$paste_id . '#comments';
if ($err !== '') {
$to .= (strpos($to, '?') === false ? '?' : '&') . 'c_err=' . rawurlencode($err);
}
header('Location: ' . $to);
exit;
}
// header
$theme = 'theme/' . htmlspecialchars($default_theme, ENT_QUOTES, 'UTF-8');
require_once $theme . '/header.php';
// Fetch comments (tree if available), then decorate
if (function_exists('getPasteCommentsTree')) {
$comments = getPasteCommentsTree($pdo, (int)$paste_id);
// decorate recursively
$decorate = function (&$node) use (&$decorate, $pdo) {
$node['body_html'] = render_comment_html((string)($node['body'] ?? ''));
$node['can_delete'] = isset($_SESSION['username']) && isset($_SESSION['csrf_token']) && (function() use ($pdo, $node) {
$uid = 0;
try {
$q = $pdo->prepare("SELECT id FROM users WHERE username = ?");
$q->execute([$_SESSION['username']]);
$uid = (int)($q->fetchColumn() ?: 0);
} catch (Throwable $e) { $uid = 0; }
return userOwnsComment($pdo, (int)$node['id'], $uid, (string)$_SESSION['username']);
})();
if (!empty($node['children'])) {
foreach ($node['children'] as &$ch) $decorate($ch);
unset($ch);
}
};
foreach ($comments as &$c) $decorate($c);
unset($c);
} else {
// flat fetch
$comments = getPasteComments($pdo, (int)$paste_id, 200, 0);
foreach ($comments as &$c) {
$c['body_html'] = render_comment_html((string)$c['body']);
$c['can_delete'] = isset($_SESSION['username']) && isset($_SESSION['csrf_token']) && (function() use ($pdo, $c) {
$uid = 0;
try {
$q = $pdo->prepare("SELECT id FROM users WHERE username = ?");
$q->execute([$_SESSION['username']]);
$uid = (int)($q->fetchColumn() ?: 0);
} catch (Throwable $e) { $uid = 0; }
return userOwnsComment($pdo, (int)$c['id'], $uid, (string)$_SESSION['username']);
})();
}
unset($c);
}
// Carry any error from PRG redirect
if ($comment_error === '' && isset($_GET['c_err']) && $_GET['c_err'] !== '') {
$comment_error = (string)$_GET['c_err'];
}
// view OR password prompt
if ($p_password === "NONE") {
updateMyView($pdo, (int) $paste_id);
$p_download = $mod_rewrite == '1' ? $baseurl . "download/$paste_id" : $baseurl . "paste.php?download&id=$paste_id";
$p_raw = $mod_rewrite == '1' ? $baseurl . "raw/$paste_id" : $baseurl . "paste.php?raw&id=$paste_id";
$p_embed = $mod_rewrite == '1' ? $baseurl . "embed/$paste_id" : $baseurl . "paste.php?embed&id=$paste_id";
require_once $theme . '/view.php';
// View-once (SELF) cleanup after increment
$current_views = getPasteViewCount($pdo, (int) $paste_id);
if ($p_expiry === "SELF" && $current_views >= 2) {
deleteMyPaste($pdo, (int) $paste_id);
}
} else {
// Password-protected flow shows the prompt via errors.php
$require_password = true;
$p_password_input = isset($_POST['mypass'])
? trim((string) $_POST['mypass'])
: (string) ($_SESSION['p_password'] ?? '');
// Prebuild convenience links that carry the typed password
$p_download = $mod_rewrite == '1'
? $baseurl . "download/$paste_id?password=" . rawurlencode($p_password_input)
: $baseurl . "paste.php?download&id=$paste_id&password=" . rawurlencode($p_password_input);
$p_raw = $mod_rewrite == '1'
? $baseurl . "raw/$paste_id?password=" . rawurlencode($p_password_input)
: $baseurl . "paste.php?raw&id=$paste_id&password=" . rawurlencode($p_password_input);
$p_embed = $mod_rewrite == '1'
? $baseurl . "embed/$paste_id?password=" . rawurlencode($p_password_input)
: $baseurl . "paste.php?embed&id=$paste_id&password=" . rawurlencode($p_password_input);
if ($p_password_input !== '' && password_verify($p_password_input, $p_password)) {
updateMyView($pdo, (int) $paste_id);
require_once $theme . '/view.php';
$current_views = getPasteViewCount($pdo, (int) $paste_id);
if ($p_expiry === "SELF" && $current_views >= 2) {
deleteMyPaste($pdo, (int) $paste_id);
}
} else {
$error = $p_password_input !== ''
? ($lang['wrongpwd'] ?? 'Incorrect password.')
: ($lang['pwdprotected'] ?? 'This paste is password-protected.');
$_SESSION['p_password'] = $p_password_input;
require_once $theme . '/errors.php'; // partial renders password prompt
}
}
// footer
require_once $theme . '/footer.php';
} catch (PDOException $e) {
error_log("paste.php: Database error: " . $e->getMessage());
// Still render a readable error page (no password box)
$error = ($lang['error'] ?? 'Database error.') . ': ' . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
global $default_theme, $baseurl, $mod_rewrite, $pdo, $require_password;
$require_password = false;
$theme = 'theme/' . htmlspecialchars($default_theme ?? 'default', ENT_QUOTES, 'UTF-8');
require_once $theme . '/header.php';
require_once $theme . '/errors.php';
require_once $theme . '/footer.php';
}
?>