Compare commits
22 Commits
release/2.
...
master
Author | SHA1 | Date |
---|---|---|
Anonymous Contributor | 685853a9f9 | |
Anonymous Contributor | 36ef1c5721 | |
Anonymous Contributor | 882c5ae372 | |
Anonymous Contributor | e4e050908b | |
Anonymous Contributor | 2b5c00db1c | |
Anonymous Contributor | 2f9caf8923 | |
Anonymous Contributor | 939f600b3f | |
Anonymous Contributor | 8231fbb08c | |
Anonymous Contributor | eb5a850869 | |
Anonymous Contributor | ce41e107af | |
Anonymous Contributor | 204fcb96e1 | |
Anonymous Contributor | 891f1dff70 | |
Anonymous Contributor | 6a4c343b03 | |
Anonymous Contributor | dbcb57f71f | |
Anonymous Contributor | 1a869b62c2 | |
Anonymous Contributor | 93d8aafd6f | |
Anonymous Contributor | 2ed8285c93 | |
Anonymous Contributor | a166e65421 | |
Anonymous Contributor | e6ba73430b | |
Anonymous Contributor | 80db5f35d1 | |
Anonymous Contributor | bb40c8c9a4 | |
Anonymous Contributor | a213a38b3c |
41
README.md
41
README.md
|
@ -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.
|
31
app/Mcp.php
31
app/Mcp.php
|
@ -11,6 +11,7 @@ class Mcp implements ConnectionProvider
|
|||
|
||||
private ?PDO $db = null;
|
||||
private array $config;
|
||||
private string $dataDir;
|
||||
private string $templateDir;
|
||||
|
||||
const ROUTES = [
|
||||
|
@ -21,7 +22,8 @@ class Mcp implements ConnectionProvider
|
|||
'getAccessList' => 'Api\\GetAccessList',
|
||||
'onlineDisplay' => 'Api\\OnlineDisplay',
|
||||
'viewerWelcomeSite' => 'Api\\ViewerWelcomePage',
|
||||
'runCron' => 'Api\\CronStarter'
|
||||
'runCron' => 'Api\\CronStarter',
|
||||
'downloadIar' => 'Api\\DownloadIar'
|
||||
],
|
||||
'page' => [
|
||||
'dashboard' => 'Page\\Dashboard',
|
||||
|
@ -42,6 +44,7 @@ class Mcp implements ConnectionProvider
|
|||
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);
|
||||
|
@ -64,6 +67,9 @@ class Mcp implements ConnectionProvider
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the MySQL database (if not done already) and returns the connection.
|
||||
*/
|
||||
public function db(): PDO
|
||||
{
|
||||
if ($this->db == null) {
|
||||
|
@ -75,16 +81,29 @@ class Mcp implements ConnectionProvider
|
|||
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
|
||||
{
|
||||
return $this->config[strtolower($key)];
|
||||
$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([
|
||||
|
@ -94,6 +113,14 @@ class Mcp implements ConnectionProvider
|
|||
])->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';
|
||||
|
|
|
@ -14,6 +14,8 @@ class MigrationManager
|
|||
'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'
|
||||
];
|
||||
|
@ -23,12 +25,20 @@ class MigrationManager
|
|||
'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 = 2;
|
||||
private const MIGRATE_VERSION_CURRENT = 4;
|
||||
|
||||
private int $migrateVersion;
|
||||
private string $migrateVersionFile;
|
||||
|
|
|
@ -246,6 +246,9 @@ class OpenSim
|
|||
$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;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,12 +19,20 @@ class TemplateBuilder
|
|||
$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) {
|
||||
|
@ -33,18 +41,29 @@ class TemplateBuilder
|
|||
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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mcp\Cron;
|
||||
|
||||
class AssetChecker extends CronJob
|
||||
{
|
||||
|
||||
public function __construct(\Mcp\Mcp $app)
|
||||
{
|
||||
parent::__construct($app, Frequency::MONTHLY);
|
||||
}
|
||||
|
||||
public function run(): bool
|
||||
{
|
||||
$statement = $this->app->db()->prepare("SELECT id,hash FROM fsassets ORDER BY create_time DESC");
|
||||
$statement->execute();
|
||||
|
||||
$count = 0;
|
||||
|
||||
while ($row = $statement->fetch()) {
|
||||
$fileNameParts = array();
|
||||
$fileNameParts[0] = substr($row['hash'], 0, 2);
|
||||
$fileNameParts[1] = substr($row['hash'], 2, 2);
|
||||
$fileNameParts[2] = substr($row['hash'], 4, 2);
|
||||
$fileNameParts[3] = substr($row['hash'], 6, 4);
|
||||
$fileNameParts[4] = $row['hash'] . ".gz";
|
||||
|
||||
$fileNameParts['UUID'] = $row['id'];
|
||||
$fileNameParts['FilePath'] = "/data/assets/base/" . $fileNameParts[0] . "/" . $fileNameParts[1] . "/" . $fileNameParts[2] . "/" . $fileNameParts[3] . "/" . $fileNameParts[4];
|
||||
|
||||
if (file_exists($fileNameParts['FilePath'])) {
|
||||
$filesize = filesize($fileNameParts['FilePath']);
|
||||
if ($filesize === false) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
$filesize = 0;
|
||||
}
|
||||
|
||||
$fileNameParts['FileSize'] = $filesize;
|
||||
$fileNameParts['Count'] = $count++;
|
||||
|
||||
if ($fileNameParts['FileSize'] == 0) {
|
||||
$add = $this->app->db()->prepare('DELETE FROM fsassets WHERE hash = :fileHash');
|
||||
$add->execute(['fileHash' => $row['hash']]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mcp\Cron;
|
||||
|
||||
class CheckInventory extends CronJob
|
||||
{
|
||||
public function __construct(\Mcp\Mcp $app)
|
||||
{
|
||||
parent::__construct($app, Frequency::MONTHLY);
|
||||
}
|
||||
|
||||
public function run(): bool
|
||||
{
|
||||
$invCheckStatement = $this->app->db()->prepare("UPDATE inventoryitems i SET i.inventoryName = concat('[DEFEKT] ', i.inventoryName)
|
||||
WHERE i.assetID IN (
|
||||
SELECT i.assetID FROM inventoryitems i WHERE
|
||||
NOT EXISTS(
|
||||
SELECT * FROM fsassets fs WHERE fs.id = i.assetID)
|
||||
AND NOT i.inventoryName LIKE '[DEFEKT] %' AND i.assetType <> 24
|
||||
)");
|
||||
|
||||
$invCheckStatement->execute();
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mcp\Cron;
|
||||
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use Mcp\Mcp;
|
||||
|
||||
abstract class CronJob {
|
||||
|
||||
protected Mcp $app;
|
||||
private Frequency $freq;
|
||||
|
||||
public function __construct(Mcp $app, Frequency $freq)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->freq = $freq;
|
||||
}
|
||||
|
||||
public function getNextRun(int $lastRun)
|
||||
{
|
||||
$prevDate = getdate($lastRun);
|
||||
$res = new DateTime('@'.$lastRun);
|
||||
switch($this->freq) {
|
||||
case Frequency::EACH_MINUTE:
|
||||
$res->add(DateInterval::createFromDateString('1 minute'));
|
||||
break;
|
||||
case Frequency::HOURLY:
|
||||
$res->add(DateInterval::createFromDateString('1 hour'));
|
||||
break;
|
||||
case Frequency::DAILY:
|
||||
$res->add(DateInterval::createFromDateString('1 day'));
|
||||
$res->setTime(0, 0, 0);
|
||||
break;
|
||||
case Frequency::WEEKLY:
|
||||
$res->add(DateInterval::createFromDateString('1 week'));
|
||||
break;
|
||||
case Frequency::MONTHLY:
|
||||
$res->setDate($prevDate['year'] + ($prevDate['mon'] == 12 ? 1 : 0), $prevDate['mon'] == 12 ? 1 : $prevDate['mon'] + 1, 1);
|
||||
break;
|
||||
case Frequency::YEARLY:
|
||||
$res->setDate($prevDate['year'] + 1, 1, 1);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
return $res->getTimestamp();
|
||||
}
|
||||
|
||||
abstract public function run(): bool;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mcp\Cron;
|
||||
|
||||
enum Frequency
|
||||
{
|
||||
case YEARLY; // 01.01. of each year
|
||||
case MONTHLY; // 1st of each month
|
||||
case WEEKLY; // 1 week after last run
|
||||
case DAILY; // Next day after last run, at 00:00
|
||||
case HOURLY; // One hour after last run
|
||||
case EACH_MINUTE; // One minute after last run
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mcp\Cron;
|
||||
|
||||
use Mcp\OpenSim;
|
||||
use Mcp\Opensim\RestConsole;
|
||||
use Mcp\Util\Util;
|
||||
|
||||
class IarMonitor extends CronJob
|
||||
{
|
||||
|
||||
private ?RestConsole $console = null;
|
||||
private bool $consoleAvailable = true;
|
||||
|
||||
public function __construct(\Mcp\Mcp $app)
|
||||
{
|
||||
parent::__construct($app, Frequency::EACH_MINUTE);
|
||||
}
|
||||
|
||||
public function run(): bool
|
||||
{
|
||||
$opensim = new OpenSim($this->app->db());
|
||||
|
||||
$dirPath = $this->app->getDataDir().DIRECTORY_SEPARATOR.'iars';
|
||||
if (!is_dir($dirPath)) {
|
||||
mkdir($dirPath);
|
||||
}
|
||||
|
||||
$statement = $this->app->db()->prepare("SELECT userID,iarfilename,filesize,state FROM mcp_iar_state WHERE state < ?");
|
||||
$statement->execute([2]);
|
||||
|
||||
if ($statement->rowCount() > 0) {
|
||||
while ($row = $statement->fetch()) {
|
||||
if ($row['state'] == 0) { // 0 - Request to OS pending
|
||||
if ($this->console() === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = explode(' ', $opensim->getUserName($row['userID']));
|
||||
if ($this->console()->sendCommand('save iar '.$name[0].' '.$name[1].' /* password '.$this->app->config('iarfetcher')['os-iar-path'].$row['iarfilename'])) {
|
||||
$statementUpdate = $this->app->db()->prepare('UPDATE mcp_iar_state SET state = ? WHERE userID = ?');
|
||||
$statementUpdate->execute([1, $row['userID']]);
|
||||
}
|
||||
} elseif ($row['state'] == 1) { // 1 - IAR Creation in progress
|
||||
$fullFilePath = $dirPath.DIRECTORY_SEPARATOR.$row['iarfilename'];
|
||||
if (file_exists($fullFilePath)) {
|
||||
$filesize = filesize($fullFilePath);
|
||||
|
||||
if ($filesize != $row['filesize']) {
|
||||
$statementUpdate = $this->app->db()->prepare('UPDATE mcp_iar_state SET filesize = ? WHERE userID = ?');
|
||||
$statementUpdate->execute([$filesize, $row['userID']]);
|
||||
} else {
|
||||
$statementUpdate = $this->app->db()->prepare('UPDATE mcp_iar_state SET state = ?, created = ? WHERE userID = ?');
|
||||
$statementUpdate->execute([2, time(), $row['userID']]);
|
||||
|
||||
Util::sendInworldIM("00000000-0000-0000-0000-000000000000", $row['userID'], "Inventory", $this->app->config('grid')['homeurl'], "Deine IAR ist fertig zum Download: https://".$this->app->config('domain').'/index.php?api=downloadIar&id='.substr($row['iarfilename'], 0, strlen($row['iarfilename']) - 4).' , sie ist mit dem Passwort "password" geschützt.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->console != null) {
|
||||
$this->console->closeSession();
|
||||
}
|
||||
}
|
||||
|
||||
// 2 - IAR creation finished; delete if expired
|
||||
$weekOld = time() - 604800;
|
||||
$statementExpired = $this->app->db()->prepare('SELECT userID,iarfilename FROM mcp_iar_state WHERE state = ? AND created < ?');
|
||||
$statementExpired->execute([2, $weekOld]);
|
||||
$statementDeleteExpired = $this->app->db()->prepare('DELETE FROM mcp_iar_state WHERE state = ? AND userID = ?');
|
||||
while ($row = $statementExpired->fetch()) {
|
||||
$fullFilePath = $dirPath.DIRECTORY_SEPARATOR.$row['iarfilename'];
|
||||
if (file_exists($fullFilePath) && unlink($fullFilePath)) {
|
||||
$statementDeleteExpired->execute([2, $row['userID']]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function console(): RestConsole|bool
|
||||
{
|
||||
if (!$this->consoleAvailable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->console == null) {
|
||||
$restCfg = $this->app->config('iarfetcher');
|
||||
$console = new RestConsole($restCfg['host'], intval($restCfg['port']));
|
||||
if ($console->startSession($restCfg['user'], $restCfg['password'])) {
|
||||
$this->console = $console;
|
||||
}
|
||||
else {
|
||||
$this->consoleAvailable = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->console;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mcp\Cron;
|
||||
|
||||
use Mcp\OpenSim;
|
||||
use Mcp\Util\SmtpClient;
|
||||
|
||||
use SimpleXMLElement;
|
||||
|
||||
class OfflineIm extends CronJob
|
||||
{
|
||||
private const IM_TYPE = array(
|
||||
"0" => "eine Nachricht",
|
||||
"3" => "eine Gruppeneinladung",
|
||||
"4" => "ein Inventaritem",
|
||||
"5" => "eine Bestätigung zur Annahme von Inventar",
|
||||
"6" => "eine Information zur Ablehnung von Inventar",
|
||||
"7" => "eine Aufforderung zur Gruppenwahl",
|
||||
"9" => "ein Inventaritem von einem Script",
|
||||
"19" => "eine Nachricht von einem Script",
|
||||
"32" => "eine Gruppennachricht",
|
||||
"38" => "eine Freundschaftsanfrage",
|
||||
"39" => "eine Bestätigung über die Annahme der Freundschaft",
|
||||
"40" => "eine Information über das Ablehnen der Freundschaft"
|
||||
);
|
||||
|
||||
public function __construct(\Mcp\Mcp $app)
|
||||
{
|
||||
parent::__construct($app, Frequency::EACH_MINUTE);
|
||||
}
|
||||
|
||||
public function run(): bool
|
||||
{
|
||||
$statement = $this->app->db()->prepare("SELECT ID,PrincipalID,Message FROM im_offline");
|
||||
$statement->execute();
|
||||
|
||||
while ($row = $statement->fetch()) {
|
||||
$opensim = new OpenSim($this->app->db());
|
||||
|
||||
$email = $opensim->getUserMail($row['PrincipalID']);
|
||||
$allowOfflineIM = $opensim->allowOfflineIM($row['PrincipalID']);
|
||||
|
||||
if ($email != "" && $allowOfflineIM == "TRUE" && !$this->isMailAlreadySent($row['ID'])) {
|
||||
$statementSend = $this->app->db()->prepare('INSERT INTO mcp_offlineim_send (id) VALUES (:idnummer)');
|
||||
$statementSend->execute(['idnummer' => $row['ID']]);
|
||||
|
||||
$mailcfg = $this->app->config('smtp');
|
||||
$smtpClient = new SmtpClient($mailcfg['host'], intval($mailcfg['port']), $mailcfg['address'], $mailcfg['password']);
|
||||
|
||||
$xmlMessage = new SimpleXMLElement($row['Message']);
|
||||
|
||||
$imType = $this::IM_TYPE["" . $xmlMessage->dialog . ""];
|
||||
$htmlMessage = "Du hast " . $imType . " in " . $this->app->config('grid')['name'] . " bekommen. <br><p><ul><li>" . htmlspecialchars($xmlMessage->message) . "</li></ul></p>Gesendet von: ";
|
||||
|
||||
if (isset($xmlMessage->fromAgentName)) {
|
||||
$htmlMessage .= $xmlMessage->fromAgentName;
|
||||
}
|
||||
|
||||
if (isset($xmlMessage->RegionID) && isset($xmlMessage->Position)) {
|
||||
if ($xmlMessage->Position->X != 0 || $xmlMessage->Position->Y != 0 || $xmlMessage->Position->Z != 0) { //TODO
|
||||
$htmlMessage .= " @ " . $opensim->getRegionName($xmlMessage->RegionID) . "/" . $xmlMessage->Position->X . "/" . $xmlMessage->Position->Y . "/" . $xmlMessage->Position->Z;
|
||||
} else {
|
||||
$htmlMessage .= " @ " . $opensim->getRegionName($xmlMessage->RegionID);
|
||||
}
|
||||
}
|
||||
|
||||
$tpl = $this->app->template('mail.php')->vars([
|
||||
'title' => substr($imType, strpos($imType, ' '))
|
||||
])->unsafeVar('message', $htmlMessage);
|
||||
$smtpClient->sendHtml($mailcfg['address'], $mailcfg['name'], $email, "Du hast " . $imType . " in " . $this->app->config('grid')['name'] . ".", $tpl);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private function isMailAlreadySent($id): bool
|
||||
{
|
||||
$statement = $this->app->db()->prepare("SELECT 1 FROM mcp_offlineim_send WHERE id = ? LIMIT 1");
|
||||
$statement->execute(array($id));
|
||||
|
||||
if ($statement->rowCount() != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mcp\Cron;
|
||||
|
||||
use Mcp\OpenSim;
|
||||
use Mcp\Util\Util;
|
||||
|
||||
class RegionChecker extends CronJob
|
||||
{
|
||||
public function __construct(\Mcp\Mcp $app)
|
||||
{
|
||||
parent::__construct($app, Frequency::DAILY);
|
||||
}
|
||||
|
||||
public function run(): bool
|
||||
{
|
||||
$statement = $this->app->db()->prepare("SELECT uuid,regionName,owner_uuid,serverURI,OfflineTimer FROM regions LEFT JOIN mcp_regions_info ON regions.uuid = mcp_regions_info.regionID COLLATE utf8mb3_unicode_ci");
|
||||
$statement->execute();
|
||||
|
||||
while ($row = $statement->fetch()) {
|
||||
$curl = curl_init($row['serverURI'] . 'jsonSimStats');
|
||||
curl_setopt($curl, CURLOPT_TIMEOUT, 15);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
$result = curl_exec($curl);
|
||||
|
||||
if ($result === false || strlen($result) == 0) {
|
||||
if ($row['OfflineTimer'] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$opensim = new OpenSim($this->app->db());
|
||||
|
||||
$longOffline = ($row['OfflineTimer'] + 3600) <= time();
|
||||
echo "Die Region " . $row['regionName'] . " von " . $opensim->getUserName($row['owner_uuid']) . " ist " . ($longOffline ? "seit über einer Stunde" : "") . " nicht erreichbar.\n"; //TODO: Increase to 1-3 months
|
||||
|
||||
if ($longOffline) {
|
||||
if ($this->app->config('grid')['delete-inactive-regions']) {
|
||||
Util::sendInworldIM("00000000-0000-0000-0000-000000000000", $row['owner_uuid'], "Region", $this->app->config('grid')['homeurl'], "WARNUNG: Deine Region '" . $row['regionName'] . "' ist nicht erreichbar und wurde deshalb aus dem Grid entfernt.");
|
||||
|
||||
$statementUpdate = $this->app->db()->prepare('DELETE FROM regions WHERE uuid = :uuid');
|
||||
$statementUpdate->execute(['uuid' => $row['uuid']]);
|
||||
}
|
||||
$statementRemoveStats = $this->app->db()->prepare('DELETE FROM mcp_regions_info WHERE regionID = :uuid');
|
||||
$statementRemoveStats->execute(['uuid' => $row['uuid']]);
|
||||
} elseif ($this->app->config('grid')['warn-inactive-regions']) {
|
||||
Util::sendInworldIM("00000000-0000-0000-0000-000000000000", $row['owner_uuid'], "Region", $this->app->config('grid')['homeurl'], "WARNUNG: Deine Region '" . $row['regionName'] . "' ist nicht erreichbar!");
|
||||
}
|
||||
} else {
|
||||
$regionData = json_decode($result);
|
||||
|
||||
$statementAccounts = $this->app->db()->prepare('REPLACE INTO `mcp_regions_info` (`regionID`, `RegionVersion`, `ProcMem`, `Prims`, `SimFPS`, `PhyFPS`, `OfflineTimer`) VALUES (:regionID, :RegionVersion, :ProcMem, :Prims, :SimFPS, :PhyFPS, :OfflineTimer)');
|
||||
$statementAccounts->execute(['regionID' => $row['uuid'], 'RegionVersion' => $regionData->Version, 'ProcMem' => intval(str_replace(',', '', $regionData->ProcMem)), 'Prims' => $regionData->Prims, 'SimFPS' => $regionData->SimFPS, 'PhyFPS' => $regionData->PhyFPS, 'OfflineTimer' => time()]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mcp\Cron;
|
||||
|
||||
class SessionCleanup extends CronJob
|
||||
{
|
||||
public function __construct(\Mcp\Mcp $app)
|
||||
{
|
||||
parent::__construct($app, Frequency::HOURLY);
|
||||
}
|
||||
|
||||
public function run(): bool
|
||||
{
|
||||
error_log("Session GC");
|
||||
session_start();
|
||||
session_gc();
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -3,8 +3,20 @@ declare(strict_types=1);
|
|||
|
||||
namespace Mcp\Middleware;
|
||||
|
||||
/**
|
||||
* Middleware implementations can be registered for a RequestHandler.
|
||||
*
|
||||
* If this is done, request processing continues only if Middleware::canAccess() returns true.
|
||||
*/
|
||||
interface Middleware
|
||||
{
|
||||
/**
|
||||
* Returns true if the request should be processed, i.e. if the client has permissionn to perform this request.
|
||||
*/
|
||||
public function canAccess(): bool;
|
||||
|
||||
/**
|
||||
* Called when canAcces() returns false, e.g. to redirect unauthorized users.
|
||||
*/
|
||||
public function handleUnauthorized(): void;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mcp\Opensim;
|
||||
|
||||
use XMLReader;
|
||||
|
||||
class RestConsole
|
||||
{
|
||||
private string $baseUrl;
|
||||
private ?string $sessionId;
|
||||
|
||||
public function __construct(string $host, int $port)
|
||||
{
|
||||
$this->baseUrl = "http://".$host.':'.$port;
|
||||
}
|
||||
|
||||
public function startSession(string $username, string $password): bool
|
||||
{
|
||||
$response = $this->sendRequest('/StartSession/', [
|
||||
'USER' => $username,
|
||||
'PASS' => $password
|
||||
]);
|
||||
|
||||
if ($this->detectError($response, '/StartSession/')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->sessionId = $response['SessionID'];
|
||||
$this->readResponses();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function closeSession(): void
|
||||
{
|
||||
$response = $this->sendRequest('/CloseSession/', [
|
||||
'ID' => $this->sessionId
|
||||
]);
|
||||
|
||||
$this->detectError($response, '/CloseSession/');
|
||||
}
|
||||
|
||||
public function readResponses(): array
|
||||
{
|
||||
if ($this->sessionId == null) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$response = $this->sendRequest('/ReadResponses/'.$this->sessionId);
|
||||
if ($this->detectError($response, '/ReadResponses/')) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return $response['Line'];
|
||||
}
|
||||
|
||||
public function sendCommand(string $command): bool
|
||||
{
|
||||
$response = $this->sendRequest('/SessionCommand/', [
|
||||
'ID' => $this->sessionId,
|
||||
'COMMAND' => $command
|
||||
]);
|
||||
|
||||
if ($this->detectError($response, '/SessionCommand/')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $response['Result'] == 'OK';
|
||||
}
|
||||
|
||||
private function detectError(array|int $response, string $request): bool
|
||||
{
|
||||
if (gettype($response) == 'integer') {
|
||||
error_log('OS RestConsole request '.$this->baseUrl.$request.' failed, status: '.$response);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function sendRequest(string $request, array $data = array()): array|int
|
||||
{
|
||||
$curl = curl_init($this->baseUrl.$request);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($curl, CURLOPT_POST, true);
|
||||
curl_setopt($curl, CURLOPT_USERAGENT, 'mcp-restconsole/0.0.1');
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, [
|
||||
'Accept' => 'text/xml'
|
||||
]);
|
||||
|
||||
$postData = '';
|
||||
foreach ($data as $key => $val) {
|
||||
$postData = $postData.(strlen($postData) > 0 ? '&' : '').$key.'='.$val;
|
||||
}
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, $postData);
|
||||
|
||||
$res = curl_exec($curl);
|
||||
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
if ($status != 200) {
|
||||
return $status;
|
||||
}
|
||||
|
||||
|
||||
return $this->parseXml($res);
|
||||
}
|
||||
|
||||
private function parseXml(string $response): array
|
||||
{
|
||||
$xmlReader = XMLReader::XML($response, "UTF-8");
|
||||
$res = array();
|
||||
if ($xmlReader->next()) {
|
||||
$consoleSession = $xmlReader->expand();
|
||||
if ($consoleSession->nodeName == 'ConsoleSession') {
|
||||
foreach ($consoleSession->childNodes as $childNode) {
|
||||
$name = $childNode->nodeName;
|
||||
if (isset($res[$name])) {
|
||||
if (gettype($res[$name]) == 'string') {
|
||||
$res[$name] = array($res[$name], $childNode->nodeValue);
|
||||
}
|
||||
else {
|
||||
$res[$name][] = $childNode->nodeValue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$res[$name] = $childNode->nodeValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ use Mcp\Util\Util;
|
|||
class ForgotPassword extends \Mcp\RequestHandler
|
||||
{
|
||||
|
||||
const MESSAGE = 'Hallo %%NAME%%,<br/><br/>wir haben soeben eine Anfrage zur Zurücksetzung des Passworts für deinen 4Creative-Account erhalten.<br/><br/>Klicke <a href="%%RESET_LINK%%">hier</a>, um ein neues Passwort festzulegen. Dieser Link läuft in 24 Stunden ab.<br/><br/>Falls du diese Anfrage nicht gesendet hast, ignoriere sie einfach. Bei weiteren Fragen kannst du uns unter info@4creative.net oder per Discord über @ikeytan erreichen.';
|
||||
const MESSAGE = 'Hallo %%NAME%%,<br/><br/>wir haben soeben eine Anfrage zur Zurücksetzung des Passworts für deinen 4Creative-Account erhalten.<br/><br/>Klicke <a href="%%RESET_LINK%%">hier</a>, um ein neues Passwort festzulegen. Dieser Link läuft in 24 Stunden ab.<br/><br/>Falls du diese Anfrage nicht gesendet hast, ignoriere sie einfach. Bei weiteren Fragen kannst du uns unter info@4creative.net oder über unseren Discord-Server erreichen.';
|
||||
|
||||
public function __construct(\Mcp\Mcp $app)
|
||||
{
|
||||
|
|
|
@ -30,12 +30,11 @@ class Identities extends \Mcp\RequestHandler
|
|||
|
||||
$opensim = new OpenSim($this->app->db());
|
||||
|
||||
$csrf = $this->app->csrfField();
|
||||
while ($row = $statement->fetch()) {
|
||||
if ($row['IdentityID'] == $_SESSION['UUID']) {
|
||||
$entry = '<tr><td>'.htmlspecialchars(trim($opensim->getUserName($row['IdentityID']))).' <span class="badge badge-info">Aktiv</span></td><td>-</td></tr>';
|
||||
} else {
|
||||
$entry = '<tr><td>'.htmlspecialchars(trim($opensim->getUserName($row['IdentityID']))).'</td><td><form action="index.php?page=identities" method="post">'.$csrf.'<input type="hidden" name="uuid" value="'.htmlspecialchars($row['IdentityID']).'"><button type="submit" name="enableIdent" class="btn btn-success btn-sm">Aktivieren</button> <button type="submit" name="deleteIdent" class="btn btn-danger btn-sm">Löschen</button></form></td></tr>';
|
||||
$entry = '<tr><td>'.htmlspecialchars(trim($opensim->getUserName($row['IdentityID']))).'</td><td data-uuid="'.htmlspecialchars($row['IdentityID']).'"><button name="enableIdent" class="btn btn-success btn-sm" data-toggle="modal" data-target="#isc">Aktivieren</button> <button type="submit" name="deleteIdent" class="btn btn-danger btn-sm" data-toggle="modal" data-target="#idc">Löschen</button></td></tr>';
|
||||
}
|
||||
|
||||
$table = $table.$entry;
|
||||
|
@ -50,6 +49,7 @@ class Identities extends \Mcp\RequestHandler
|
|||
$this->app->template('identities.php')->parent('__dashboard.php')->vars([
|
||||
'title' => 'Identitäten',
|
||||
'username' => $_SESSION['DISPLAYNAME'],
|
||||
'activeIdent' => $_SESSION['FIRSTNAME'].' '.$_SESSION['LASTNAME'],
|
||||
'message' => $message
|
||||
])->unsafeVar('ident-list', $table.'</tbody></table>')->render();
|
||||
}
|
||||
|
@ -121,14 +121,17 @@ class Identities extends \Mcp\RequestHandler
|
|||
|
||||
$statementAccounts = $this->app->db()->prepare('INSERT INTO UserAccounts (PrincipalID, ScopeID, FirstName, LastName, Email, ServiceURLs, Created, UserLevel, UserFlags, UserTitle, active) VALUES (:PrincipalID, :ScopeID, :FirstName, :LastName, :Email, :ServiceURLs, :Created, :UserLevel, :UserFlags, :UserTitle, :active )');
|
||||
$statementAccounts->execute(['PrincipalID' => $avatarUUID, 'ScopeID' => "00000000-0000-0000-0000-000000000000", 'FirstName' => $avatarNameParts[0], 'LastName' => $avatarNameParts[1], 'Email' => $_SESSION['EMAIL'], 'ServiceURLs' => "HomeURI= GatekeeperURI= InventoryServerURI= AssetServerURI= ", 'Created' => time(), 'UserLevel' => 0, 'UserFlags' => 0, 'UserTitle' => "", 'active' => 1]);
|
||||
|
||||
|
||||
$statementProfile = $this->app->db()->prepare('INSERT INTO `userprofile` (`useruuid`, `profilePartner`, `profileImage`, `profileURL`, `profileFirstImage`, `profileAllowPublish`, `profileMaturePublish`, `profileWantToMask`, `profileWantToText`, `profileSkillsMask`, `profileSkillsText`, `profileLanguages`, `profileAboutText`, `profileFirstText`) VALUES (:useruuid, :profilePartner, :profileImage, :profileURL, :profileFirstImage, :profileAllowPublish, :profileMaturePublish, :profileWantToMask, :profileWantToText, :profileSkillsMask, :profileSkillsText, :profileLanguages, :profileAboutText, :profileFirstText)');
|
||||
$statementProfile->execute(['useruuid' => $avatarUUID, 'profilePartner' => "00000000-0000-0000-0000-000000000000", 'profileImage' => "00000000-0000-0000-0000-000000000000", 'profileURL' => '', 'profileFirstImage' => "00000000-0000-0000-0000-000000000000", "profileAllowPublish" => "0", "profileMaturePublish" => "0", "profileWantToMask" => "0", "profileWantToText" => "", "profileSkillsMask" => "0", "profileSkillsText" => "", "profileLanguages" => "", "profileAboutText" => "", "profileFirstText" => ""]);
|
||||
|
||||
$statementUserIdentitys = $this->app->db()->prepare('INSERT INTO mcp_user_identities (PrincipalID, IdentityID) VALUES (:PrincipalID, :IdentityID)');
|
||||
$statementUserIdentitys->execute(['PrincipalID' => $_SESSION['UUID'], 'IdentityID' => $avatarUUID]);
|
||||
} else {
|
||||
$_SESSION['identities_err'] = 'Dieser Name ist schon in Benutzung.';
|
||||
}
|
||||
} else {
|
||||
$_SESSION['identities_err'] = 'Der Name muss aus einem Vor und einem Nachnamen bestehen.';
|
||||
$_SESSION['identities_err'] = 'Der Name muss aus einem Vor- und einem Nachnamen bestehen.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,28 +18,30 @@ class Profile extends \Mcp\RequestHandler
|
|||
{
|
||||
$tpl = $this->app->template('profile.php')->parent('__dashboard.php');
|
||||
|
||||
$statement = $this->app->db()->prepare("CREATE TABLE IF NOT EXISTS `iarstates` (`userID` VARCHAR(36) NOT NULL COLLATE 'utf8_unicode_ci', `filesize` BIGINT(20) NOT NULL DEFAULT '0', `iarfilename` VARCHAR(64) NOT NULL COLLATE 'utf8_unicode_ci', `running` INT(1) NOT NULL DEFAULT '0', PRIMARY KEY (`userID`) USING BTREE) COLLATE='utf8_unicode_ci' ENGINE=InnoDB;");
|
||||
$statement->execute();
|
||||
|
||||
//Prüfe ob IAR grade erstellt wird.
|
||||
$statementIARCheck = $this->app->db()->prepare('SELECT 1 FROM iarstates WHERE userID =:userID');
|
||||
$statementIARCheck->execute(['userID' => $_SESSION['UUID']]);
|
||||
$iarRunning = $statementIARCheck->rowCount() != 0;
|
||||
$statementIARCheck->closeCursor();
|
||||
|
||||
if ($iarRunning) {
|
||||
if (isset($_SESSION['iar_created'])) {
|
||||
$tpl->unsafeVar('iar-message', '<div class="alert alert-success" role="alert">Deine IAR wird jetzt erstellt und der Download Link wird dir per PM zugesendet.</div>');
|
||||
} else {
|
||||
$tpl->unsafeVar('iar-message', '<div class="alert alert-danger" role="alert">Aktuell wird eine IAR erstellt.<br>Warte bitte bis du eine PM bekommst.</div>');
|
||||
$iarRunning = false;
|
||||
|
||||
if (isset($_SESSION['iar_created'])) {
|
||||
$tpl->unsafeVar('iar-message', '<div class="alert alert-success" role="alert">Deine IAR wird jetzt erstellt und der Download Link wird dir per PM zugesendet.</div>');
|
||||
unset($_SESSION['iar_created']);
|
||||
$iarRunning = true;
|
||||
} else {
|
||||
$statementIARCheck = $this->app->db()->prepare('SELECT iarfilename,state,created FROM mcp_iar_state WHERE userID =:userID');
|
||||
$statementIARCheck->execute(['userID' => $_SESSION['UUID']]);
|
||||
if ($row = $statementIARCheck->fetch()) {
|
||||
if ($row['state'] < 2) {
|
||||
$tpl->unsafeVar('iar-message', '<div class="alert alert-danger" role="alert">Aktuell wird eine IAR erstellt.<br>Warte bitte bis du eine PM bekommst.</div>');
|
||||
$iarRunning = true;
|
||||
}
|
||||
else {
|
||||
$tpl->unsafeVar('iar-message', '<div class="alert alert-success role="alert">Du kannst dir deine IAR (erstellt am '.date('d.m.Y', $row['created']).') <a href="https://'.$this->app->config('domain').'/index.php?api=downloadIar&id='.substr($row['iarfilename'], 0, strlen($row['iarfilename']) - 4).'">hier</a> herunterladen. Sie ist mit dem Passwort <i>password</i> geschützt.</div>');
|
||||
}
|
||||
}
|
||||
$tpl->var('iar-button-state', 'disabled');
|
||||
$statementIARCheck->closeCursor();
|
||||
}
|
||||
else {
|
||||
$tpl->vars([
|
||||
'iar-message' => ' ',
|
||||
'iar-state' => ''
|
||||
]);
|
||||
|
||||
if ($iarRunning) {
|
||||
$tpl->var('iar-button-state', 'disabled');
|
||||
}
|
||||
|
||||
$opensim = new OpenSim($this->app->db());
|
||||
|
@ -75,12 +77,27 @@ class Profile extends \Mcp\RequestHandler
|
|||
if (isset($_POST['createIAR'])) {
|
||||
$validator = new FormValidator(array()); // CSRF validation only
|
||||
if($validator->isValid($_POST)) {
|
||||
$iarname = md5(time().$_SESSION['UUID'] . rand()).".iar";
|
||||
|
||||
$statementIARSTART = $this->app->db()->prepare('INSERT INTO iarstates (userID, filesize, iarfilename) VALUES (:userID, :filesize, :iarfilename)');
|
||||
$statementIARSTART->execute(['userID' => $_SESSION['UUID'], 'filesize' => 0, 'iarfilename' => $iarname]);
|
||||
$validRequest = true;
|
||||
|
||||
$_SESSION['iar_created'] = true;
|
||||
$statementIarFile = $this->app->db()->prepare('SELECT iarfilename,state,created FROM mcp_iar_state WHERE userID = ?');
|
||||
$statementIarFile->execute([$_SESSION['UUID']]);
|
||||
if ($row = $statementIarFile->fetch()) {
|
||||
if ($row['state'] == 2) {
|
||||
unlink($this->app->getDataDir().DIRECTORY_SEPARATOR.'iars'.DIRECTORY_SEPARATOR.$row['iarfilename']);
|
||||
}
|
||||
else {
|
||||
$validRequest = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($validRequest) {
|
||||
$iarname = md5(time().$_SESSION['UUID'] . rand()).".iar";
|
||||
|
||||
$statementIARSTART = $this->app->db()->prepare('INSERT INTO mcp_iar_state (userID, filesize, iarfilename) VALUES (:userID, :filesize, :iarfilename) ON DUPLICATE KEY UPDATE filesize = :replFilesize, state = :replState');
|
||||
$statementIARSTART->execute(['userID' => $_SESSION['UUID'], 'filesize' => 0, 'iarfilename' => $iarname, 'replFilesize' => 0, 'replState' => 0]);
|
||||
|
||||
$_SESSION['iar_created'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif (isset($_POST['saveProfileData'])) {
|
||||
|
|
|
@ -11,7 +11,7 @@ use Mcp\Util\Util;
|
|||
class ResetPassword extends \Mcp\RequestHandler
|
||||
{
|
||||
|
||||
private const MESSAGE = 'Hallo %%NAME%%,<br/><br/>das Passwort für deinen 4Creative-Account wurde soeben über die Funktion "Passwort vergessen" geändert.<br/><br/>Solltest du diese Änderung nicht selbst durchgeführt haben, wende dich bitte umgehend per E-Mail (info@4creative.net) oder Discord (@ikeytan) an uns.';
|
||||
private const MESSAGE = 'Hallo %%NAME%%,<br/><br/>das Passwort für deinen 4Creative-Account wurde soeben über die Funktion "Passwort vergessen" geändert.<br/><br/>Solltest du diese Änderung nicht selbst durchgeführt haben, wende dich bitte umgehend per E-Mail (info@4creative.net) oder über unseren Discord-Server an uns.';
|
||||
private const TOKEN_INVALID = 'Dieser Link zur Passwortzurücksetzung ist nicht gültig. Bitte klicke oder kopiere den Link aus der E-Mail, die du erhalten hast.';
|
||||
private const TOKEN_EXPIRED = 'Dein Link zur Passwortzurücksetzung ist abgelaufen. Klicke <a href="index.php?page=forgot">hier</a>, um eine neue Anfrage zu senden.';
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ class SmtpClient
|
|||
{
|
||||
$mailer = new PHPMailer(true);
|
||||
$mailer->isSMTP();
|
||||
$mailer->CharSet = 'UTF-8';
|
||||
$mailer->Host = $host;
|
||||
$mailer->Port = $port;
|
||||
$mailer->Username = $username;
|
||||
|
@ -39,7 +40,7 @@ class SmtpClient
|
|||
$this->mailer->Subject = $subject;
|
||||
ob_start();
|
||||
$tpl->render();
|
||||
$tplOut = ob_end_clean();
|
||||
$tplOut = ob_get_flush();
|
||||
$this->mailer->Body = $tplOut;
|
||||
$this->mailer->AltBody = $this::htmlToPlain($tplOut);
|
||||
|
||||
|
|
|
@ -42,17 +42,20 @@ class Util
|
|||
return $res;
|
||||
}
|
||||
|
||||
public static function getDataFromHTTP($url, $content = "", $requestTyp = "application/text")
|
||||
public static function getDataFromHTTP($url, $content = "", $requestType = "application/text")
|
||||
{
|
||||
try {
|
||||
if ($content != "") {
|
||||
return file_get_contents($url, true, stream_context_create(array('http' => array('header' => 'Content-type: '.$requestTyp, 'method' => 'POST', 'timeout' => 0.5, 'content' => $content))));
|
||||
} else {
|
||||
return file_get_contents($url);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "(HTTP REQUEST) error while conntect to remote server. : ".$url;
|
||||
$curl = curl_init($url);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($curl, CURLOPT_TIMEOUT, 1);
|
||||
if ($content != "") {
|
||||
curl_setopt($curl, CURLOPT_POST, true);
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, $content);
|
||||
curl_setopt($curl, CURLOPT_USERAGENT, 'mcp/0.0.1');
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type' => $requestType
|
||||
]);
|
||||
}
|
||||
return curl_exec($curl);
|
||||
}
|
||||
|
||||
public static function sendInworldIM($fromUUID, $toUUID, $fromName, $targetURL, $text)
|
||||
|
|
|
@ -35,4 +35,14 @@ password = secret
|
|||
[grid]
|
||||
name = OpenSim
|
||||
main-news = Yet another OpenSim Grid.
|
||||
homeurl = http://...:8002
|
||||
homeurl = http://...:8002
|
||||
|
||||
; Benötigt für die Anforderung von IARs
|
||||
[iarfetcher]
|
||||
; Zugangsdaten der REST-Konsole von OpenSimulator
|
||||
host = example.com
|
||||
port = 9001 ; Einstellung console_port
|
||||
user = mcp
|
||||
password = secret
|
||||
; IAR-Verzeichnis aus Sicht der OpenSimulator-Instanz
|
||||
os-iar-path = /opt/opensim/iars/
|
24
cron.php
24
cron.php
|
@ -1,24 +0,0 @@
|
|||
<?php
|
||||
$RUNTIME = array();
|
||||
$RUNTIME['BASEDIR'] = __DIR__;
|
||||
set_include_path('.:'.$RUNTIME['BASEDIR']);
|
||||
include_once "config.php";
|
||||
|
||||
if(!isset($RUNTIME['CRON_RESTRICTION'])) {
|
||||
http_response_code(500);
|
||||
die();
|
||||
}
|
||||
|
||||
if ($RUNTIME['CRON_RESTRICTION'] != 'none' && (!isset($RUNTIME['CRON_KEY']) || !isset($REQUEST['key']) || $_REQUEST['key'] !== $RUNTIME['CRON_KEY'])) {
|
||||
http_response_code(401);
|
||||
die();
|
||||
}
|
||||
|
||||
if ($handle = opendir('./cron/')) {
|
||||
while (false !== ($entry = readdir($handle))) {
|
||||
if ($entry != "." && $entry != "..") {
|
||||
include_once "./cron/".$entry;
|
||||
}
|
||||
}
|
||||
closedir($handle);
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
<?php
|
||||
include_once 'app/OpenSim.php';
|
||||
$opensim = new OpenSim();
|
||||
|
||||
$statement = $RUNTIME['PDO']->prepare("CREATE TABLE IF NOT EXISTS `iarstates` (`userID` VARCHAR(36) NOT NULL COLLATE 'utf8_unicode_ci', `filesize` BIGINT(20) NOT NULL DEFAULT '0', `iarfilename` VARCHAR(64) NOT NULL COLLATE 'utf8_unicode_ci', `running` INT(1) NOT NULL DEFAULT '0', PRIMARY KEY (`userID`) USING BTREE) COLLATE='utf8_unicode_ci' ENGINE=InnoDB;");
|
||||
$statement->execute();
|
||||
|
||||
$statement = $RUNTIME['PDO']->prepare("SELECT userID,iarfilename,filesize FROM iarstates WHERE running = 1 LIMIT 1");
|
||||
$statement->execute();
|
||||
|
||||
if ($row = $statement->fetch()) {
|
||||
$email = $opensim->getUserMail($row['userID']);
|
||||
|
||||
$fullFilePath = "/var/www/html/data/".$row['iarfilename'];
|
||||
|
||||
echo "Aktive IAR für ".$opensim->getUserName($row['userID'])." gefunden. File: ".$fullFilePath."\n";
|
||||
|
||||
if (file_exists($fullFilePath)) {
|
||||
$filesize = filesize($fullFilePath);
|
||||
|
||||
if ($filesize != $row['filesize']) {
|
||||
$statementUpdate = $RUNTIME['PDO']->prepare('UPDATE iarstates SET filesize = :filesize WHERE userID = :userID');
|
||||
$statementUpdate->execute(['filesize' => $filesize, 'userID' => $row['userID']]);
|
||||
|
||||
echo "Status der IAR für ".$opensim->getUserName($row['userID']).": Speichert...\n";
|
||||
} else {
|
||||
$APIURL = $RUNTIME['SIDOMAN']['URL']."api.php?CONTAINER=".$RUNTIME['SIDOMAN']['CONTAINER']."&KEY=".$RUNTIME['SIDOMAN']['PASSWORD']."&METODE=RESTART";
|
||||
$APIResult = file_get_contents($APIURL);
|
||||
echo "Status der IAR für ".$opensim->getUserName($row['userID']).": Sende Mail...\n";
|
||||
$statementUpdate = $RUNTIME['PDO']->prepare('DELETE FROM iarstates WHERE userID = :userID');
|
||||
$statementUpdate->execute(['userID' => $row['userID']]);
|
||||
|
||||
sendInworldIM("00000000-0000-0000-0000-000000000000", $row['userID'], "Inventory", $RUNTIME['GRID']['HOMEURL'], "Deine IAR ist fertig zum Download: ".$RUNTIME['IAR']['BASEURL'].$row['iarfilename']);
|
||||
}
|
||||
} else {
|
||||
$name = explode(" ", $opensim->getUserName($row['userID']));
|
||||
|
||||
$APIURL = $RUNTIME['SIDOMAN']['URL']."api.php?CONTAINER=".$RUNTIME['SIDOMAN']['CONTAINER']."&KEY=".$RUNTIME['SIDOMAN']['PASSWORD']."&METODE=COMMAND&COMMAND=".urlencode("save iar ".$name[0]." ".$name[1]." /* PASSWORD /downloads/".$row['iarfilename']);
|
||||
$APIResult = file_get_contents($APIURL);
|
||||
|
||||
echo "IAR für ".$name[0]." ".$name[1]." wurde gestartet: Status: ".$APIResult."\n";
|
||||
}
|
||||
} else {
|
||||
$statement = $RUNTIME['PDO']->prepare("SELECT userID,iarfilename FROM iarstates WHERE running = 0 LIMIT 1");
|
||||
$statement->execute();
|
||||
|
||||
while ($row = $statement->fetch()) {
|
||||
$statementUpdate = $RUNTIME['PDO']->prepare('UPDATE iarstates SET running = :running WHERE userID = :userID');
|
||||
$statementUpdate->execute(['running' => 1, 'userID' => $row['userID']]);
|
||||
|
||||
$name = explode(" ", $opensim->getUserName($row['userID']));
|
||||
|
||||
$APIURL = $RUNTIME['SIDOMAN']['URL']."api.php?CONTAINER=".$RUNTIME['SIDOMAN']['CONTAINER']."&KEY=".$RUNTIME['SIDOMAN']['PASSWORD']."&METODE=COMMAND&COMMAND=".urlencode("save iar ".$name[0]." ".$name[1]." /* PASSWORD /downloads/".$row['iarfilename']);
|
||||
$APIResult = file_get_contents($APIURL);
|
||||
|
||||
echo "IAR für ".$name[0]." ".$name[1]." wurde gestartet: Status: ".$APIResult."\n";
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
$statement = $RUNTIME['PDO']->prepare("SELECT id,hash FROM fsassets ORDER BY create_time DESC");
|
||||
$statement->execute();
|
||||
|
||||
$count = 0;
|
||||
|
||||
while ($row = $statement->fetch()) {
|
||||
$fileNameParts = array();
|
||||
$fileNameParts[0] = substr($row['hash'], 0, 2);
|
||||
$fileNameParts[1] = substr($row['hash'], 2, 2);
|
||||
$fileNameParts[2] = substr($row['hash'], 4, 2);
|
||||
$fileNameParts[3] = substr($row['hash'], 6, 4);
|
||||
$fileNameParts[4] = $row['hash'].".gz";
|
||||
|
||||
//$fileNameParts['Time'] = time();
|
||||
$fileNameParts['UUID'] = $row['id'];
|
||||
$fileNameParts['FilePath'] = "/data/assets/base/".$fileNameParts[0]."/".$fileNameParts[1]."/".$fileNameParts[2]."/".$fileNameParts[3]."/".$fileNameParts[4];
|
||||
|
||||
if (file_exists($fileNameParts['FilePath'])) {
|
||||
$filesize = filesize($fileNameParts['FilePath']);
|
||||
if ($filesize === false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$filesize = 0;
|
||||
}
|
||||
|
||||
$fileNameParts['FileSize'] = $filesize;
|
||||
$fileNameParts['Count'] = $count++;
|
||||
|
||||
if ($fileNameParts['FileSize'] == 0) {
|
||||
$add = $RUNTIME['PDO']->prepare('DELETE FROM fsassets WHERE hash = :fileHash');
|
||||
$add->execute(['fileHash' => $row['hash']]);
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
$InventarCheckStatement = $RUNTIME['PDO']->prepare("UPDATE inventoryitems i SET
|
||||
i.inventoryName = concat('[DEFEKT] ', i.inventoryName)
|
||||
WHERE
|
||||
i.assetID IN (
|
||||
SELECT
|
||||
i.assetID
|
||||
FROM inventoryitems i
|
||||
WHERE
|
||||
NOT EXISTS( SELECT *
|
||||
FROM fsassets fs
|
||||
WHERE
|
||||
fs.id = i.assetID
|
||||
)
|
||||
AND NOT i.inventoryName LIKE '[DEFEKT] %'
|
||||
AND i.assetType <> 24
|
||||
)");
|
||||
|
||||
$InventarCheckStatement->execute();
|
|
@ -1,102 +0,0 @@
|
|||
<?php
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\SMTP;
|
||||
|
||||
include_once 'lib/phpmailer/Exception.php';
|
||||
include_once 'lib/phpmailer/PHPMailer.php';
|
||||
include_once 'lib/phpmailer/SMTP.php';
|
||||
|
||||
$statement = $RUNTIME['PDO']->prepare("CREATE TABLE IF NOT EXISTS im_offline_send (`id` int(6) NOT NULL DEFAULT 0) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci");
|
||||
$statement->execute();
|
||||
|
||||
function isMailAlreadySent($id)
|
||||
{
|
||||
global $RUNTIME;
|
||||
|
||||
$statement = $RUNTIME['PDO']->prepare("SELECT 1 FROM im_offline_send WHERE id = ? LIMIT 1");
|
||||
$statement->execute(array($id));
|
||||
|
||||
if ($statement->rowCount() != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$IMTYP = array(
|
||||
"0" => "eine Nachricht",
|
||||
"3" => "eine Gruppeneinladung",
|
||||
"4" => "ein Inventaritem",
|
||||
"5" => "eine Bestätigung zur Annahme von Inventar",
|
||||
"6" => "eine Information zur Ablehnung von Inventar",
|
||||
"7" => "eine Aufforderung zur Gruppenwahl",
|
||||
"9" => "ein Inventaritem von einem Script",
|
||||
"19" => "eine Nachricht von einem Script",
|
||||
"32" => "eine Gruppennachricht",
|
||||
"38" => "eine Freundschaftsanfrage",
|
||||
"39" => "eine Bestätigung über die Annahme der Freundschaft",
|
||||
"40" => "eine Information über das Ablehnen der Freundschaft"
|
||||
);
|
||||
|
||||
//$statement = $RUNTIME['PDO']->prepare("SELECT * FROM im_offline WHERE PrincipalID = '1148b04d-7a93-49e9-b3c9-ea0cdeec38f7'");
|
||||
$statement = $RUNTIME['PDO']->prepare("SELECT ID,PrincipalID,Message FROM im_offline");
|
||||
$statement->execute();
|
||||
|
||||
while ($row = $statement->fetch()) {
|
||||
include_once 'app/OpenSim.php';
|
||||
$opensim = new OpenSim();
|
||||
|
||||
$email = $opensim->getUserMail($row['PrincipalID']);
|
||||
$allowOfflineIM = $opensim->allowOfflineIM($row['PrincipalID']);
|
||||
|
||||
if ($email != "" && $allowOfflineIM == "TRUE") {
|
||||
if (!isMailAlreadySent($row['ID'])) {
|
||||
$statementSend = $RUNTIME['PDO']->prepare('INSERT INTO im_offline_send (id) VALUES (:idnummer)');
|
||||
$statementSend->execute(['idnummer' => $row['ID']]);
|
||||
|
||||
$mail = new PHPMailer(true);
|
||||
|
||||
$mail->SMTPDebug = SMTP::DEBUG_SERVER;
|
||||
$mail->isSMTP();
|
||||
$mail->Host = $RUNTIME['SMTP']['SERVER'];
|
||||
$mail->Port = $RUNTIME['SMTP']['PORT'];
|
||||
$mail->SMTPAuth = false;
|
||||
|
||||
$mail->setFrom($RUNTIME['SMTP']['ADRESS'], $RUNTIME['GRID']['NAME']);
|
||||
$mail->addAddress($email, $opensim->getUserName($row['PrincipalID']));
|
||||
|
||||
$XMLMESSAGE = new SimpleXMLElement($row['Message']);
|
||||
|
||||
$HTMLMESSAGE = "Du hast ".$IMTYP["".$XMLMESSAGE->dialog.""]." in ".$RUNTIME['GRID']['NAME']." bekommen. <br><p><ul><li>".htmlspecialchars($XMLMESSAGE->message)."</li></ul></p>Gesendet von: ";
|
||||
|
||||
if (isset($XMLMESSAGE->fromAgentName)) {
|
||||
$HTMLMESSAGE .= $XMLMESSAGE->fromAgentName;
|
||||
}
|
||||
|
||||
if (isset($XMLMESSAGE->RegionID) && isset($XMLMESSAGE->Position)) {
|
||||
if ($XMLMESSAGE->Position->X != 0 || $XMLMESSAGE->Position->X != 0 || $XMLMESSAGE->Position->X != 0) { //TODO
|
||||
$HTMLMESSAGE .= " @ ".$opensim->getRegionName($XMLMESSAGE->RegionID)."/".$XMLMESSAGE->Position->X."/".$XMLMESSAGE->Position->Y."/".$XMLMESSAGE->Position->Z;
|
||||
} else {
|
||||
$HTMLMESSAGE .= " @ ".$opensim->getRegionName($XMLMESSAGE->RegionID);
|
||||
}
|
||||
}
|
||||
|
||||
$HTML = new HTML();
|
||||
$HTML->importHTML("mail.html");
|
||||
$HTML->setSeitenInhalt($HTMLMESSAGE);
|
||||
$HTML->build();
|
||||
|
||||
$mail->isHTML(true);
|
||||
$mail->Subject = "Du hast ".$IMTYP["".$XMLMESSAGE->dialog.""]." in ".$RUNTIME['GRID']['NAME'].".";
|
||||
$mail->Body = $HTML->ausgabe();
|
||||
$mail->AltBody = strip_tags($HTMLMESSAGE);
|
||||
|
||||
//print_r($mail);
|
||||
$mail->send();
|
||||
}else{
|
||||
//echo $row['ID']." wurde bereits gesendet.";
|
||||
}
|
||||
}else{
|
||||
//echo $row['PrincipalID']." möchte keine offline IM oder hat keine E-MAIL Adresse hinterlegt.";
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
<?php
|
||||
$createStatement = $RUNTIME['PDO']->prepare("CREATE TABLE IF NOT EXISTS `regions_info` (`regionID` VARCHAR(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;");
|
||||
$createStatement->execute();
|
||||
|
||||
$statement = $RUNTIME['PDO']->prepare("SELECT uuid,regionName,owner_uuid,serverURI FROM regions");
|
||||
$statement->execute();
|
||||
|
||||
ini_set('default_socket_timeout', 3);
|
||||
|
||||
$ctx = stream_context_create(array('http'=>
|
||||
array(
|
||||
'timeout' => 3,
|
||||
)
|
||||
));
|
||||
|
||||
while($row = $statement->fetch())
|
||||
{
|
||||
$result = file_get_contents($row['serverURI']."jsonSimStats", false, $ctx);
|
||||
|
||||
if($result == FALSE || $result == "")
|
||||
{
|
||||
include 'app/OpenSim.php';
|
||||
|
||||
echo "Die Region ".$row['regionName']." von ".$opensim->getUserName($row['owner_uuid'])." ist nicht erreichbar.\n";
|
||||
|
||||
$infoStatement = $RUNTIME['PDO']->prepare("SELECT OfflineTimer FROM regions_info WHERE regionID = :regionID");
|
||||
$infoStatement->execute(['regionID' => $row['uuid']]);
|
||||
|
||||
if($infoRow = $infoStatement->fetch())
|
||||
{
|
||||
if(($infoRow['OfflineTimer'] + 3600) <= time())
|
||||
{
|
||||
echo "Die Region ".$row['regionName']." von ".$opensim->getUserName($row['owner_uuid'])." ist seit über eine Stunde nicht erreichbar!\n";
|
||||
|
||||
//sendInworldIM("00000000-0000-0000-0000-000000000000", $row['owner_uuid'], "Region", $RUNTIME['GRID']['HOMEURL'], "WARNUNG: Deine Region '".$row['regionName']."' ist nicht erreichbar und wurde deshalb aus dem Grid entfernt.");
|
||||
|
||||
//$statementUpdate = $RUNTIME['PDO']->prepare('DELETE FROM regions WHERE uuid = :uuid');
|
||||
//$statementUpdate->execute(['uuid' => $row['uuid']]);
|
||||
}else{
|
||||
//sendInworldIM("00000000-0000-0000-0000-000000000000", $row['owner_uuid'], "Region", $RUNTIME['GRID']['HOMEURL'], "WARNUNG: Deine Region '".$row['regionName']."' ist nicht erreichbar!");
|
||||
}
|
||||
}
|
||||
}else{
|
||||
$regionData = json_decode($result);
|
||||
|
||||
$statementAccounts = $RUNTIME['PDO']->prepare('REPLACE INTO `regions_info` (`regionID`, `RegionVersion`, `ProcMem`, `Prims`, `SimFPS`, `PhyFPS`, `OfflineTimer`) VALUES (:regionID, :RegionVersion, :ProcMem, :Prims, :SimFPS, :PhyFPS, :OfflineTimer)');
|
||||
$statementAccounts->execute(['regionID' => $row['uuid'], 'RegionVersion' => $regionData->Version, 'ProcMem' => $regionData->ProcMem, 'Prims' => $regionData->Prims, 'SimFPS' => $regionData->SimFPS, 'PhyFPS' => $regionData->PhyFPS, 'OfflineTimer' => time()]);
|
||||
}
|
||||
}
|
||||
?>
|
|
@ -0,0 +1,12 @@
|
|||
$('#isc').on('show.bs.modal', function(event) {
|
||||
let identCol = $(event.relatedTarget).parent();
|
||||
let uuid = identCol.data('uuid');
|
||||
$('#isc-ident-uuid').attr('value', uuid);
|
||||
$('#isc-ident-name').text(identCol.prev().text());
|
||||
});
|
||||
$('#idc').on('show.bs.modal', function(event) {
|
||||
let identCol = $(event.relatedTarget).parent();
|
||||
let uuid = identCol.data('uuid');
|
||||
$('#idc-ident-uuid').attr('value', uuid);
|
||||
$('#idc-ident-name').text(identCol.prev().text());
|
||||
});
|
|
@ -139,5 +139,6 @@
|
|||
<script src="./js/vendor/bootstrap.bundle.min.js"></script>
|
||||
<script src="./js/vendor/jquery.easing.min.js"></script>
|
||||
<script src="./js/sb-admin.min.js"></script>
|
||||
<?= $v['custom-js'] ?>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title><?= $v['title'] ?></title>
|
||||
<link rel="stylesheet" type="text/css" href="./style/login.min.css">
|
||||
<link href="./style/4Creative.ico" rel="icon">
|
||||
<link href="./style/4Creative.ico" rel="apple-touch-icon">
|
||||
<link href="./img/4Creative.ico" rel="icon">
|
||||
<link href="./img/4Creative.ico" rel="apple-touch-icon">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
<div>
|
||||
Hier kannst du die UUID von deinem Avatar ändern und später jederzeit wieder zurückwechseln. <br>
|
||||
Inventar und Gruppen bleiben dabei erhalten. <br>
|
||||
Jede Identität hat ein eigenes Aussehen, ein eigenes Profil und eine eigene Freundesliste.<br>
|
||||
Nach der Änderung musst du dich neu anmelden.<br>
|
||||
|
||||
Jede Identität hat ein eigenes Aussehen, ein eigenes Profil und eine eigene Freundesliste.
|
||||
</div>
|
||||
<br><?= $v['message'] ?><br>
|
||||
|
||||
|
@ -16,7 +14,7 @@
|
|||
<div style="width: 400px; margin: auto; left: 50%;">
|
||||
Hier kannst du eine neue Identität erstellen.
|
||||
</div>
|
||||
|
||||
|
||||
<div style="width: 400px; margin: auto; left: 50%;">
|
||||
<form action="index.php?page=identities" method="post">
|
||||
<div class="row" style="margin-top: 15px;">
|
||||
|
@ -25,7 +23,7 @@
|
|||
<input type="text" class="form-control" id="newName" name="newName" placeholder="Name">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row" style="margin-top: 15px;">
|
||||
<div class="col">
|
||||
<?= $v['csrf'] ?>
|
||||
|
@ -36,4 +34,72 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="isc" tabindex="-1" aria-labelledby="iscLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="iscLabel">Identitätswechsel bestätigen</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Folgende Daten sind für alle deine Identitäten gleich:
|
||||
<ul>
|
||||
<li>Passwort</li>
|
||||
<li>Inventar</li>
|
||||
<li>Gruppen</li>
|
||||
</ul>
|
||||
Dagegen besitzt du nach dem Wechsel die folgenden, separaten Einstellungen deiner neuen Identität:
|
||||
<ul>
|
||||
<li>Name</li>
|
||||
<li>User-Level</li>
|
||||
<li>Profil</li>
|
||||
<li>Freundesliste</li>
|
||||
</ul>
|
||||
Möchtest du deine aktive Identität von <b><?= $v['activeIdent'] ?></b> zu <b id="isc-ident-name"></b> wechseln? Du kannst jederzeit zurückwechseln.
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<form action="index.php?page=identities" method="post">
|
||||
<input type="hidden" value="" name="uuid" id="isc-ident-uuid">
|
||||
<?= $v['csrf'] ?>
|
||||
<button type="submit" name="enableIdent" class="btn btn-primary btn-success">Identität wechseln</button>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Abbrechen</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="idc" tabindex="-1" aria-labelledby="idcLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="idcLabel">Löschung der Identität bestätigen</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Wenn du eine Identität löschst, werden folgende zu dieser zugehörige Daten gelöscht:
|
||||
<ul>
|
||||
<li>Name</li>
|
||||
<li>User-Level</li>
|
||||
<li>Profil</li>
|
||||
<li>Freundesliste</li>
|
||||
</ul>
|
||||
Deine anderen Account-Daten sind davon nicht betroffen.<br>
|
||||
Möchtest du die Identität <b id="idc-ident-name"></b> wirklich löschen?
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<form action="index.php?page=identities" method="post">
|
||||
<input type="hidden" value="" name="uuid" id="idc-ident-uuid">
|
||||
<?= $v['csrf'] ?>
|
||||
<button type="submit" name="deleteIdent" class="btn btn-primary btn-danger">Identität löschen</button>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Abbrechen</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php $v['custom-js'] = '<script src="./js/identities.js"></script>' ?>
|
|
@ -29,12 +29,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 15px;">
|
||||
<div class="col">
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 15px;">
|
||||
<div class="col">
|
||||
<label for="inputpartner">Partner</label>
|
||||
|
@ -42,24 +36,18 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 15px;">
|
||||
<div class="col">
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 15px;">
|
||||
<div class="col">
|
||||
<?= $v['csrf'] ?>
|
||||
<button type="submit" name="saveProfileData" class="btn btn-primary btn-lg">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div style="width: 400px; margin: auto; left: 50%;">
|
||||
<div class="row" style="margin-top: 15px;">
|
||||
<div class="col">
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
<form action="index.php?page=profile" method="post">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
|
@ -86,17 +74,14 @@
|
|||
<div class="row" style="margin-top: 15px;">
|
||||
<div class="col">
|
||||
<?= $v['csrf'] ?>
|
||||
<p class="text-center"><button type="submit" name="savePassword" class="btn btn-primary btn-lg">Speichern</button></p>
|
||||
<button type="submit" name="savePassword" class="btn btn-primary btn-lg">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="row" style="margin-top: 15px;">
|
||||
<div class="col">
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div style="width: 400px; margin: auto; left: 50%;">
|
||||
<p class="lead"><b>IAR Sicherung</b></p>
|
||||
<p class="text-center"><?= $v['iar-message'] ?></p>
|
||||
Hier kannst du eine IAR deines Inventars erstellen.<br>
|
||||
|
|
Loading…
Reference in New Issue