070320261132

This commit is contained in:
Manfred Aabye
2026-03-07 11:32:44 +01:00
committed by GitHub
parent 04c7231a31
commit 3d7867a045
4 changed files with 539 additions and 137 deletions
+1 -7
View File
@@ -74,6 +74,7 @@ Die Datei `env.php` enthält alle sensiblen Zugangsdaten und Umgebungsvariablen
## Robust.ini Setup
MapTileURL = "${Const|BaseURL}/oswebinterface/maptile.php";
MessageUrl = "${Const|BaseURL}/oswebinterface/messages.php"
SearchURL = "${Const|BaseURL}/oswebinterface/search.php"
DestinationGuide = "${Const|BaseURL}/oswebinterface/guide.php"
AvatarPicker = "${Const|BaseURL}/oswebinterface/avatarpicker.php"
@@ -90,10 +91,3 @@ Die Datei `env.php` enthält alle sensiblen Zugangsdaten und Umgebungsvariablen
GridStatusRSS = ${Const|BaseURL}/oswebinterface/gridstatusrss.php
---
+110 -29
View File
@@ -1,35 +1,116 @@
<?php
// Einbinden der Konfigurationsdatei
require_once __DIR__ . '/include/config.php';
// Header setzen, um den Inhaltstyp auf JSON festzulegen
header('Content-Type: application/json');
// Nachrichtendaten basierend auf der MOTD-Einstellung erstellen
if (MOTD === 'Dyn') {
// Dynamische MOTD
$hour = date('H');
if ($hour < 12) {
$greeting = "Guten Morgen";
} else {
$greeting = "Guten Tag";
/**
* Decide whether caller expects JSON. Robust LoginService reads plain text,
* while web tools can still request JSON.
*/
function wants_json_response(): bool
{
if (isset($_GET['format']) && strtolower((string)$_GET['format']) === 'json') {
return true;
}
$message = [
"message" => "$greeting auf " . SITE_NAME . "! Bitte beachte unsere Regeln und Richtlinien.",
"type" => "system",
"url_tos" => BASE_URL . "/include/tos.php",
"url_dmca" => BASE_URL . "/include/dmca.php"
];
} else {
// Statische MOTD
$message = [
"message" => MOTD_STATIC_MESSAGE,
"type" => MOTD_STATIC_TYPE,
"url_tos" => MOTD_STATIC_URL_TOS,
"url_dmca" => MOTD_STATIC_URL_DMCA
];
$accept = $_SERVER['HTTP_ACCEPT'] ?? '';
return stripos($accept, 'application/json') !== false;
}
// JSON-Ausgabe
echo json_encode($message, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
?>
/**
* Query a single numeric value and return null on failure.
*/
function query_scalar_int(mysqli $conn, string $sql): ?int
{
$result = $conn->query($sql);
if ($result === false) {
return null;
}
$row = $result->fetch_row();
$result->free();
if (!$row || !isset($row[0])) {
return null;
}
return (int)$row[0];
}
/**
* Read live grid stats from the Robust database.
*/
function load_grid_stats(): ?array
{
if (!defined('DB_SERVER') || !defined('DB_USERNAME') || !defined('DB_PASSWORD') || !defined('DB_NAME')) {
return null;
}
$conn = @new mysqli(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($conn->connect_errno) {
return null;
}
$stats = [
'online_users' => query_scalar_int($conn, 'SELECT COUNT(*) FROM Presence'),
'regions' => query_scalar_int($conn, 'SELECT COUNT(*) FROM regions'),
'accounts' => query_scalar_int($conn, 'SELECT COUNT(*) FROM UserAccounts'),
'active_30d' => query_scalar_int($conn, 'SELECT COUNT(*) FROM GridUser WHERE Login > (UNIX_TIMESTAMP() - (30*86400))'),
'grid_users' => query_scalar_int($conn, 'SELECT COUNT(*) FROM GridUser'),
];
$conn->close();
foreach ($stats as $value) {
if ($value !== null) {
return $stats;
}
}
return null;
}
/**
* Build MOTD text for viewer login.
*/
function build_message_text(?array $stats): string
{
if (defined('MOTD') && MOTD !== 'Dyn') {
return defined('MOTD_STATIC_MESSAGE') ? (string)MOTD_STATIC_MESSAGE : 'Welcome to the grid!';
}
$hour = (int)date('G');
$greeting = $hour < 12 ? 'Good morning' : 'Good day';
$siteName = defined('SITE_NAME') ? SITE_NAME : 'to the Grid';
$text = $greeting . ' on ' . $siteName . '!';
if ($stats !== null) {
$text .= sprintf(
' Online: %d - Regions: %d - Active 30 days: %d',
(int)($stats['online_users'] ?? 0),
(int)($stats['regions'] ?? 0),
(int)($stats['active_30d'] ?? 0)
);
}
return $text;
}
$stats = load_grid_stats();
$messageText = build_message_text($stats);
$payload = [
'message' => $messageText,
'type' => defined('MOTD_STATIC_TYPE') ? MOTD_STATIC_TYPE : 'system',
'url_tos' => defined('MOTD_STATIC_URL_TOS') ? MOTD_STATIC_URL_TOS : (BASE_URL . '/include/tos.php'),
'url_dmca' => defined('MOTD_STATIC_URL_DMCA') ? MOTD_STATIC_URL_DMCA : (BASE_URL . '/include/dmca.php'),
'stats' => $stats,
'generated_at' => gmdate('c'),
];
if (wants_json_response()) {
header('Content-Type: application/json; charset=utf-8');
echo json_encode($payload, JSON_UNESCAPED_SLASHES);
exit;
}
header('Content-Type: text/plain; charset=utf-8');
echo $messageText;
+312 -101
View File
@@ -1,116 +1,327 @@
<?php
$title = "Web Search";
include 'include/header.php';
header('X-Frame-Options: SAMEORIGIN');
header('X-Content-Type-Options: nosniff');
function gs_param($keys, $default = '')
{
foreach ($keys as $key) {
if (isset($_GET[$key])) {
return trim((string) $_GET[$key]);
}
}
return $default;
}
function gs_escape_like($value)
{
return str_replace(array('\\', '%', '_'), array('\\\\', '\\%', '\\_'), $value);
}
function gs_table_exists($mysqli, $tableName)
{
$sql = 'SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ? LIMIT 1';
$stmt = mysqli_prepare($mysqli, $sql);
if ($stmt === false) {
return false;
}
mysqli_stmt_bind_param($stmt, 's', $tableName);
mysqli_stmt_execute($stmt);
$res = mysqli_stmt_get_result($stmt);
$exists = ($res !== false && mysqli_num_rows($res) > 0);
if ($res !== false) {
mysqli_free_result($res);
}
mysqli_stmt_close($stmt);
return $exists;
}
$queryText = gs_param(array('q', 'query', 'search', 'term', 's', 'text'), '');
$queryType = strtolower(gs_param(array('type', 'category', 'scope', 't'), 'all'));
$typeAlias = array(
'person' => 'people',
'avatars' => 'people',
'avatar' => 'people',
'group' => 'groups',
'place' => 'places',
'regions' => 'places',
'region' => 'places',
'classified' => 'classifieds',
'classifieds' => 'classifieds',
);
if (isset($typeAlias[$queryType])) {
$queryType = $typeAlias[$queryType];
}
$allowedTypes = array('all', 'people', 'groups', 'places', 'classifieds');
if (!in_array($queryType, $allowedTypes, true)) {
$queryType = 'all';
}
$mysqli = @mysqli_connect(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($mysqli === false) {
http_response_code(500);
echo 'Datenbankverbindung fehlgeschlagen.';
exit;
}
$rows = array();
$warnings = array();
if ($queryText !== '') {
$needle = '%' . gs_escape_like($queryText) . '%';
if ($queryType === 'all' || $queryType === 'people') {
$sql = 'SELECT FirstName, LastName FROM UserAccounts WHERE FirstName LIKE ? OR LastName LIKE ? ORDER BY FirstName, LastName LIMIT 100';
$stmt = mysqli_prepare($mysqli, $sql);
if ($stmt !== false) {
mysqli_stmt_bind_param($stmt, 'ss', $needle, $needle);
mysqli_stmt_execute($stmt);
$res = mysqli_stmt_get_result($stmt);
if ($res !== false) {
while ($r = mysqli_fetch_assoc($res)) {
$rows[] = array(
'type' => 'People',
'name' => trim(($r['FirstName'] ?? '') . ' ' . ($r['LastName'] ?? '')),
'extra' => '',
);
}
mysqli_free_result($res);
}
mysqli_stmt_close($stmt);
}
}
if ($queryType === 'all' || $queryType === 'groups') {
$sql = 'SELECT Name, Charter FROM os_groups_groups WHERE Name LIKE ? OR Charter LIKE ? ORDER BY Name LIMIT 100';
$stmt = mysqli_prepare($mysqli, $sql);
if ($stmt !== false) {
mysqli_stmt_bind_param($stmt, 'ss', $needle, $needle);
mysqli_stmt_execute($stmt);
$res = mysqli_stmt_get_result($stmt);
if ($res !== false) {
while ($r = mysqli_fetch_assoc($res)) {
$rows[] = array(
'type' => 'Groups',
'name' => (string) ($r['Name'] ?? ''),
'extra' => (string) ($r['Charter'] ?? ''),
);
}
mysqli_free_result($res);
}
mysqli_stmt_close($stmt);
}
}
if ($queryType === 'all' || $queryType === 'places') {
$sql = 'SELECT regionName, serverIP, serverPort FROM regions WHERE regionName LIKE ? ORDER BY regionName LIMIT 100';
$stmt = mysqli_prepare($mysqli, $sql);
if ($stmt !== false) {
mysqli_stmt_bind_param($stmt, 's', $needle);
mysqli_stmt_execute($stmt);
$res = mysqli_stmt_get_result($stmt);
if ($res !== false) {
while ($r = mysqli_fetch_assoc($res)) {
$extra = (string) ($r['serverIP'] ?? '');
if (isset($r['serverPort']) && $r['serverPort'] !== '') {
$extra .= ':' . $r['serverPort'];
}
$rows[] = array(
'type' => 'Places',
'name' => (string) ($r['regionName'] ?? ''),
'extra' => $extra,
);
}
mysqli_free_result($res);
}
mysqli_stmt_close($stmt);
}
}
if ($queryType === 'all' || $queryType === 'classifieds') {
if (gs_table_exists($mysqli, 'classifieds')) {
$sql = 'SELECT name, simname, parcelname FROM classifieds WHERE name LIKE ? OR description LIKE ? OR simname LIKE ? ORDER BY expirationdate DESC LIMIT 100';
$stmt = mysqli_prepare($mysqli, $sql);
if ($stmt !== false) {
mysqli_stmt_bind_param($stmt, 'sss', $needle, $needle, $needle);
mysqli_stmt_execute($stmt);
$res = mysqli_stmt_get_result($stmt);
if ($res !== false) {
while ($r = mysqli_fetch_assoc($res)) {
$rows[] = array(
'type' => 'Classifieds',
'name' => (string) ($r['name'] ?? ''),
'extra' => trim((string) ($r['simname'] ?? '') . ' / ' . (string) ($r['parcelname'] ?? ''), ' /'),
);
}
mysqli_free_result($res);
}
mysqli_stmt_close($stmt);
}
} elseif ($queryType === 'classifieds') {
$warnings[] = 'Classifieds table not found in this database.';
}
}
}
mysqli_close($mysqli);
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Search</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Grid Search</title>
<style>
:root {
color-scheme: dark;
--bg: #151515;
--bg-top: #2a2a2a;
--panel: #1f1f1f;
--panel-border: #3d3d3d;
--text: #ececec;
--muted: #b5b5b5;
--input-bg: #242424;
--input-border: #505050;
--th-bg: #2d2d2d;
--row-border: #3a3a3a;
--accent: #7a7a7a;
--accent-2: #5e5e5e;
}
<!--
Datei search.php
body {
font-family: Arial, sans-serif;
margin: 1rem;
background: linear-gradient(180deg, var(--bg-top) 0%, var(--bg) 42%);
color: var(--text);
}
Ich möchte das so umbauen das in unterschiedlichen schriftarten folgendes angezeigt wird:
A search function is already integrated; select People, Groups, Places, Land Sales, Events or Classifieds from the options above.
Eine Suchfunktion ist bereits integriert; wählen Sie oben Leute, Gruppen, Orte, Land-Verkauf, Events oder Anzeigen aus.
Une fonction de recherche est déjà intégrée ; sélectionnez Personnes, Groupes, Lieux, Ventes de terrains, Événements ou Petites annonces parmi les options ci-dessus.
Ya viene integrada una función de búsqueda; seleccione Personas, Grupos, Lugares, Ventas de terrenos, Eventos o Clasificados de las opciones anteriores.
.card {
background: var(--panel);
border: 1px solid var(--panel-border);
border-radius: 8px;
padding: 1rem;
width: 100%;
max-width: 600px;
margin: 0 auto;
margin: 0 auto;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.35);
}
Der Optionaler Button soll die Server IP ermitteln und verwenden.
-->
.toolbar {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
margin-bottom: 1rem;
}
<!-- W3.CSS -->
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
input,
select,
button {
padding: 0.45rem 0.55rem;
border: 1px solid var(--input-border);
border-radius: 6px;
background: var(--input-bg);
color: var(--text);
}
<!-- Font Awesome 4 (oder 5/6, je nach Bedarf) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
input::placeholder {
color: var(--muted);
}
<style>
body, html {
height: 100%;
margin: 0;
background: #050b1a;
color: #fff;
font-family: "Segoe UI", Arial, sans-serif;
}
.bg-grid {
background-image: linear-gradient(#1b2740 1px, transparent 1px),
linear-gradient(90deg, #1b2740 1px, transparent 1px);
background-size: 40px 40px;
}
.holo-text {
text-shadow: 0 0 10px #6cf, 0 0 20px #6cf;
letter-spacing: 4px;
}
.port-text {
font-size: 14px;
color: #aaa;
}
</style>
button {
background: linear-gradient(180deg, var(--accent) 0%, var(--accent-2) 100%);
color: #ffffff;
border-color: #6c6c6c;
cursor: pointer;
}
button:hover {
filter: brightness(1.06);
}
input:focus,
select:focus,
button:focus {
outline: 2px solid rgba(170, 170, 170, 0.35);
outline-offset: 1px;
}
table {
width: 100%;
border-collapse: collapse;
}
th,
td {
border-bottom: 1px solid var(--row-border);
text-align: left;
padding: 0.55rem;
font-size: 0.95rem;
}
th {
background: var(--th-bg);
}
tbody tr:nth-child(even) {
background: rgba(255, 255, 255, 0.02);
}
.muted {
color: var(--muted);
}
</style>
</head>
<body>
<div class="card">
<h2>Grid Search</h2>
<form method="get" class="toolbar">
<input type="text" name="q" value="<?php echo htmlspecialchars($queryText); ?>" placeholder="Search term..." required>
<select name="type">
<option value="all" <?php echo $queryType === 'all' ? 'selected' : ''; ?>>All</option>
<option value="people" <?php echo $queryType === 'people' ? 'selected' : ''; ?>>People</option>
<option value="groups" <?php echo $queryType === 'groups' ? 'selected' : ''; ?>>Groups</option>
<option value="places" <?php echo $queryType === 'places' ? 'selected' : ''; ?>>Places</option>
<option value="classifieds" <?php echo $queryType === 'classifieds' ? 'selected' : ''; ?>>Classifieds</option>
</select>
<button type="submit">Search</button>
</form>
<div class="w3-display-container w3-center bg-grid" style="height:100%;">
<div class="w3-display-middle">
<!-- Logo / Titelzeile -->
<div class="w3-margin-bottom">
<i class="fa fa-cube w3-text-green" style="font-size:48px;"></i>
<span class="w3-xlarge w3-margin-left">OpenSimulator</span>
<?php if ($queryText === ''): ?>
<p class="muted">Search parameter can be passed by viewer as `q`, `query`, `search` or `term`.</p>
<?php else: ?>
<p class="muted">Results: <?php echo count($rows); ?></p>
<?php foreach ($warnings as $warning): ?>
<p class="muted"><?php echo htmlspecialchars($warning); ?></p>
<?php endforeach; ?>
<table>
<thead>
<tr>
<th>Type</th>
<th>Name</th>
<th>Extra</th>
</tr>
</thead>
<tbody>
<?php if (empty($rows)): ?>
<tr><td colspan="3">No results found.</td></tr>
<?php else: ?>
<?php foreach ($rows as $row): ?>
<tr>
<td><?php echo htmlspecialchars($row['type']); ?></td>
<td><?php echo htmlspecialchars($row['name']); ?></td>
<td><?php echo htmlspecialchars($row['extra']); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<!-- Search Code -->
<div class="holo-text" style="font-size:80px; font-weight:bold;">
Search
</div>
<!-- Text darunter -->
<!-- Mehrsprachiger Hinweistext in unterschiedlichen Farben -->
<div class="w3-large w3-margin-top">
<div style="color: #e0f7fa; border-radius: 8px; margin-bottom: 8px; padding: 8px 12px;">
A search function is already integrated - select People, Groups, Places, Land Sales, Events or Classifieds from the options above.
</div>
<div style="color: #fce4ec; border-radius: 8px; margin-bottom: 8px; padding: 8px 12px;">
Eine Suchfunktion ist bereits integriert - wählen Sie oben Leute, Gruppen, Orte, Land-Verkauf, Events oder Anzeigen aus.
</div>
<div style="color: #fff3e0; border-radius: 8px; margin-bottom: 8px; padding: 8px 12px;">
Une fonction de recherche est déjà intégrée - sélectionnez Personnes, Groupes, Lieux, Ventes de terrains, Événements ou Petites annonces parmi les options ci-dessus.
</div>
<div style="color: #e8f5e9; border-radius: 8px; padding: 8px 12px;">
Ya viene integrada una función de búsqueda - seleccione Personas, Grupos, Lugares, Ventas de terrenos, Eventos o Clasificados de las opciones anteriores.
</div>
</div>
<!-- Icon-Leiste (Teleport / Server etc.) -->
<div class="w3-margin-top">
<i class="fa fa-server w3-xlarge w3-margin-right"></i>
<i class="fa fa-television w3-xlarge w3-margin-right"></i>
<i class="fa fa-location-arrow w3-xlarge"></i>
</div>
<!-- Optionaler Button: Server-IP ermitteln und verwenden -->
<!-- Optionaler Button -->
<div class="w3-margin-top">
<button onclick="redirectToIndexWithIP()" class="w3-button w3-border w3-round w3-hover-blue">
<i class="fa fa-home w3-margin-right"></i>
Go to homepage
</button>
</div>
<script>
function redirectToIndexWithIP() {
fetch('https://api.ipify.org?format=json')
.then(response => response.json())
.then(data => {
// IP als Query-Parameter an index.php anhängen
window.location.href = 'index.php?serverip=' + encodeURIComponent(data.ip);
})
.catch(() => {
document.getElementById('server-ip').textContent = 'IP konnte nicht ermittelt werden.';
});
}
</script>
</div>
</div>
</body>
</html>
+116
View File
@@ -0,0 +1,116 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Search</title>
<!--
Datei search.php
Ich möchte das so umbauen das in unterschiedlichen schriftarten folgendes angezeigt wird:
A search function is already integrated; select People, Groups, Places, Land Sales, Events or Classifieds from the options above.
Eine Suchfunktion ist bereits integriert; wählen Sie oben Leute, Gruppen, Orte, Land-Verkauf, Events oder Anzeigen aus.
Une fonction de recherche est déjà intégrée ; sélectionnez Personnes, Groupes, Lieux, Ventes de terrains, Événements ou Petites annonces parmi les options ci-dessus.
Ya viene integrada una función de búsqueda; seleccione Personas, Grupos, Lugares, Ventas de terrenos, Eventos o Clasificados de las opciones anteriores.
Der Optionaler Button soll die Server IP ermitteln und verwenden.
-->
<!-- W3.CSS -->
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<!-- Font Awesome 4 (oder 5/6, je nach Bedarf) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
body, html {
height: 100%;
margin: 0;
background: #050b1a;
color: #fff;
font-family: "Segoe UI", Arial, sans-serif;
}
.bg-grid {
background-image: linear-gradient(#1b2740 1px, transparent 1px),
linear-gradient(90deg, #1b2740 1px, transparent 1px);
background-size: 40px 40px;
}
.holo-text {
text-shadow: 0 0 10px #6cf, 0 0 20px #6cf;
letter-spacing: 4px;
}
.port-text {
font-size: 14px;
color: #aaa;
}
</style>
</head>
<body>
<div class="w3-display-container w3-center bg-grid" style="height:100%;">
<div class="w3-display-middle">
<!-- Logo / Titelzeile -->
<div class="w3-margin-bottom">
<i class="fa fa-cube w3-text-green" style="font-size:48px;"></i>
<span class="w3-xlarge w3-margin-left">OpenSimulator</span>
</div>
<!-- Search Code -->
<div class="holo-text" style="font-size:80px; font-weight:bold;">
Search
</div>
<!-- Text darunter -->
<!-- Mehrsprachiger Hinweistext in unterschiedlichen Farben -->
<div class="w3-large w3-margin-top">
<div style="color: #e0f7fa; border-radius: 8px; margin-bottom: 8px; padding: 8px 12px;">
A search function is already integrated - select People, Groups, Places, Land Sales, Events or Classifieds from the options above.
</div>
<div style="color: #fce4ec; border-radius: 8px; margin-bottom: 8px; padding: 8px 12px;">
Eine Suchfunktion ist bereits integriert - wählen Sie oben Leute, Gruppen, Orte, Land-Verkauf, Events oder Anzeigen aus.
</div>
<div style="color: #fff3e0; border-radius: 8px; margin-bottom: 8px; padding: 8px 12px;">
Une fonction de recherche est déjà intégrée - sélectionnez Personnes, Groupes, Lieux, Ventes de terrains, Événements ou Petites annonces parmi les options ci-dessus.
</div>
<div style="color: #e8f5e9; border-radius: 8px; padding: 8px 12px;">
Ya viene integrada una función de búsqueda - seleccione Personas, Grupos, Lugares, Ventas de terrenos, Eventos o Clasificados de las opciones anteriores.
</div>
</div>
<!-- Icon-Leiste (Teleport / Server etc.) -->
<div class="w3-margin-top">
<i class="fa fa-server w3-xlarge w3-margin-right"></i>
<i class="fa fa-television w3-xlarge w3-margin-right"></i>
<i class="fa fa-location-arrow w3-xlarge"></i>
</div>
<!-- Optionaler Button: Server-IP ermitteln und verwenden -->
<!-- Optionaler Button -->
<div class="w3-margin-top">
<button onclick="redirectToIndexWithIP()" class="w3-button w3-border w3-round w3-hover-blue">
<i class="fa fa-home w3-margin-right"></i>
Go to homepage
</button>
</div>
<script>
function redirectToIndexWithIP() {
fetch('https://api.ipify.org?format=json')
.then(response => response.json())
.then(data => {
// IP als Query-Parameter an index.php anhängen
window.location.href = 'index.php?serverip=' + encodeURIComponent(data.ip);
})
.catch(() => {
document.getElementById('server-ip').textContent = 'IP konnte nicht ermittelt werden.';
});
}
</script>
</div>
</div>
</body>
</html>