..., 'description' => ..., 'url' => ...] or null function get_meta_for_path(string $uri): ?array { // Match protocol/collection/user routes (path-based, not hash-based) // e.g. /protocols/slug, /collections/slug, /users/username if (!preg_match('#^/(protocols|collections|users)/([^/]+)#', $uri, $m)) { return null; } $type = $m[1]; $slug = $m[2]; // Load minimal DB access without full API overhead require_once __DIR__ . '/api/db.php'; require_once __DIR__ . '/api/config.php'; if ($type === 'protocols') { $row = db_one( "SELECT p.title, p.description, p.source, u.username AS author_username FROM protocols p LEFT JOIN users u ON p.author_id = u.id WHERE p.slug = ? AND p.visibility = 'public'", [$slug] ); if (!$row) return null; return [ 'title' => $row['title'], 'description' => $row['description'] ?: ($row['source'] ? 'Source: ' . $row['source'] : ''), 'type' => 'article', ]; } if ($type === 'collections') { $row = db_one( "SELECT c.title, c.description, u.username AS author_username FROM collections c LEFT JOIN users u ON c.author_id = u.id WHERE c.slug = ? AND c.visibility = 'public'", [$slug] ); if (!$row) return null; return [ 'title' => $row['title'], 'description' => $row['description'] ?: '', 'type' => 'website', ]; } if ($type === 'users') { $row = db_one("SELECT username, display_name, bio FROM users WHERE username = ?", [$slug]); if (!$row) return null; $name = $row['display_name'] ?: '@' . $row['username']; return [ 'title' => $name, 'description' => $row['bio'] ?: 'Protocols by ' . $name, 'type' => 'profile', ]; } return null; } // Helper: serve index.html with asset paths rewritten for subdirectory deployments // Injects social media meta tags when viewing a specific protocol/collection/user $serve_index = function() use ($basePath, $uri) { $index = __DIR__ . '/frontend/index.html'; if (file_exists($index)) { header('Content-Type: text/html'); $html = file_get_contents($index); // Inject meta tags for social media previews $meta = get_meta_for_path($uri); if ($meta) { require_once __DIR__ . '/api/config.php'; $siteName = config_get('site_name', 'Protocol Droid'); $fullTitle = $meta['title'] . ' · ' . $siteName; $desc = $meta['description']; $canonical = ($basePath ?: '') . $uri; // Build canonical URL (scheme + host + path) $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; $host = $_SERVER['HTTP_HOST'] ?? 'localhost'; $canonicalUrl = $scheme . '://' . $host . $canonical; $metaTags = "$fullTitle\n"; $metaTags .= "\n"; $metaTags .= "\n"; $metaTags .= "\n"; $metaTags .= "\n"; $metaTags .= "\n"; $metaTags .= "\n"; $metaTags .= "\n"; $metaTags .= "\n"; $metaTags .= "\n"; // Replace the default tag with our enriched version $html = preg_replace('#<title>[^<]*#', $metaTags, $html, 1); } // Rewrite absolute /frontend/ paths to include the base path if ($basePath) { $html = str_replace('/frontend/', $basePath . '/frontend/', $html); } echo $html; exit; } http_response_code(404); echo 'Frontend not found'; exit; }; if ($real_file === false || strpos($real_file, $frontend_dir) !== 0) { $serve_index(); } if (!is_file($real_file)) { $serve_index(); } $ext = pathinfo($real_file, PATHINFO_EXTENSION); $types = [ 'css' => 'text/css', 'js' => 'application/javascript', 'html' => 'text/html', 'svg' => 'image/svg+xml', 'png' => 'image/png', 'jpg' => 'image/jpeg', 'json' => 'application/json', ]; if (isset($types[$ext])) header('Content-Type: ' . $types[$ext]); readfile($real_file);