From 3d7867a0451feb7673b3caf8a073d1bc66af98ab Mon Sep 17 00:00:00 2001 From: Manfred Aabye Date: Sat, 7 Mar 2026 11:32:44 +0100 Subject: [PATCH] 070320261132 --- README.md | 8 +- message.php | 139 +++++++++++---- search.php | 413 ++++++++++++++++++++++++++++++++++----------- search.php.offline | 116 +++++++++++++ 4 files changed, 539 insertions(+), 137 deletions(-) create mode 100644 search.php.offline diff --git a/README.md b/README.md index d137772..1f62be7 100644 --- a/README.md +++ b/README.md @@ -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 --- - - - - - - - diff --git a/message.php b/message.php index a64e87f..8015825 100644 --- a/message.php +++ b/message.php @@ -1,35 +1,116 @@ "$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); -?> \ No newline at end of file +/** + * 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; \ No newline at end of file diff --git a/search.php b/search.php index 5e6d792..db75eab 100644 --- a/search.php +++ b/search.php @@ -1,116 +1,327 @@ + 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); +?> - - Search + + + Grid Search + + 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); + } + +
+

Grid Search

+
+ + + +
-
-
- - -
- - OpenSimulator + +

Search parameter can be passed by viewer as `q`, `query`, `search` or `term`.

+ +

Results:

+ +

+ + + + + + + + + + + + + + + + + + + + + + +
TypeNameExtra
No results found.
+
- - -
- Search -
- - - - -
-
- 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. -
-
- - -
- - - -
- - - -
- -
- - - -
-
- diff --git a/search.php.offline b/search.php.offline new file mode 100644 index 0000000..5e6d792 --- /dev/null +++ b/search.php.offline @@ -0,0 +1,116 @@ + + + + + Search + + + + + + + + + + + + + +
+
+ + +
+ + OpenSimulator +
+ + +
+ Search +
+ + + + +
+
+ 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. +
+
+ + +
+ + + +
+ + + +
+ +
+ + + +
+
+ + +