1
0
Fork 0

Compare commits

...

136 Commits

Author SHA1 Message Date
Anonymous Contributor 685853a9f9 Make daily cronjobs run at midnight 2023-09-15 19:20:06 +02:00
Anonymous Contributor 36ef1c5721 Add documentation 2023-09-15 19:19:45 +02:00
Anonymous Contributor 882c5ae372 Fix icon url in presession template 2023-09-15 19:03:26 +02:00
Anonymous Contributor e4e050908b Mention IAR password to user 2023-09-11 08:50:33 +02:00
Anonymous Contributor 2b5c00db1c Improve design of profile page 2023-09-11 08:31:21 +02:00
Anonymous Contributor 2f9caf8923 Show IAR creation state/download on profile page 2023-09-11 08:29:28 +02:00
Anonymous Contributor 939f600b3f Use curl for getDataFromHTTP utility function 2023-09-11 08:01:12 +02:00
Anonymous Contributor 8231fbb08c Fix DB query and stats parsing of RegionChecker 2023-09-11 07:29:02 +02:00
Anonymous Contributor eb5a850869 Small fixes 2023-09-11 07:05:21 +02:00
Anonymous Contributor ce41e107af Fix documentation in example config 2023-09-11 06:06:33 +02:00
Anonymous Contributor 204fcb96e1 Fix identities table not being migrated properly 2023-09-11 06:06:22 +02:00
Anonymous Contributor 891f1dff70 Add API endpoint to make IAR files downloadable 2023-09-11 06:05:50 +02:00
Anonymous Contributor 6a4c343b03 Optimize IarMonitor 2023-09-11 06:04:30 +02:00
Anonymous Contributor dbcb57f71f Add better explanations for the identity system 2023-09-11 04:26:06 +02:00
Anonymous Contributor 1a869b62c2 Create separate user profile for new identities 2023-09-11 03:13:59 +02:00
Anonymous Contributor 93d8aafd6f Add new cronjob for IAR request queue processing 2023-09-10 12:07:45 +02:00
Anonymous Contributor 2ed8285c93 Change structure of IAR table, add migrations 2023-09-10 12:00:49 +02:00
Anonymous Contributor a166e65421 Return JSON report for cron calls 2023-09-10 11:58:20 +02:00
Anonymous Contributor e6ba73430b Add simple OpenSimulator RestConsole client 2023-09-10 07:41:42 +02:00
Anonymous Contributor 80db5f35d1 Add internal cronjob for PHP session cleanup 2023-09-10 04:53:40 +02:00
Anonymous Contributor bb40c8c9a4 Port working cronjobs to new CronJob API 2023-09-10 04:53:13 +02:00
Anonymous Contributor a213a38b3c Add simple API and starter for cron jobs 2023-09-10 04:41:21 +02:00
Anonymous Contributor b18b960fb0 Use APCu (if available) to cache username lookups 2023-09-10 02:45:06 +02:00
Anonymous Contributor c416ec0992 Fix region deletion as admin 2023-09-10 01:31:26 +02:00
Anonymous Contributor 13495cd041 Fix control flow of registration RequestHandler 2023-09-10 01:30:11 +02:00
Anonymous Contributor 9e0267513b Add displayError utility function 2023-09-10 01:29:08 +02:00
Anonymous Contributor 8e20909f23 Fix leftover reference to RUNTIME in Profile.php 2023-09-09 06:31:23 +02:00
Anonymous Contributor f87fa5d5bb Fix some template variables 2023-09-09 06:30:42 +02:00
Anonymous Contributor 5274f306e3 Make sure argument of Util::fillString is string 2023-09-09 06:29:52 +02:00
Anonymous Contributor 9483889e55 Fix func. call with wrong signature in OpenSim.php 2023-09-09 06:28:51 +02:00
Anonymous Contributor 37ad81fb16 Rename own tables to be consistent and distinct 2023-09-09 06:27:20 +02:00
Anonymous Contributor f50053a745 Add migration feature for own MySQL tables 2023-09-09 06:21:30 +02:00
Anonymous Contributor 07f196fc51 Remove old temporary directory 2023-09-06 17:42:15 +02:00
Anonymous Contributor fc0412d50a Strip all tags from plaintext mail 2023-09-06 17:35:56 +02:00
Anonymous Contributor f9453d5dec Switch to INI-based configuration 2023-09-06 17:34:32 +02:00
Anonymous Contributor 598e55dd6f Fix text overflow in minimized navbar 2023-09-06 16:07:15 +02:00
Anonymous Contributor 7be78e387d Use Ubuntu font globally 2023-09-06 16:00:33 +02:00
Anonymous Contributor 79959cc696 Remove unused styles from login CSS 2023-09-06 15:46:02 +02:00
Anonymous Contributor 01eb373798 Remove unused classes from login's util.css 2023-09-06 01:56:24 +02:00
Anonymous Contributor 27fe2187b4 Improve font-family declarations in login CSS 2023-09-06 01:55:41 +02:00
Anonymous Contributor c7bf6f4d6e Fix minor errors in template HTML 2023-09-05 22:25:08 +02:00
Anonymous Contributor 3bc2fee80a Fix class name of reset-password handler 2023-09-05 22:23:56 +02:00
Anonymous Contributor 2e3124406a Adapt DiscordUtil to directory structure change 2023-09-05 22:00:18 +02:00
Anonymous Contributor ef9cb8831e Fix username not being included in profile page 2023-09-05 21:59:49 +02:00
Anonymous Contributor cbc065f3a4 Delete leftovers from old structure 2023-09-05 21:59:21 +02:00
Anonymous Contributor eca5aa0d92 Adapt templates to new asset dir structure 2023-09-05 21:59:03 +02:00
Anonymous Contributor 1ac6474194 Move other JS and data files to new dir structure 2023-09-05 20:51:41 +02:00
Anonymous Contributor a4b008f7ae Use Sass to compile one stylesheet per page type 2023-09-05 20:50:12 +02:00
Anonymous Contributor 6dae322a2a Add dep. management and Sass compilation with npm 2023-09-05 20:48:06 +02:00
Anonymous Contributor ea5c1ee4d7 Move Ubuntu font, only keeping one copy 2023-09-05 18:20:54 +02:00
Anonymous Contributor 16dcd2ab46 Move and update FontAwesome files 2023-09-05 18:19:26 +02:00
Anonymous Contributor 94d84ab935 Move favicon 2023-09-05 18:14:13 +02:00
Anonymous Contributor d33a89b355 Remove unused assets 2023-09-05 18:11:15 +02:00
Anonymous Contributor 9ebb54ce64 Change directory structure for images 2023-09-05 10:40:08 +02:00
Anonymous Contributor b68884f33c Implement API endpoints as RequestHandlers 2023-09-05 01:27:06 +02:00
Anonymous Contributor 6b96f8281c Adapt all templates to new templating system 2023-09-05 01:15:40 +02:00
Anonymous Contributor 1cc4746c6a Implement all existing pages as RequestHandlers 2023-09-05 01:12:48 +02:00
Anonymous Contributor 27899ce9c1 Fix/improve middleware classes 2023-09-05 01:09:59 +02:00
Anonymous Contributor 686e991266 Adapt existing classes to new structure and PSR-12 2023-09-05 01:09:01 +02:00
Anonymous Contributor cc240f47a2 Add templating system based on native PHP 2023-09-05 01:02:53 +02:00
Anonymous Contributor a80919fdce Add object-oriented bootstrap and request handling 2023-09-05 00:55:09 +02:00
Anonymous Contributor b163f4d764 Add middleware functionality 2023-08-29 13:55:12 +02:00
Anonymous Contributor 024a140609 Add simple PSR-4 autoloader 2023-08-29 13:53:32 +02:00
Anonymous Contributor 6c22684b2f Add optional API key for cron, rename entry point 2023-08-27 12:56:12 +02:00
Anonymous Contributor ea2fffa872 Add account and identity deletion to admin 2023-08-27 12:20:45 +02:00
Anonymous Contributor d6ca2e6e00 Move identity deletion into OpenSim function 2023-08-27 12:17:33 +02:00
Anonymous Contributor c5871a626b Add distinction of users and identities in admin 2023-08-27 11:59:43 +02:00
Anonymous Contributor 007e0ac4fb Add self-service user account deletion 2023-08-27 11:20:50 +02:00
Anonymous Contributor 48882cbb1b Add feature for deleting own identities 2023-08-27 08:32:40 +02:00
Anonymous Contributor 120fb3772e Fix formatting according to PSR-12 2023-08-27 05:31:32 +02:00
Anonymous Contributor 9a5182816f Add user-friendly invite code error messages 2023-08-23 18:16:36 +02:00
Anonymous Contributor bfeb4f5eef Fix potential access to empty region stats array 2023-08-23 18:16:36 +02:00
Anonymous Contributor 6f150ac55c Fix session variable assignment in login 2023-08-23 18:16:36 +02:00
Anonymous Contributor fb605bcf4a Fix typo 2023-08-23 18:16:36 +02:00
Anonymous Contributor 3b35e88a9f Fix includes in OfflineIM 2023-08-23 18:16:36 +02:00
Anonymous Contributor f19e186cd3 Let admins generate password reset links 2023-08-23 18:16:36 +02:00
Anonymous Contributor 0991d5a487 Improve login security and efficiency 2023-08-23 18:16:36 +02:00
Anonymous Contributor eda4c5a030 Finalize "forgot password" feature 2023-08-23 18:16:36 +02:00
Anonymous Contributor 1ee795a399 Improve routing 2023-08-23 18:16:36 +02:00
Anonymous Contributor 88e9c25bb0 Fix "forgot password" link 2023-08-23 18:16:36 +02:00
Anonymous Contributor 82157cad76 Add e-mail-based password reset feature 2023-08-23 18:16:36 +02:00
Anonymous Contributor 406cdcce31 Update PHPMailer 2023-08-23 18:16:36 +02:00
Anonymous Contributor 94e8b2e6af Improve login form error messages 2023-08-23 18:16:36 +02:00
Anonymous Contributor b400f0c4e5 Fix erroneous SQL queries (ignored pre-PHP 8) 2023-08-23 18:16:36 +02:00
Anonymous Contributor 463ab4abe0 Prevent invite codes being reusable in some cases 2023-08-23 18:16:36 +02:00
Anonymous Contributor 0140cf1a57 Revert password hashing for OpenSim compatibility 2023-08-23 18:16:36 +02:00
Anonymous Contributor 954794870e Enforce POSt for region removal, validate input 2023-08-23 18:16:36 +02:00
Anonymous Contributor 16affffa66 Display correct title on friends page 2023-08-23 18:16:36 +02:00
Anonymous Contributor 2670cf604e Change validation regexes to be more strict 2023-08-23 18:16:36 +02:00
Anonymous Contributor 879b1d8e3f Fix profile form validation 2023-08-23 18:16:36 +02:00
Anonymous Contributor 0a79d465ac Add include path to cron 2023-08-23 18:16:36 +02:00
Anonymous Contributor 0ecd9aed75 Optimize user group query 2023-08-23 18:16:36 +02:00
Anonymous Contributor 391d716200 Fix include path 2023-08-23 18:16:36 +02:00
Anonymous Contributor bf07367b0c Fix friend entries having GET removal links in some cases 2023-08-23 18:16:36 +02:00
Anonymous Contributor 5d6b6565cd Add minimum password length requirement 2023-08-23 18:16:36 +02:00
Anonymous Contributor 8946511d46 Fix viewer welcome page 2023-08-23 18:16:35 +02:00
Anonymous Contributor 143f8ef3ed Fix property references 2023-08-23 18:16:35 +02:00
Anonymous Contributor 29796584f7 Check if "required" parameter is set 2023-08-23 18:16:35 +02:00
Anonymous Contributor e036a009cb Adapt HTML to password form change 2023-08-23 18:16:35 +02:00
Anonymous Contributor 22bdf45b26 Fix profile page HTML 2023-08-23 18:16:35 +02:00
Anonymous Contributor 4dfb3d81c3 Enforce POST when sending register form 2023-08-23 18:16:35 +02:00
Anonymous Contributor d46835e8eb Check if new name is already taken 2023-08-23 18:16:35 +02:00
Anonymous Contributor 03f5cd489d Remove useless double check of input lengths 2023-08-23 18:16:35 +02:00
Anonymous Contributor 7b0539b96f Save IAR message state across requests 2023-08-23 18:16:35 +02:00
Anonymous Contributor 497dcb85a8 Remove unused allUsers variable and query 2023-08-23 18:16:35 +02:00
Anonymous Contributor c3106f4787 Actually merge profile and password change pages 2023-08-23 18:16:35 +02:00
Anonymous Contributor 4d20b7e2f3 Only check regex and equals when field is provided 2023-08-23 18:16:35 +02:00
Anonymous Contributor d70fd4357a Remove dead link from admin dashboard 2023-08-23 18:16:35 +02:00
Anonymous Contributor 6b88527f05 Properly check request method in login form 2023-08-23 18:16:35 +02:00
Anonymous Contributor 5d0a79f20f Fix password form regexes 2023-08-23 18:16:35 +02:00
Anonymous Contributor 3d0c156cb3 Fix OpenSim API being included too late in users 2023-08-23 18:16:35 +02:00
Anonymous Contributor 841f1707eb Enforce POST and validate input for profile forms 2023-08-23 18:16:35 +02:00
Anonymous Contributor f073fb621d Add validation regex for new identity's name 2023-08-23 18:16:35 +02:00
Anonymous Contributor e192d3fd04 Fix POST request handling in dashboard forms 2023-08-23 18:16:35 +02:00
Anonymous Contributor 1df2182bae Use POST for password changes, validate input 2023-08-23 18:16:35 +02:00
Anonymous Contributor c97c58e30d Fix HTML format 2023-08-23 18:16:35 +02:00
Anonymous Contributor e2795e99b9 Do not use PHP's error control operator 2023-08-23 18:16:35 +02:00
Anonymous Contributor 9954f31721 Fix typos 2023-08-23 18:16:35 +02:00
Anonymous Contributor 57ff06d418 Generate a random string as invite code 2023-08-23 18:16:35 +02:00
Anonymous Contributor 8c7a31d88a Use POST for user management, validate input 2023-08-23 18:16:35 +02:00
Anonymous Contributor 6ca8988128 Use POST for managing identities, validate input 2023-08-23 18:16:35 +02:00
Anonymous Contributor 9d760f7dc3 Use POST for leaving groups, validate input 2023-08-23 18:16:35 +02:00
Anonymous Contributor e6d51a0afb Use POST when removing friends, validate input 2023-08-23 18:16:35 +02:00
Anonymous Contributor 87c21a06eb Fix incorrect regex escaping 2023-08-23 18:16:35 +02:00
Anonymous Contributor 0a6b06fb29 Fix property reference 2023-08-23 18:16:35 +02:00
Anonymous Contributor 2e7abe5bd5 Remove unneeded file 2023-08-23 18:16:35 +02:00
Anonymous Contributor d3f3ca5779 Fix include/template paths 2023-08-23 18:16:35 +02:00
Anonymous Contributor 17fe6651c8 Reflect directory structure changes 2023-08-23 18:16:35 +02:00
Anonymous Contributor f9828aa110 Move templates to template directory 2023-08-23 18:16:35 +02:00
Anonymous Contributor 4415adb6e6 Reflect directory structure changes 2023-08-23 18:16:35 +02:00
Anonymous Contributor da225d7213 Do not include unused discord class 2023-08-23 18:16:35 +02:00
Anonymous Contributor 08f29758c0 Only include and construct OpenSim when needed 2023-08-23 18:16:35 +02:00
Anonymous Contributor c9cad23e2c Only include PHPMailer when needed 2023-08-23 18:16:35 +02:00
Anonymous Contributor 59c9e53f1e Rename PHPMailer directory 2023-08-23 18:16:35 +02:00
Anonymous Contributor 45a33c2b9f Remove unused GoogleAuthenticator class 2023-08-23 18:16:35 +02:00
Anonymous Contributor 11c8fa3471 Change dir structure of class files 2023-08-23 18:16:34 +02:00
2042 changed files with 7131 additions and 195094 deletions

3
.gitignore vendored
View File

@ -1 +1,2 @@
config.php
config.php
node_modules

View File

@ -1,2 +1,41 @@
# OpenSim-Gridverwaltung
# MCP: OpenSim-Gridverwaltung
Das MCP ist ein PHP-Webinterface für Benutzer und Administratoren von OpenSimulator-Grids. Es ermöglicht Benutzern die Registrierung (auf Einladung) und Verwaltung des eigenen OpenSimulator-Accounts im Self-Service-Verfahren. Administratoren können Accounts und Regionen einfacher verwalten.
## Installation
Voraussetzung ist, dass die Datenbankstruktur eines OpenSimulator-Grids bereits existiert. Das MCP muss vor der Nutzung mit den Zugangsdaten derselben Datenbank konfiguriert werden. Eigene Tabellen des MCP besitzen zur Vermeidung von Konflikten den Präfix `mcp_`.
Folgende PHP-Erweiterungen werden benötigt:
1. php-curl
2. php-mysql (PDO)
3. php-xml
4. php-xmlrpc
Für bessere Performance kann optional `php-apcu` installiert werden.
Die Installation läuft folgendermaßen ab:
1. Gewünschtes Release als ZIP/TAR-Archiv oder per `git clone` herunterladen
2. Verzeichnisse `app`, `data`, `lib`, `public` und `templates` in das Verzeichnis des Webservers entpacken
3. Öffentliche Stammverzeichnis des Webservers (Apache: `DocumentRoot`, nginx: `root`) auf Pfad zum Verzeichnis `public` ändern
4. Index des Webservers auf index.php ändern, falls erforderlich
5. Beispielkonfiguration `config.example.ini` anpassen, in `config.ini` umbenennen und in das Verzeichnis der in Schritt 2 entpackten Verzeichnisse verschieben
## Aktualisierung
Zur Aktualisierung müssen lediglich die Verzeichnisse `app`, `lib`, `public` und `templates`, sowie der Inhalt von `data` (außer `iars`) ersetzt werden.
Die Migration der Datenbankstruktur erfolgt automatisch. Möglicherweise erforderliche Änderungen an der Konfiguration sind den Release-Informationen zu entnehmen.
## Entwickler
Die Abhängigkeiten des Frontends werden über [npm](https://www.npmjs.com/) verwaltet. Alle erforderlichen Pakete können nach dem Download des Repository über `npm install` heruntergeladen werden.
In `package.json` ist zudem ein Buildprozess (Befehl: `npm build`) definiert, durch den die Sass-Stylesheets im Verzeichnis `scss` kompiliert und optimiert und zusammen mit Schriftarten und Skripts im öffentlichen Webverzeichnis abgelegt werden.
Das Backend besitzt kein Dependency Management. Die einzige Abhängigkeit, [PHPMailer](https://github.com/PHPMailer/PHPMailer), wurde manuell in das Repository eingefügt. Der Autoloader sucht im Verzeichnis `lib` nach solchen externen Klassen.
### Verarbeitung von Anfragen
Das Skript `index.php` wird bei allen Anfragen aufgerufen. Die angeforderte Route wird als GET-Parameter `<Gruppe>=<Name>` übermittelt. Gruppen (momentan `api` und `page`), zugehörige Seiten und die assoziierte `RequestHandler`-Subklasse sind in `Mcp.php` definiert.
Ist zu einer Anfrage eine Route definiert, wird der zugehörige `RequestHandler` erzeugt. Ist eine `Middleware`-Klasse für diesen definiert, ist die weitere Verarbeitung von dem Rückgabewert von `Middleware::canAccess()` abhängig.
Schließlich wird je nach Methode der Anfrage `RequestHandler::get()` bzw. `RequestHandler::post()` aufgerufen.

View File

@ -1 +0,0 @@
Deny from all

View File

@ -1,110 +0,0 @@
<?php
# Copyright (c)Melanie Thielker and Teravus Ovares (http://opensimulator.org/)
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the OpenSim Project nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# updated for Robust installations: BlueWall 2011
# further minor changes by justincc (http://justincc.org)
# Tables
$presence = "Presence";
# XMLRPC
$xmlrpc_server = xmlrpc_server_create();
xmlrpc_server_register_method($xmlrpc_server, "preflightBuyLandPrep", "buy_land_prep");
function validate_user($agent_id, $s_session_id)
{
$stmt = $RUNTIME['PDO']->prepare("SELECT UserID FROM Presence WHERE UserID=? AND SecureSessionID = ?");
$stmt->execute(array($agent_id, $s_session_id));
if($stmt->rowCount() == 0) {
return false;
}
$res = $stmt->fetch();
return $res['UserID'];
}
function buy_land_prep($method_name, $params, $app_data)
{
$confirmvalue = "";
$req = $params[0];
$agentid = $req['agentId'];
$sessionid = $req['secureSessionId'];
$amount = $req['currencyBuy'];
$billableArea = $req['billableArea'];
$ID = validate_user($agentid, $sessionid);
if($ID)
{
$membership_levels = array(
'levels' => array(
'id' => "00000000-0000-0000-0000-000000000000",
'description' => "some level"));
$landUse = array(
'upgrade' => False,
'action' => "".SYSURL."");
$currency = array(
'estimatedCost' => "200.00"); // convert_to_real($amount));
$membership = array(
'upgrade' => False,
'action' => "".SYSURL."",
'levels' => $membership_levels);
$response_xml = xmlrpc_encode(array(
'success' => True,
'currency' => $currency,
'membership' => $membership,
'landUse' => $landUse,
'currency' => $currency,
'confirm' => $confirmvalue));
header("Content-type: text/xml");
print $response_xml;
}
else
{
header("Content-type: text/xml");
$response_xml = xmlrpc_encode(array(
'success' => False,
'errorMessage' => "\n\nUnable to Authenticate\n\nClick URL for more info.",
'errorURI' => "".SYSURL.""));
print $response_xml;
}
return "";
}
$request_xml = file_get_contents('php://input');
xmlrpc_server_call_method($xmlrpc_server, $request_xml, '');
xmlrpc_server_destroy($xmlrpc_server);
?>

View File

@ -1,29 +0,0 @@
<?php
$membership_levels = array(
'levels' => array(
'id' => "00000000-0000-0000-0000-000000000000",
'description' => "some level"));
$landUse = array(
'upgrade' => False,
'action' => "");
$currency = array(
'estimatedCost' => "200.00"); // convert_to_real($amount));
$membership = array(
'upgrade' => False,
'action' => "",
'levels' => $membership_levels);
$response_xml = xmlrpc_encode(array(
'success' => True,
'currency' => $currency,
'membership' => $membership,
'landUse' => $landUse,
'currency' => $currency,
'confirm' => "200.00"));
header("Content-type: text/xml");
print $response_xml;
?>

View File

@ -1,5 +0,0 @@
1148b04d-7a93-19e9-b3c9-ea1cdeec38f7
1148b04d-7a93-29e9-b3c9-ea1cdeec38f7
1148b04d-7a93-39e9-b3c9-ea1cdeec38f7
1148b04d-7a93-49e9-b3c9-ea1cdeec38f7
1148b04d-7a93-59e9-b3c9-ea1cdeec38f7

View File

@ -1,34 +0,0 @@
<html>
<head>
<meta http-equiv="refresh" content="15">
</head>
<body style="background-image: url('./style/images/fabric-pattern.png')">
<?php
$statement = $RUNTIME['PDO']->prepare("SELECT UserID,RegionID FROM Presence WHERE RegionID != '00000000-0000-0000-0000-000000000000' ORDER BY RegionID ASC");
$statement->execute();
if($statement->rowCount() == 0)
{
echo "<h1>Es ist niemand online!</h1>";
}else{
echo '<table style="width:350px;margin-left:auto;margin-right:auto;margin-top:25px"><tr><th align="left" style="background-color: #FF8000;">Name</th><th align="left" style="background-color: #FF8000;">Region</th></tr>';
$entryColor = TRUE;
while($row = $statement->fetch())
{
if($entryColor == TRUE)
$entry = '<tr style="background-color: #F2F2F2;"><td>'.trim($RUNTIME['OPENSIM']->getUserName($row['UserID'])).'</td><td>'.$RUNTIME['OPENSIM']->getRegionName($row['RegionID']).'</td></tr>';
if($entryColor == FALSE)
$entry = '<tr style="background-color: #E6E6E6;"><td>'.trim($RUNTIME['OPENSIM']->getUserName($row['UserID'])).'</td><td>'.$RUNTIME['OPENSIM']->getRegionName($row['RegionID']).'</td></tr>';
echo $entry;
$entryColor = !$entryColor;
}
echo '</table>';
}
?>
</body>
</html>

View File

@ -1,34 +0,0 @@
<?php
$HTML = new HTML();
$HTML->setHTMLTitle("Spalsh");
$HTML->importHTML("style/viewerWelcomeSite/default.html");
$IMAGES = array();
if ($handle = opendir('./data/viewerWelcomeImages'))
{
while (false !== ($entry = readdir($handle)))
{
if ($entry != "." && $entry != "..")
{
$IMAGES = array_merge($IMAGES, array("./data/viewerWelcomeImages/".$entry));
}
}
closedir($handle);
}
shuffle($IMAGES);
$HTML->importSeitenInhalt("pages/HTML/viewerWelcomeImages.html");
$HTML->ReplaceSeitenInhalt("%%JSONIMAGEARRAY%%", json_encode($IMAGES));
$HTML->ReplaceSeitenInhalt("%%GRIDNAME%%", $RUNTIME['GRID']['NAME']);
$HTML->ReplaceSeitenInhalt("%%SHOWNEWS%%", $RUNTIME['GRID']['MAIN_NEWS']);
$HTML->ReplaceSeitenInhalt("%%SHOWSTATS%%", "Registrierte User: ".$RUNTIME['OPENSIM']->getUserCount()."<br>Regionen: ".$RUNTIME['OPENSIM']->getRegionCount()."<br>Aktuell Online: ".($RUNTIME['OPENSIM']->getOnlineCount()-1));
$HTML->build();
echo $HTML->ausgabe();
?>

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Mcp;
use PDO;
interface ConnectionProvider
{
public function db(): PDO;
}

40
app/FormValidator.php Normal file
View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Mcp;
class FormValidator {
private array $fieldValidation;
public function __construct(array $fieldValidation)
{
$this->fieldValidation = $fieldValidation;
}
public function isValid(array $req): bool
{
if (!isset($req['csrf']) || $req['csrf'] !== $_SESSION['csrf']) {
return false;
}
foreach ($this->fieldValidation as $field => $params) {
if (isset($req[$field]) && strlen(trim($req[$field])) > 0) {
if (isset($params['regex'])) {
if (!preg_match($params['regex'], $req[$field])) {
return false;
}
}
elseif (isset($params['equals']) && $params['equals'] !== $req[$field]) {
return false;
}
}
elseif (isset($params['required']) && $params['required']) {
return false;
}
}
return true;
}
}

149
app/Mcp.php Normal file
View File

@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
namespace Mcp;
use Exception;
use PDO;
class Mcp implements ConnectionProvider
{
private ?PDO $db = null;
private array $config;
private string $dataDir;
private string $templateDir;
const ROUTES = [
'api' => [
'economy' => 'Api\\Economy',
'economylandtool' => 'Api\\EconomyLandTool',
'economylandtool.php' => 'Api\\EconomyLandTool',
'getAccessList' => 'Api\\GetAccessList',
'onlineDisplay' => 'Api\\OnlineDisplay',
'viewerWelcomeSite' => 'Api\\ViewerWelcomePage',
'runCron' => 'Api\\CronStarter',
'downloadIar' => 'Api\\DownloadIar'
],
'page' => [
'dashboard' => 'Page\\Dashboard',
'forgot' => 'Page\\ForgotPassword',
'friends' => 'Page\\Friends',
'groups' => 'Page\\Groups',
'identities' => 'Page\\Identities',
'login' => 'Page\\Login',
'profile' => 'Page\\Profile',
'regions' => 'Page\\Regions',
'register' => 'Page\\Register',
'reset-password' => 'Page\\ResetPassword',
'user-online-state' => 'Page\\OnlineUsers',
'users' => 'Page\\ManageUsers'
]
];
public function __construct($basedir)
{
$this->templateDir = $basedir.DIRECTORY_SEPARATOR.'templates';
$this->dataDir = $basedir.DIRECTORY_SEPARATOR.'data';
$this->config = array();
try {
$config = parse_ini_file($basedir.DIRECTORY_SEPARATOR.'config.ini', true);
foreach ($config['general'] as $key => $val) {
$this->config[$key] = $val;
}
unset($config['general']);
$this->config = array_merge($config, $this->config);
} catch (Exception $e) {
error_log('Could not load config, aborting. Error: '.$e->getMessage());
http_response_code(503);
exit();
}
$migrate = new MigrationManager($basedir.DIRECTORY_SEPARATOR.'data'.DIRECTORY_SEPARATOR.'migrate_ver');
if (!$migrate->isMigrated() && !$migrate->migrate($this->db())) {
error_log('Migration to latest DB structure version failed, aborting.');
http_response_code(503);
exit();
}
}
/**
* Connects to the MySQL database (if not done already) and returns the connection.
*/
public function db(): PDO
{
if ($this->db == null) {
$this->db = new PDO('mysql:host='.$this->config['mysql']['host'].';dbname='.$this->config['mysql']['db'],
$this->config['mysql']['user'],
$this->config['mysql']['password']);
}
return $this->db;
}
/**
* Returns the value associated with the specified key in this config, as either a string, an integer or an array.
* Keys are lower-cased for compatibility reasons.
*
* If there is no entry with this key, an empty array is returned.
*/
public function config($key): string|array|int
{
$realKey = strtolower($key);
return isset($this->config[$realKey]) ? $this->config[$realKey] : array();
}
/**
* Returns a hidden form field with the current CSRF token set.
*/
public function csrfField(): string
{
return '<input type="hidden" name="csrf" value="'.(isset($_SESSION['csrf']) ? $_SESSION['csrf'] : '').'">';
}
/**
* Creates a TemplateBuilder instance for the specified template file, setting some basic variables.
*/
public function template($name): TemplateBuilder
{
return (new TemplateBuilder($this->templateDir, $name))->vars([
'domain' => $this->config['domain'],
'title' => 'MCP',
'admin' => isset($_SESSION['LEVEL']) && $_SESSION['LEVEL'] > 100
])->unsafeVar('csrf', $this->csrfField());
}
/**
* Returns the path of the data/ directory, mostly used for dynamically created assets.
*/
public function getDataDir(): string
{
return $this->dataDir;
}
public function handleRequest()
{
$reqClass = 'Mcp\\Page\\Error';
if (empty($_GET)) {
$reqClass = 'Mcp\\'.$this::ROUTES['page'][array_key_first($this::ROUTES['page'])];
} else {
if (isset($_GET['logout'])) {
session_start();
session_destroy();
header('Location: /');
return;
}
foreach ($this::ROUTES as $type => $routes) {
if (isset($_GET[$type])) {
if (strlen($_GET[$type]) <= 100 && preg_match('/^[0-9a-zA-Z-_.]+$/', $_GET[$type]) && isset($routes[$_GET[$type]])) {
$reqClass = 'Mcp\\'.$routes[$_GET[$type]];
}
break;
}
}
}
(new $reqClass($this))->handleRequest();
}
}

126
app/MigrationManager.php Normal file
View File

@ -0,0 +1,126 @@
<?php
declare(strict_types=1);
namespace Mcp;
use Exception;
use PDO;
class MigrationManager
{
private const INIT = [
'CREATE TABLE IF NOT EXISTS `mcp_user_identities` (`PrincipalID` CHAR(36) NOT NULL, `IdentityID` CHAR(36) NOT NULL, PRIMARY KEY (`PrincipalID`, `IdentityID`)) ENGINE=MyISAM CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci',
'CREATE TABLE IF NOT EXISTS `mcp_password_reset` (`PrincipalID` CHAR(36) NOT NULL, `Token` CHAR(32) NOT NULL, `RequestTime` BIGINT NOT NULL, PRIMARY KEY(`PrincipalID`), UNIQUE(`Token`)) ENGINE MyISAM DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci',
'CREATE TABLE IF NOT EXISTS `mcp_invites` (`InviteCode` CHAR(64) NOT NULL, PRIMARY KEY (`InviteCode`)) ENGINE InnoDB',
'CREATE TABLE IF NOT EXISTS `mcp_offlineim_send` (`id` int(6) NOT NULL DEFAULT 0) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci',
'CREATE TABLE IF NOT EXISTS `mcp_regions_info` (`regionID` CHAR(36) NOT NULL COLLATE utf8_unicode_ci, `RegionVersion` VARCHAR(128) NOT NULL DEFAULT "" COLLATE utf8_unicode_ci, `ProcMem` INT(11) NOT NULL, `Prims` INT(11) NOT NULL, `SimFPS` INT(11) NOT NULL, `PhyFPS` INT(11) NOT NULL, `OfflineTimer` INT(11) NOT NULL DEFAULT 0, PRIMARY KEY (`regionID`) USING BTREE) COLLATE=utf8_unicode_ci ENGINE=InnoDB',
'CREATE TABLE IF NOT EXISTS `mcp_cron_runs` (`Name` VARCHAR(50) NOT NULL, `LastRun` INT(11) UNSIGNED NOT NULL, PRIMARY KEY(`Name`)) ENGINE InnoDB',
'CREATE TABLE IF NOT EXISTS `mcp_iar_state` (`userID` CHAR(36) NOT NULL COLLATE utf8_unicode_ci, `filesize` BIGINT(20) NOT NULL DEFAULT 0, `iarfilename` VARCHAR(64) NOT NULL COLLATE utf8_unicode_ci, `state` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, `created` INT(11) UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`userID`) USING BTREE) COLLATE=utf8_unicode_ci ENGINE=InnoDB',
'CREATE TRIGGER IF NOT EXISTS del_id_trig AFTER DELETE ON UserAccounts FOR EACH ROW DELETE FROM mcp_user_identities WHERE mcp_user_identities.PrincipalID = OLD.PrincipalID OR mcp_user_identities.IdentityID = OLD.PrincipalID',
'CREATE TRIGGER IF NOT EXISTS del_pwres_trig AFTER DELETE ON UserAccounts FOR EACH ROW DELETE FROM mcp_password_reset WHERE mcp_password_reset.PrincipalID = OLD.PrincipalID'
];
private const MIGRATIONS = [
1 => [
'RENAME TABLE IF EXISTS UserIdentitys TO mcp_user_identities, PasswordResetTokens TO mcp_password_reset, InviteCodes TO mcp_invites, im_offline_send TO mcp_offlineim_send, regions_info TO mcp_regions_info',
'ALTER TABLE mcp_invites MODIFY COLUMN InviteCode CHAR(64) NOT NULL',
'ALTER TABLE mcp_regions_info MODIFY COLUMN regionID CHAR(36), MODIFY COLUMN ProcMem INT(11) UNSIGNED NOT NULL, MODIFY COLUMN Prims INT(11) UNSIGNED NOT NULL, MODIFY COLUMN SimFPS FLOAT NOT NULL, MODIFY COLUMN PhyFPS FLOAT NOT NULL, MODIFY COLUMN OfflineTimer BIGINT UNSIGNED NOT NULL DEFAULT 0',
'ALTER TABLE mcp_user_identities MODIFY COLUMN PrincipalID CHAR(36) COLLATE utf8mb3_general_ci, MODIFY COLUMN IdentityID CHAR(36) COLLATE utf8mb3_general_ci',
'CREATE TRIGGER IF NOT EXISTS del_id_trig AFTER DELETE ON UserAccounts FOR EACH ROW DELETE FROM mcp_user_identities WHERE mcp_user_identities.PrincipalID = OLD.PrincipalID OR mcp_user_identities.IdentityID = OLD.PrincipalID',
'CREATE TRIGGER IF NOT EXISTS del_pwres_trig AFTER DELETE ON UserAccounts FOR EACH ROW DELETE FROM mcp_password_reset WHERE mcp_password_reset.PrincipalID = OLD.PrincipalID'
],
2 => [
'CREATE TABLE IF NOT EXISTS `mcp_cron_runs` (`Name` VARCHAR(50) NOT NULL, `LastRun` INT(11) UNSIGNED NOT NULL, PRIMARY KEY(`Name`)) ENGINE InnoDB'
],
3 => [
'RENAME TABLE IF EXISTS iarstates TO mcp_iar_state',
'ALTER TABLE mcp_iar_state MODIFY COLUMN userID CHAR(36) NOT NULL COLLATE utf8_unicode_ci, DROP COLUMN running, ADD COLUMN `state` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 AFTER iarfilename, ADD COLUMN created INT(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `state`'
]
];
private const MIGRATE_VERSION_CURRENT = 4;
private int $migrateVersion;
private string $migrateVersionFile;
public function __construct(string $migrateVersionFile)
{
$this->migrateVersionFile = $migrateVersionFile;
$this->migrateVersion = 0;
$apcu = false;
if ($this->apcuAvailable()) {
$this->migrateVersion = apcu_exists('mcp_migrate_version') ? apcu_fetch('mcp_migrate_version') : 0;
$apcu = true;
}
if ($this->migrateVersion == 0 && file_exists($migrateVersionFile)) {
$migrateVer = file_get_contents($migrateVersionFile);
if (preg_match('/^\d+$/', $migrateVer)) {
$this->migrateVersion = intval($migrateVer);
if ($apcu) {
apcu_store('mcp_migrate_version', $this->migrateVersion);
}
}
}
}
public function isMigrated(): bool
{
return $this->migrateVersion == $this::MIGRATE_VERSION_CURRENT;
}
public function migrate(PDO $db): bool
{
if ($this->migrateVersion == 0) {
// MCP < v2.x.x might be initialized without migration data
$checkLegacy = $db->prepare('SHOW TABLES LIKE ?');
$checkLegacy->execute(['UserIdentitys']);
if ($checkLegacy->rowCount() > 0) {
$this->migrateVersion = 1;
}
else {
return $this->runStatements($db, $this::INIT) && $this->updateVersion();
}
}
for ($ver = $this->migrateVersion; $ver <= count($this::MIGRATIONS); $ver++) {
if (!isset($this::MIGRATIONS[$ver])) {
break;
}
if (!$this->runStatements($db, $this::MIGRATIONS[$ver])) {
return false;
}
}
$this->updateVersion();
return true;
}
private function updateVersion(): bool
{
file_put_contents($this->migrateVersionFile, $this::MIGRATE_VERSION_CURRENT);
if ($this->apcuAvailable()) {
apcu_store('mcp_migrate_version', $this::MIGRATE_VERSION_CURRENT);
}
return true;
}
private function runStatements(PDO $db, array $stmts): bool
{
try {
foreach ($stmts as $stmt) {
$db->exec($stmt);
}
return true;
} catch (Exception $e) {
error_log('Could not execute statements: '.$e->getMessage()."\n".$e->getTraceAsString());
return false;
}
}
private function apcuAvailable(): bool
{
return function_exists('apcu_fetch') && apcu_enabled();
}
}

280
app/OpenSim.php Normal file
View File

@ -0,0 +1,280 @@
<?php
declare(strict_types=1);
namespace Mcp;
use Exception;
use PDO;
class OpenSim
{
private PDO $pdo;
private bool $apcu;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
$this->apcu = function_exists('apcu_fetch') && apcu_enabled();
}
private function getUserNameFromGridData($userID, $table, $row): ?string
{
$statementGridUser = $this->pdo->prepare('SELECT '.$row.' FROM '.$table.' WHERE '.$row.' LIKE ?');
$statementGridUser->execute(array($userID.';%'));
while ($rowGridUser = $statementGridUser->fetch()) {
$userData = explode(";", $rowGridUser[$row]);
if (count($userData) >= 3) {
$dbUserID = $userData[0];
$dbUserName = $userData[2];
if ($dbUserID == $userID) {
if ($this->apcu) {
apcu_store('os_username_'.$userID, $dbUserName, 600);
}
return $dbUserName;
}
}
}
return null;
}
public function getUserName($userID): string
{
if ($userID == "00000000-0000-0000-0000-000000000000") {
return "Unknown User";
}
if ($this->apcu && apcu_exists('os_username_'.$userID)) {
return apcu_fetch('os_username_'.$userID);
}
$statementUser = $this->pdo->prepare('SELECT FirstName,LastName FROM UserAccounts WHERE PrincipalID = ?');
$statementUser->execute(array($userID));
if ($rowUser = $statementUser->fetch()) {
$name = $rowUser['FirstName'].' '.$rowUser['LastName'];
if ($this->apcu) {
apcu_store('os_username_'.$userID, $name, 300);
}
return $name;
}
$res = $this->getUserNameFromGridData($userID, 'GridUser', 'UserID');
if ($res == null) {
$res = $this->getUserNameFromGridData($userID, 'Friends', 'PrincipalID');
}
return $res == null ? "Unknown User" : $res;
}
public function getUserUUID($userName): ?string
{
$statementUser = $this->pdo->prepare('SELECT PrincipalID,FirstName,LastName FROM UserAccounts WHERE FirstName = ? AND LastName = ?');
$statementUser->execute(explode(' ', $userName));
if ($rowUser = $statementUser->fetch()) {
return $rowUser['PrincipalID'];
}
return null;
}
public function getRegionName($regionID): string
{
$statementRegion = $this->pdo->prepare("SELECT regionName FROM regions WHERE uuid = ?");
$statementRegion->execute(array($regionID));
if ($rowRegion = $statementRegion->fetch()) {
return $rowRegion['regionName'];
}
return "Unknown Region";
}
public function getPartner($userID): string
{
$statement = $this->pdo->prepare("SELECT profilePartner FROM userprofile WHERE useruuid = ?");
$statement->execute(array($userID));
while ($row = $statement->fetch()) {
if ($row['profilePartner'] != "00000000-0000-0000-0000-000000000000") {
return $row['profilePartner'];
}
}
return '';
}
public function allowOfflineIM($userID): string
{
$statement = $this->pdo->prepare("SELECT imviaemail FROM usersettings WHERE useruuid = ?");
$statement->execute(array($userID));
if ($row = $statement->fetch()) {
return strtoupper($row['imviaemail']);
}
return "FALSE";
}
public function getUserMail($userID): string
{
$statement = $this->pdo->prepare("SELECT Email FROM UserAccounts WHERE PrincipalID = ?");
$statement->execute(array($userID));
if ($row = $statement->fetch()) {
return $row['Email'];
}
return "";
}
private function getEntryCount($table): int
{
$statementCount = $this->pdo->prepare('SELECT COUNT(*) AS Count FROM '.$table);
$statementCount->execute();
if ($row = $statementCount->fetch()) {
return $row['Count'];
}
return 0;
}
public function getUserCount(): int
{
return $this->getEntryCount('UserAccounts');
}
public function getRegionCount(): int
{
return $this->getEntryCount('regions');
}
public function getOnlineCount(): int
{
return $this->getEntryCount('Presence');
}
public function deleteUser($uuid): bool
{
try {
$this->pdo->beginTransaction();
$statementAuth = $this->pdo->prepare('DELETE FROM auth WHERE UUID = ?');
$statementAuth->execute([$uuid]);
$statementAgentPrefs = $this->pdo->prepare('DELETE FROM AgentPrefs WHERE PrincipalID = ?');
$statementAgentPrefs->execute([$uuid]);
$statementAvatars = $this->pdo->prepare('DELETE FROM Avatars WHERE PrincipalID = ?');
$statementAvatars->execute([$uuid]);
$statementGridUser = $this->pdo->prepare('DELETE FROM GridUser WHERE UserID = ?');
$statementGridUser->execute([$uuid]);
$statementEstateUser = $this->pdo->prepare('DELETE FROM estate_users WHERE uuid = ?');
$statementEstateUser->execute([$uuid]);
$statementEstateBan = $this->pdo->prepare('DELETE FROM estateban WHERE bannedUUID = ?');
$statementEstateBan->execute([$uuid]);
$statementHgTraveling = $this->pdo->prepare('DELETE FROM hg_traveling_data WHERE UserID = ?');
$statementHgTraveling->execute([$uuid]);
$statementUserIdentitys = $this->pdo->prepare('DELETE FROM mcp_user_identities WHERE PrincipalID = ?');
$statementUserIdentitys->execute([$uuid]);
$statementFriends = $this->pdo->prepare('DELETE FROM Friends WHERE PrincipalID = ? OR Friend = ?');
$statementFriends->execute([$uuid, $uuid]);
$statementImOffline = $this->pdo->prepare('DELETE FROM im_offline WHERE PrincipalID = ?');
$statementImOffline->execute([$uuid]);
$statementInventoryFolders = $this->pdo->prepare('DELETE FROM inventoryfolders WHERE agentID = ?');
$statementInventoryFolders->execute([$uuid]);
$statementInventoryItems = $this->pdo->prepare('DELETE FROM inventoryitems WHERE avatarID = ?');
$statementInventoryItems->execute([$uuid]);
$statementGroupMembership = $this->pdo->prepare('DELETE FROM os_groups_membership WHERE PrincipalID = ?');
$statementGroupMembership->execute([$uuid]);
$statementGroupRoles = $this->pdo->prepare('DELETE FROM os_groups_rolemembership WHERE PrincipalID = ?');
$statementGroupRoles->execute([$uuid]);
$statementGroupRoles = $this->pdo->prepare('DELETE FROM Presence WHERE UserID = ?');
$statementGroupRoles->execute([$uuid]);
$statementMute = $this->pdo->prepare('DELETE FROM MuteList WHERE AgentID = ? OR MuteID = ?');
$statementMute->execute([$uuid, $uuid]);
$statementUserAccounts = $this->pdo->prepare('DELETE FROM UserAccounts WHERE PrincipalID = ?');
$statementUserAccounts->execute([$uuid]);
$statementUserData = $this->pdo->prepare('DELETE FROM userdata WHERE UserId = ?');
$statementUserData->execute([$uuid]);
$statementUserNotes = $this->pdo->prepare('DELETE FROM usernotes WHERE targetuuid = ?');
$statementUserNotes->execute([$uuid]);
$statementUserProfile = $this->pdo->prepare('DELETE FROM userprofile WHERE useruuid = ?');
$statementUserProfile->execute([$uuid]);
$statementUserSettings = $this->pdo->prepare('DELETE FROM usersettings WHERE useruuid = ?');
$statementUserSettings->execute([$uuid]);
$this->pdo->commit();
return true;
} catch (Exception $pdoException) {
$this->pdo->rollBack();
error_log('Could not delete account '.$uuid.': '.$pdoException->getMessage());
return false;
}
}
public function deleteIdentity($uuid, $identId): bool
{
$statementValidate = $this->pdo->prepare('SELECT 1 FROM mcp_user_identities WHERE PrincipalID = ? AND IdentityID = ?');
$statementValidate->execute([$uuid, $identId]);
if($statementValidate->fetch()) {
$statementDelete = $this->pdo->prepare('DELETE FROM UserAccounts WHERE PrincipalID = ?');
$statementDelete->execute([$identId]);
$statementUserProfile = $this->pdo->prepare('DELETE FROM userprofile WHERE useruuid = ?');
$statementUserProfile->execute([$identId]);
return true;
}
return false;
}
public function generateUuid(): string
{
return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
// 32 bits for "time_low"
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
// 16 bits for "time_mid"
mt_rand( 0, 0xffff ),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
mt_rand( 0, 0x0fff ) | 0x4000,
// 16 bits, 8 bits for "clk_seq_hi_res",
// 8 bits for "clk_seq_low",
// two most significant bits holds zero and one for variant DCE1.1
mt_rand( 0, 0x3fff ) | 0x8000,
// 48 bits for "node"
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
);
}
}

54
app/RequestHandler.php Normal file
View File

@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Mcp;
use Exception;
use Mcp\Middleware\Middleware;
abstract class RequestHandler
{
protected Mcp $app;
private ?Middleware $middleware;
public function __construct(Mcp $app, Middleware $mw = null)
{
$this->app = $app;
$this->middleware = $mw;
}
public function handleRequest(): void
{
if ($_SERVER['REQUEST_METHOD'] != 'GET' && $_SERVER['REQUEST_METHOD'] != 'POST') {
http_response_code(400);
exit();
}
if ($this->middleware != null) {
try {
if (!$this->middleware->canAccess()) {
$this->middleware->handleUnauthorized();
exit();
}
} catch (Exception $e) {
error_log("Middleware handling raised an exception: " + $e->getMessage());
http_response_code(500);
exit();
}
}
$_SERVER['REQUEST_METHOD'] == 'GET' ? $this->get() : $this->post();
}
public function get(): void
{
http_response_code(405);
}
public function post(): void
{
http_response_code(405);
}
}

78
app/TemplateBuilder.php Normal file
View File

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace Mcp;
use Mcp\Util\TemplateVarArray;
class TemplateBuilder
{
private string $basedir;
private string $name;
private ?string $parent = null;
private array $vars = [];
public function __construct(string $basedir, string $name)
{
$this->basedir = $basedir;
$this->name = $name;
}
/**
* Sets another template to be the "parent" of this one.
*
* The template specified in this TemplateBuilder's constructor will be included into the parent.
*/
public function parent(string $parent): TemplateBuilder
{
$this->parent = $parent;
return $this;
}
/**
* Sets multiple variables after escaping them.
*/
public function vars(array $vars): TemplateBuilder
{
foreach ($vars as $key => $val) {
$this->vars[$key] = htmlspecialchars(strval($val));
}
return $this;
}
/**
* Sets the specified variable for this template, after escaping it.
*/
public function var(string $key, string $val): TemplateBuilder
{
$this->vars[$key] = htmlspecialchars($val);
return $this;
}
/**
* Sets the specified variable for this template WITHOUT escaping it.
*
* User input included this way has to be manually sanitized before.
*/
public function unsafeVar(string $key, string $val): TemplateBuilder
{
$this->vars[$key] = $val;
return $this;
}
/**
* Displays the template(s) with the current set of variables.
*/
public function render(): void
{
$v = new TemplateVarArray($this->vars);
$basepath = $this->basedir.DIRECTORY_SEPARATOR;
if ($this->parent == null) {
require $basepath.$this->name;
} else {
$v['child-template'] = $basepath.$this->name;
require $basepath.$this->parent;
}
}
}

50
app/api/CronStarter.php Normal file
View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Mcp\Api;
use Mcp\RequestHandler;
class CronStarter extends RequestHandler
{
private const CRONJOBS_INTERNAL = ['SessionCleanup'];
public function get(): void
{
if ($this->app->config('cron-restriction') == 'key' && !(isset($_GET['key']) && hash_equals($this->app->config('cron-key'), $_GET['key']))) {
http_response_code(403);
return;
}
$cronJobs = array_merge($this->app->config('cronjobs'), $this::CRONJOBS_INTERNAL);
$cronStatement = $this->app->db()->prepare('SELECT Name,LastRun FROM mcp_cron_runs');
$cronStatement->execute();
$jobRuns = array();
while ($row = $cronStatement->fetch()) {
$jobRuns[$row['Name']] = $row['LastRun'];
}
$resArray = [];
$cronUpdateStatement = $this->app->db()->prepare('REPLACE INTO mcp_cron_runs(Name,LastRun) VALUES (?,?)');
foreach ($cronJobs as $jobName) {
$jobClass = "Mcp\\Cron\\".$jobName;
if (in_array($jobName, $cronJobs)) {
$job = (new $jobClass($this->app));
$now = time();
$nextRun = $job->getNextRun(isset($jobRuns[$jobName]) ? $jobRuns[$jobName] : $now - 60);
if ($now >= $nextRun && $job->run()) {
$cronUpdateStatement->execute([$jobName, time()]);
$resArray[$jobName] = ['result' => 'ok', 'nextRun' => $job->getNextRun(time())];
}
else {
$resArray[$jobName] = ['result' => 'failed'];
}
}
}
echo json_encode($resArray);
}
}

24
app/api/DownloadIar.php Normal file
View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Mcp\Api;
class DownloadIar extends \Mcp\RequestHandler
{
public function get(): void
{
if (preg_match('/^[a-f0-9]{32}$/', $_GET['id'])) {
$path = $this->app->getDataDir().DIRECTORY_SEPARATOR.'iars'.DIRECTORY_SEPARATOR.$_GET['id'].'.iar';
if (file_exists($path)) {
header('Content-Type: '.mime_content_type($path));
header('Content-Disposition: attachment; filename='.$_GET['id'].'.iar');
header('Content-Length: '.filesize($path));
readfile($path);
return;
}
}
http_response_code(404);
}
}

119
app/api/Economy.php Normal file
View File

@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
namespace Mcp\Api;
class Economy extends \Mcp\RequestHandler
{
<