Compare commits
7
Commits
REST-API
...
user-rights
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2901a5e362 | ||
|
|
2231d65465 | ||
|
|
4558a717fa | ||
|
|
b2b4ac2124 | ||
|
|
f7fd4ed183 | ||
|
|
a73b2ec13f | ||
|
|
dce0a0caed |
No files matched your search
@@ -3,3 +3,17 @@ RewriteEngine On
|
|||||||
RewriteRule ^(Commands|config_backups|controller|modules)($|/.*) - [F,L,NC]
|
RewriteRule ^(Commands|config_backups|controller|modules)($|/.*) - [F,L,NC]
|
||||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||||
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
||||||
|
|
||||||
|
# -----------------------------------
|
||||||
|
# .php entfernen
|
||||||
|
# -----------------------------------
|
||||||
|
|
||||||
|
# browser requestet datei.php -> redirect zu /datei
|
||||||
|
RewriteCond %{THE_REQUEST} \s/+(.+?)\.php[\s?] [NC]
|
||||||
|
RewriteRule ^ /%1 [R=301,L]
|
||||||
|
|
||||||
|
# /datei -> datei.php
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteCond %{DOCUMENT_ROOT}/$1.php -f
|
||||||
|
RewriteRule ^(.+)$ $1.php [L]
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
include_once '../controller/DatabaseController.php';
|
include_once( __DIR__ . '/../../controller/DatabaseController.php');
|
||||||
include_once '../controller/DataController.php';
|
include_once( __DIR__ . '/../../controller/DataController.php');
|
||||||
include_once __DIR__ . '/../modules/Mail.php';
|
include_once( __DIR__ . '/../../controller/ApiController.php');
|
||||||
|
include_once(__DIR__ . '/../../modules/Mail.php');
|
||||||
|
|
||||||
header("Content-Type: application/json");
|
header("Content-Type: application/json");
|
||||||
header("Access-Control-Allow-Origin: *");
|
header("Access-Control-Allow-Origin: *");
|
||||||
@@ -13,9 +14,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
|||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// $validTokens = [
|
|
||||||
// "15a437d128c918633e57dc7d18118a073b807644f21731ed5a7ebcf3eca9e242",
|
|
||||||
// ];
|
|
||||||
$validTokens = DatabaseShow(
|
$validTokens = DatabaseShow(
|
||||||
"API_Credentials",
|
"API_Credentials",
|
||||||
"TOKEN"
|
"TOKEN"
|
||||||
@@ -60,6 +58,8 @@ if (!in_array($token, $validTokens)) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$data = null;
|
||||||
|
|
||||||
if (in_array($token, $validTokens)) {
|
if (in_array($token, $validTokens)) {
|
||||||
DatabaseInsert(
|
DatabaseInsert(
|
||||||
"API_LOGS",
|
"API_LOGS",
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
include_once '../controller/DatabaseController.php';
|
include_once( __DIR__ . '/../../controller/DatabaseController.php' );
|
||||||
include_once '../controller/DataController.php';
|
include_once( __DIR__ . '/../../controller/DataController.php' );
|
||||||
|
|
||||||
header("Content-Type: application/json");
|
header("Content-Type: application/json");
|
||||||
header("Access-Control-Allow-Origin: *");
|
header("Access-Control-Allow-Origin: *");
|
||||||
@@ -110,4 +110,4 @@ DatabaseInsert(
|
|||||||
$json
|
$json
|
||||||
);
|
);
|
||||||
|
|
||||||
echo $json;
|
echo $json;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
include_once(__DIR__ . '/../../controller/DatabaseController.php');
|
||||||
|
include_once(__DIR__ . '/../../controller/DataController.php');
|
||||||
|
include_once(__DIR__ . '/../../controller/ApiController.php');
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$api = (new ApiController());
|
||||||
|
|
||||||
|
if ($api->urlAuth($_GET['token']) == true ) {
|
||||||
|
|
||||||
|
if (isset($_GET['day'])) {
|
||||||
|
|
||||||
|
$day = $_GET['day'];
|
||||||
|
|
||||||
|
$data = DatabaseShowWithFK(
|
||||||
|
"Stundenplan",
|
||||||
|
$Stundenplan,
|
||||||
|
"ZEITEN, $day",
|
||||||
|
"$day IS NOT NULL AND $day != '' AND $day != '(NULL)' ORDER BY ZEITEN ASC"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$data = DatabaseShowWithFK(
|
||||||
|
"Stundenplan",
|
||||||
|
$Stundenplan,
|
||||||
|
"ZEITEN, MO, DI, MO, DO, FR",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
echo $api->jsonSuccess($data);
|
||||||
|
$api->requestSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http://localhost:9000/api/v2/lessons.php?token=f3a50684d7f0cc2d9b21601798ea48242fab7481a0d0407a2b8683b0b74a79e3
|
||||||
|
* http://localhost:9000/api/v2/lessons.php?token=f3a50684d7f0cc2d9b21601798ea48242fab7481a0d0407a2b8683b0b74a79e3&day=FR
|
||||||
|
*/
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
include_once(__DIR__ . '/../../controller/DatabaseController.php');
|
||||||
|
include_once(__DIR__ . '/../../controller/DataController.php');
|
||||||
|
include_once(__DIR__ . '/../../controller/ApiController.php');
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$api = (new ApiController());
|
||||||
|
|
||||||
|
if ($api->urlAuth($_GET['token']) == true ) {
|
||||||
|
|
||||||
|
if (isset($_GET['date'])) {
|
||||||
|
|
||||||
|
$date = $_GET['date'];
|
||||||
|
|
||||||
|
$data = DatabaseShowWithFK(
|
||||||
|
"Berufsschulberichte",
|
||||||
|
$Berufsschulberichte,
|
||||||
|
"*",
|
||||||
|
"DATUM = ?",
|
||||||
|
$date
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$data = DatabaseShowWithFK(
|
||||||
|
"Berufsschulberichte",
|
||||||
|
$Berufsschulberichte,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
echo $api->jsonSuccess($data);
|
||||||
|
$api->requestSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http://localhost:9000/api/v2/schoolReports.php?token=f3a50684d7f0cc2d9b21601798ea48242fab7481a0d0407a2b8683b0b74a79e3
|
||||||
|
* http://localhost:9000/api/v2/schoolReports.php?token=f3a50684d7f0cc2d9b21601798ea48242fab7481a0d0407a2b8683b0b74a79e3&date=2025-09-09
|
||||||
|
*/
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
include_once(__DIR__ . '/../../controller/DatabaseController.php');
|
||||||
|
include_once(__DIR__ . '/../../controller/DataController.php');
|
||||||
|
include_once(__DIR__ . '/../../controller/ApiController.php');
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$api = (new ApiController());
|
||||||
|
|
||||||
|
if ($api->urlAuth($_GET['token']) == true ) {
|
||||||
|
|
||||||
|
if (isset($_GET['date'])) {
|
||||||
|
|
||||||
|
$date = $_GET['date'];
|
||||||
|
|
||||||
|
$data = DatabaseShow(
|
||||||
|
"Firmenberichte",
|
||||||
|
"DATUM, PROZESS_1, PROZESS_2, PROZESS_3, PROZESS_4, BEGINN, ENDE_PUBLIC",
|
||||||
|
"DATUM = ?",
|
||||||
|
$date
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$data = DatabaseShow(
|
||||||
|
"Firmenberichte",
|
||||||
|
"DATUM, PROZESS_1, PROZESS_2, PROZESS_3, PROZESS_4, BEGINN, ENDE_PUBLIC",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
echo $api->jsonSuccess($data);
|
||||||
|
$api->requestSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http://localhost:9000/api/v2/workReports.php?token=f3a50684d7f0cc2d9b21601798ea48242fab7481a0d0407a2b8683b0b74a79e3
|
||||||
|
* http://localhost:9000/api/v2/workReports.php?token=f3a50684d7f0cc2d9b21601798ea48242fab7481a0d0407a2b8683b0b74a79e3&date=2025-09-09
|
||||||
|
*/
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require(__DIR__ . '/DatabaseController.php');
|
||||||
|
|
||||||
|
class ApiController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* method for pretty jsonsuccess output
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function jsonSuccess($data) {
|
||||||
|
return json_encode(
|
||||||
|
[
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $data,
|
||||||
|
],
|
||||||
|
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* method for pretty jsonerror output
|
||||||
|
*
|
||||||
|
* @param array $error
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function jsonError ($error) {
|
||||||
|
echo json_encode(
|
||||||
|
[
|
||||||
|
'status' => 'failed',
|
||||||
|
'data' => 'none',
|
||||||
|
'error' => $error
|
||||||
|
],
|
||||||
|
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get authentication key out database
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getAuthKey () {
|
||||||
|
$validTokens = DatabaseShow(
|
||||||
|
"API_Credentials",
|
||||||
|
"TOKEN"
|
||||||
|
);
|
||||||
|
|
||||||
|
return array_column($validTokens, 'TOKEN');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* url authentication method via token and optional parameter function
|
||||||
|
*
|
||||||
|
* @param string $token
|
||||||
|
* @param string $function
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function urlAuth ($token, $function = '') {
|
||||||
|
|
||||||
|
$validTokens = $this->getAuthKey();
|
||||||
|
if (!in_array($token, $validTokens)) {
|
||||||
|
http_response_code(401);
|
||||||
|
$error = json_encode([
|
||||||
|
"error" => "Unauthorized",
|
||||||
|
"debug_received_token" => $token,
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo $this->jsonError($error);
|
||||||
|
|
||||||
|
DatabaseInsert(
|
||||||
|
"API_LOGS",
|
||||||
|
"CLIENT_IP, TOKEN, API, FUNKTION, JSON",
|
||||||
|
(new getSpecificData())->getClientIP(),
|
||||||
|
$token,
|
||||||
|
(new getSpecificData())->getRequestURL(),
|
||||||
|
$function,
|
||||||
|
$error
|
||||||
|
);
|
||||||
|
|
||||||
|
errorMail(
|
||||||
|
"API Login failed",
|
||||||
|
$error,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function requestSave($function = '') {
|
||||||
|
DatabaseInsert(
|
||||||
|
"API_LOGS",
|
||||||
|
"CLIENT_IP, TOKEN, API, FUNKTION, JSON",
|
||||||
|
(new getSpecificData())->getClientIP(),
|
||||||
|
$_GET['token'],
|
||||||
|
(new getSpecificData())->getRequestURL(),
|
||||||
|
$function,
|
||||||
|
'{"status": "success"}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create new api token
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private function createToken() {
|
||||||
|
return bin2hex(random_bytes(32));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* save created api token
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function saveToken() {
|
||||||
|
try {
|
||||||
|
DatabaseInsert(
|
||||||
|
"API_Credentials",
|
||||||
|
"TOKEN, BENUTZER",
|
||||||
|
$this->createToken(),
|
||||||
|
"API-User"
|
||||||
|
);
|
||||||
|
} catch (Exception $error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once(__DIR__ . '/DatabaseController.php');
|
||||||
|
|
||||||
|
class RightsController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* save new site with sitename
|
||||||
|
*
|
||||||
|
* @param string $sitename
|
||||||
|
*/
|
||||||
|
private function saveSite($sitename) {
|
||||||
|
DatabaseInsert(
|
||||||
|
"Backend_Seiten",
|
||||||
|
"DATEINAME",
|
||||||
|
$sitename
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get site id by sitename
|
||||||
|
*
|
||||||
|
* @param string $sitename
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
private function getSiteID($sitename) {
|
||||||
|
$id = DatabaseShow(
|
||||||
|
"Backend_Seiten",
|
||||||
|
"ID",
|
||||||
|
"DATEINAME = ?",
|
||||||
|
$sitename
|
||||||
|
);
|
||||||
|
return $id[0]['ID'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get user id
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param string $lastname
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
private function getUserID($name, $lastname) {
|
||||||
|
$id = DatabaseShow(
|
||||||
|
"Benutzer",
|
||||||
|
"ID",
|
||||||
|
"NAME = ? AND NACHNAME = ?",
|
||||||
|
$name,
|
||||||
|
$lastname
|
||||||
|
);
|
||||||
|
return $id[0]['ID'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rights Handler for saving default user is Bruno
|
||||||
|
*
|
||||||
|
* @param string $sitename
|
||||||
|
* @param string $name
|
||||||
|
* @param string $lastname
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function RightsHandler($sitename, $name = 'Bruno', $lastname = 'Eckhardt') {
|
||||||
|
try {
|
||||||
|
$sitename = strtolower($sitename);
|
||||||
|
$this->saveSite($sitename);
|
||||||
|
|
||||||
|
DatabaseInsert(
|
||||||
|
"Benutzer_Berechtigungen",
|
||||||
|
"BENUTZER_ID, SEITEN_ID",
|
||||||
|
$this->getUserID($name, $lastname),
|
||||||
|
$this->getSiteID($sitename)
|
||||||
|
);
|
||||||
|
} catch (Exception $error) {
|
||||||
|
return json_encode(
|
||||||
|
[
|
||||||
|
"status" => "failed",
|
||||||
|
"error" => $error->getMessage()
|
||||||
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return json_encode(
|
||||||
|
[
|
||||||
|
"status" => "success",
|
||||||
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+437
-112
@@ -1,29 +1,69 @@
|
|||||||
<?php
|
<?php
|
||||||
// API Tester Service - Als Modul für index.php
|
// ======================================================
|
||||||
// Dieser Service dient zum manuellen Testen der API-Endpunkte.
|
// API TESTER
|
||||||
|
// Modul für index.php
|
||||||
|
// ======================================================
|
||||||
|
|
||||||
// Basis-Verzeichnisse scannen (Pfad relativ zur index.php)
|
// API Basis-Verzeichnis
|
||||||
$apiDir = __DIR__ . '/../api/';
|
$apiDir = realpath(__DIR__ . '/../api');
|
||||||
$apiFiles = glob($apiDir . '*.php');
|
|
||||||
|
// Alle PHP APIs rekursiv finden
|
||||||
$apiOptions = [];
|
$apiOptions = [];
|
||||||
foreach ($apiFiles as $file) {
|
|
||||||
$apiOptions[] = 'api/' . basename($file);
|
if ($apiDir !== false) {
|
||||||
|
|
||||||
|
$iterator = new RecursiveIteratorIterator(
|
||||||
|
new RecursiveDirectoryIterator(
|
||||||
|
$apiDir,
|
||||||
|
RecursiveDirectoryIterator::SKIP_DOTS
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($iterator as $file) {
|
||||||
|
|
||||||
|
if (!$file->isFile()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($file->getExtension() !== 'php') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// relativen Pfad erzeugen
|
||||||
|
$relativePath = str_replace(
|
||||||
|
$apiDir . DIRECTORY_SEPARATOR,
|
||||||
|
'',
|
||||||
|
$file->getPathname()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Windows kompatibel
|
||||||
|
$relativePath = str_replace('\\', '/', $relativePath);
|
||||||
|
|
||||||
|
$apiOptions[] = 'api/' . $relativePath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Token aus deiner get.php entnommen
|
// sortieren
|
||||||
|
sort($apiOptions);
|
||||||
|
|
||||||
|
// Standard Token
|
||||||
$defaultToken = "15a437d128c918633e57dc7d18118a073b807644f21731ed5a7ebcf3eca9e242";
|
$defaultToken = "15a437d128c918633e57dc7d18118a073b807644f21731ed5a7ebcf3eca9e242";
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Spezifische Styles für den API Tester innerhalb des Content-Bereichs */
|
/* ======================================================
|
||||||
|
API TESTER STYLES
|
||||||
|
====================================================== */
|
||||||
|
|
||||||
.api-tester-container {
|
.api-tester-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 900px;
|
max-width: 1000px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background-color: var(--card-bg);
|
background-color: var(--card-bg);
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
border-radius: 10px;
|
border-radius: 12px;
|
||||||
border: 1px solid var(--card-border);
|
border: 1px solid var(--card-border);
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.api-tester-container h2 {
|
.api-tester-container h2 {
|
||||||
@@ -31,83 +71,8 @@ $defaultToken = "15a437d128c918633e57dc7d18118a073b807644f21731ed5a7ebcf3eca9e24
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
color: var(--btn-bg);
|
color: var(--btn-bg);
|
||||||
border-bottom: 1px solid var(--card-border);
|
border-bottom: 1px solid var(--card-border);
|
||||||
padding-bottom: 10px;
|
padding-bottom: 12px;
|
||||||
display: block;
|
display: block;
|
||||||
/* style.css h2 hat flex, wir wollen hier einfaches block */
|
|
||||||
}
|
|
||||||
|
|
||||||
.tester-form-group {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tester-form-group label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tester-input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: var(--input-bg);
|
|
||||||
border: 1px solid var(--input-border);
|
|
||||||
color: var(--input-text);
|
|
||||||
border-radius: 5px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tester-btn-send {
|
|
||||||
background-color: var(--btn-bg);
|
|
||||||
color: white;
|
|
||||||
padding: 12px 20px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: bold;
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tester-btn-send:hover {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tester-response-container {
|
|
||||||
margin-top: 25px;
|
|
||||||
background-color: var(--input-bg);
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid var(--card-border);
|
|
||||||
min-height: 80px;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tester-output-content {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
font-family: 'Consolas', 'Monaco', monospace;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
margin: 0;
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tester-badge {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 3px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tester-status-200 {
|
|
||||||
background-color: var(--status-bg);
|
|
||||||
color: var(--status-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tester-status-error {
|
|
||||||
background-color: #dc3545;
|
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tester-grid {
|
.tester-grid {
|
||||||
@@ -116,140 +81,500 @@ $defaultToken = "15a437d128c918633e57dc7d18118a073b807644f21731ed5a7ebcf3eca9e24
|
|||||||
gap: 15px;
|
gap: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
.tester-form-group {
|
||||||
.tester-grid {
|
margin-bottom: 16px;
|
||||||
grid-template-columns: 1fr;
|
}
|
||||||
}
|
|
||||||
|
.tester-form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tester-input,
|
||||||
|
.tester-textarea,
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: var(--input-bg);
|
||||||
|
border: 1px solid var(--input-border);
|
||||||
|
color: var(--input-text);
|
||||||
|
border-radius: 6px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tester-textarea {
|
||||||
|
min-height: 120px;
|
||||||
|
resize: vertical;
|
||||||
|
font-family: Consolas, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tester-btn-send {
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--btn-bg);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 13px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.2s;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tester-btn-send:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#tester-response-container {
|
||||||
|
margin-top: 25px;
|
||||||
|
background-color: var(--input-bg);
|
||||||
|
border: 1px solid var(--card-border);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
min-height: 100px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tester-output-content {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
font-family: Consolas, monospace;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tester-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tester-status-200 {
|
||||||
|
background-color: #198754;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tester-status-error {
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tester-status-warning {
|
||||||
|
background-color: #ffc107;
|
||||||
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tester-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tester-checkbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tester-small-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--input-border);
|
||||||
|
color: var(--text);
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tester-small-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tester-response-info {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
|
||||||
|
.tester-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tester-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="api-tester-container">
|
<div class="api-tester-container">
|
||||||
|
|
||||||
<h2>🛠️ API Tester</h2>
|
<h2>🛠️ API Tester</h2>
|
||||||
|
|
||||||
|
<!-- API Auswahl -->
|
||||||
<div class="tester-form-group">
|
<div class="tester-form-group">
|
||||||
<label for="endpoint">API Endpunkt</label>
|
<label for="endpoint">API Endpunkt</label>
|
||||||
|
|
||||||
<select id="endpoint" class="tester-input" onchange="checkEndpoint()">
|
<select id="endpoint" class="tester-input" onchange="checkEndpoint()">
|
||||||
|
|
||||||
<?php foreach ($apiOptions as $opt): ?>
|
<?php foreach ($apiOptions as $opt): ?>
|
||||||
<option value="<?= $opt ?>"><?= $opt ?></option>
|
|
||||||
|
<option value="<?= htmlspecialchars($opt) ?>">
|
||||||
|
<?= htmlspecialchars($opt) ?>
|
||||||
|
</option>
|
||||||
|
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Methode + Function -->
|
||||||
<div class="tester-grid">
|
<div class="tester-grid">
|
||||||
|
|
||||||
<div class="tester-form-group">
|
<div class="tester-form-group">
|
||||||
<label for="method">HTTP Methode</label>
|
<label for="method">HTTP Methode</label>
|
||||||
|
|
||||||
<select id="method" class="tester-input">
|
<select id="method" class="tester-input">
|
||||||
<option value="GET">GET</option>
|
<option value="GET">GET</option>
|
||||||
<option value="POST">POST</option>
|
<option value="POST">POST</option>
|
||||||
|
<option value="PUT">PUT</option>
|
||||||
|
<option value="DELETE">DELETE</option>
|
||||||
|
<option value="PATCH">PATCH</option>
|
||||||
<option value="OPTIONS">OPTIONS</option>
|
<option value="OPTIONS">OPTIONS</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tester-form-group" id="function-group">
|
<div class="tester-form-group" id="function-group">
|
||||||
<label for="function-header">Function Header</label>
|
<label for="function-header">Function Header</label>
|
||||||
|
|
||||||
<input type="text" id="function-header" class="tester-input" placeholder="z.B. IPs, Ueberstunden">
|
<input type="text" id="function-header" class="tester-input" placeholder="z.B. IPs, Ueberstunden">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Token -->
|
||||||
<div class="tester-form-group">
|
<div class="tester-form-group">
|
||||||
|
|
||||||
<label for="token">Authorization Token</label>
|
<label for="token">Authorization Token</label>
|
||||||
<input type="text" id="token" class="tester-input" value="<?= $defaultToken ?>">
|
|
||||||
|
<div class="tester-row">
|
||||||
|
|
||||||
|
<input type="text" id="token" class="tester-input" value="<?= htmlspecialchars($defaultToken) ?>">
|
||||||
|
|
||||||
|
<label class="tester-checkbox">
|
||||||
|
<input type="checkbox" id="send-token" checked>
|
||||||
|
Mitsenden
|
||||||
|
</label>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Eigene Header -->
|
||||||
|
<div class="tester-form-group">
|
||||||
|
|
||||||
|
<label for="custom-headers">
|
||||||
|
Zusätzliche Header (JSON)
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<textarea id="custom-headers" class="tester-textarea" placeholder='{
|
||||||
|
"X-Test": "123",
|
||||||
|
"Client": "Frontend"
|
||||||
|
}'></textarea>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- JSON Body -->
|
||||||
|
<div class="tester-form-group" id="json-body-group">
|
||||||
|
|
||||||
|
<label for="json-body">
|
||||||
|
JSON Body
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<textarea id="json-body" class="tester-textarea" placeholder='{
|
||||||
|
"test": true
|
||||||
|
}'></textarea>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Datei Upload -->
|
||||||
<div class="tester-form-group hidden" id="file-group">
|
<div class="tester-form-group hidden" id="file-group">
|
||||||
<label for="files">Dateien (files)</label>
|
|
||||||
|
<label for="files">Dateien Upload</label>
|
||||||
|
|
||||||
<input type="file" id="files" class="tester-input" multiple>
|
<input type="file" id="files" class="tester-input" multiple>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="tester-btn-send" onclick="sendApiRequest()">Anfrage senden 🚀</button>
|
<!-- Buttons -->
|
||||||
|
<div class="tester-grid">
|
||||||
|
|
||||||
|
<button class="tester-btn-send" onclick="sendApiRequest()">
|
||||||
|
Anfrage senden 🚀
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="tester-btn-send" onclick="clearResponse()">
|
||||||
|
Ausgabe leeren 🧹
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Ausgabe -->
|
||||||
<div id="tester-response-container">
|
<div id="tester-response-container">
|
||||||
<div style="text-align: center; color: #888; padding: 10px;">Bereit für Anfragen.</div>
|
|
||||||
|
<div style="text-align:center; opacity:0.7; padding:15px;">
|
||||||
|
Bereit für Anfragen.
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// ======================================================
|
||||||
|
// CHECK ENDPOINT
|
||||||
|
// ======================================================
|
||||||
|
|
||||||
function checkEndpoint() {
|
function checkEndpoint() {
|
||||||
|
|
||||||
const endpoint = document.getElementById('endpoint').value;
|
const endpoint = document.getElementById('endpoint').value;
|
||||||
|
|
||||||
const fileGroup = document.getElementById('file-group');
|
const fileGroup = document.getElementById('file-group');
|
||||||
const functionGroup = document.getElementById('function-group');
|
const functionGroup = document.getElementById('function-group');
|
||||||
|
const jsonBodyGroup = document.getElementById('json-body-group');
|
||||||
const methodSelect = document.getElementById('method');
|
const methodSelect = document.getElementById('method');
|
||||||
|
|
||||||
// Standard: alles ausblenden
|
// reset
|
||||||
fileGroup.classList.add('hidden');
|
fileGroup.classList.add('hidden');
|
||||||
functionGroup.classList.remove('hidden');
|
functionGroup.classList.remove('hidden');
|
||||||
|
jsonBodyGroup.classList.remove('hidden');
|
||||||
|
|
||||||
|
// upload api
|
||||||
|
if (endpoint.includes('upload.php')) {
|
||||||
|
|
||||||
if (endpoint === 'api/upload.php') {
|
|
||||||
// Nur bei exakt api/upload.php Dateien anzeigen
|
|
||||||
fileGroup.classList.remove('hidden');
|
fileGroup.classList.remove('hidden');
|
||||||
functionGroup.classList.add('hidden');
|
jsonBodyGroup.classList.add('hidden');
|
||||||
|
|
||||||
methodSelect.value = 'POST';
|
methodSelect.value = 'POST';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endpoint === 'api/get.php') {
|
// get api
|
||||||
|
if (endpoint.includes('get.php')) {
|
||||||
methodSelect.value = 'GET';
|
methodSelect.value = 'GET';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ======================================================
|
||||||
|
// CLEAR RESPONSE
|
||||||
|
// ======================================================
|
||||||
|
|
||||||
|
function clearResponse() {
|
||||||
|
|
||||||
|
document.getElementById('tester-response-container').innerHTML = `
|
||||||
|
<div style="text-align:center; opacity:0.7; padding:15px;">
|
||||||
|
Ausgabe geleert.
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================================================
|
||||||
|
// SEND REQUEST
|
||||||
|
// ======================================================
|
||||||
|
|
||||||
async function sendApiRequest() {
|
async function sendApiRequest() {
|
||||||
|
|
||||||
const endpoint = document.getElementById('endpoint').value;
|
const endpoint = document.getElementById('endpoint').value;
|
||||||
const method = document.getElementById('method').value;
|
const method = document.getElementById('method').value;
|
||||||
const token = document.getElementById('token').value;
|
const token = document.getElementById('token').value;
|
||||||
const functionHeader = document.getElementById('function-header').value;
|
const functionHeader = document.getElementById('function-header').value;
|
||||||
|
const customHeadersInput = document.getElementById('custom-headers').value;
|
||||||
|
const jsonBodyInput = document.getElementById('json-body').value;
|
||||||
const fileInput = document.getElementById('files');
|
const fileInput = document.getElementById('files');
|
||||||
|
const sendToken = document.getElementById('send-token').checked;
|
||||||
|
|
||||||
const responseContainer = document.getElementById('tester-response-container');
|
const responseContainer = document.getElementById('tester-response-container');
|
||||||
|
|
||||||
responseContainer.innerHTML = '<div style="text-align: center; padding: 10px;">Lade...</div>';
|
responseContainer.innerHTML = `
|
||||||
|
<div style="text-align:center; padding:15px;">
|
||||||
|
Anfrage läuft...
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
let headers = {
|
let headers = {};
|
||||||
'Authorization': token
|
|
||||||
};
|
|
||||||
|
|
||||||
if (endpoint.includes('get.php') && functionHeader) {
|
// Token
|
||||||
|
if (sendToken && token.trim() !== '') {
|
||||||
|
headers['Authorization'] = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function Header
|
||||||
|
if (functionHeader.trim() !== '') {
|
||||||
headers['Function'] = functionHeader;
|
headers['Function'] = functionHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom Headers
|
||||||
|
if (customHeadersInput.trim() !== '') {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const customHeaders = JSON.parse(customHeadersInput);
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
...headers,
|
||||||
|
...customHeaders
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
responseContainer.innerHTML = `
|
||||||
|
<div style="color:#dc3545; padding:15px;">
|
||||||
|
Fehlerhafte Header JSON Struktur.
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let options = {
|
let options = {
|
||||||
method: method,
|
method: method,
|
||||||
headers: headers
|
headers: headers
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Upload
|
||||||
if (endpoint.includes('upload.php') && fileInput.files.length > 0) {
|
if (endpoint.includes('upload.php') && fileInput.files.length > 0) {
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
||||||
for (let i = 0; i < fileInput.files.length; i++) {
|
for (let i = 0; i < fileInput.files.length; i++) {
|
||||||
formData.append('files[]', fileInput.files[i]);
|
formData.append('files[]', fileInput.files[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
options.body = formData;
|
options.body = formData;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// JSON Body
|
||||||
|
if (
|
||||||
|
['POST', 'PUT', 'PATCH', 'DELETE']
|
||||||
|
.includes(method) &&
|
||||||
|
jsonBodyInput.trim() !== ''
|
||||||
|
) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
JSON.parse(jsonBodyInput);
|
||||||
|
|
||||||
|
headers['Content-Type'] = 'application/json';
|
||||||
|
|
||||||
|
options.body = jsonBodyInput;
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
responseContainer.innerHTML = `
|
||||||
|
<div style="color:#dc3545; padding:15px;">
|
||||||
|
Fehlerhafte JSON Body Struktur.
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const startedAt = performance.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const response = await fetch(endpoint, options);
|
const response = await fetch(endpoint, options);
|
||||||
|
|
||||||
|
const duration = (performance.now() - startedAt).toFixed(0);
|
||||||
|
|
||||||
const status = response.status;
|
const status = response.status;
|
||||||
|
|
||||||
const rawText = await response.text();
|
const rawText = await response.text();
|
||||||
|
|
||||||
let resultText = rawText;
|
let resultText = rawText;
|
||||||
|
|
||||||
|
// JSON hübsch formatieren
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const json = JSON.parse(rawText);
|
const json = JSON.parse(rawText);
|
||||||
|
|
||||||
resultText = JSON.stringify(json, null, 2);
|
resultText = JSON.stringify(json, null, 2);
|
||||||
|
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
const badgeClass = status >= 200 && status < 300 ? 'tester-status-200' : 'tester-status-error';
|
let badgeClass = 'tester-status-error';
|
||||||
|
|
||||||
|
if (status >= 200 && status < 300) {
|
||||||
|
badgeClass = 'tester-status-200';
|
||||||
|
} else if (status >= 300 && status < 400) {
|
||||||
|
badgeClass = 'tester-status-warning';
|
||||||
|
}
|
||||||
|
|
||||||
responseContainer.innerHTML = `
|
responseContainer.innerHTML = `
|
||||||
<div class="tester-badge ${badgeClass}">Status: ${status}</div>
|
<div class="tester-badge ${badgeClass}">
|
||||||
<pre id="tester-output-content"></pre>
|
Status: ${status}
|
||||||
`;
|
</div>
|
||||||
|
|
||||||
|
<div class="tester-response-info">
|
||||||
|
⏱️ ${duration} ms
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="tester-small-btn"
|
||||||
|
onclick="copyResponse()"
|
||||||
|
>
|
||||||
|
Antwort kopieren
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<pre id="tester-output-content"></pre>
|
||||||
|
`;
|
||||||
|
|
||||||
document.getElementById('tester-output-content').textContent = resultText;
|
document.getElementById('tester-output-content').textContent = resultText;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
responseContainer.innerHTML = `<div style="color:#dc3545; padding: 10px;">Fehler: ${error.message}</div>`;
|
|
||||||
|
responseContainer.innerHTML = `
|
||||||
|
<div style="color:#dc3545; padding:15px;">
|
||||||
|
Fehler: ${error.message}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initial check
|
// ======================================================
|
||||||
|
// COPY RESPONSE
|
||||||
|
// ======================================================
|
||||||
|
|
||||||
|
function copyResponse() {
|
||||||
|
|
||||||
|
const content = document.getElementById('tester-output-content');
|
||||||
|
|
||||||
|
if (!content) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(content.textContent);
|
||||||
|
|
||||||
|
alert('Antwort kopiert.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial
|
||||||
checkEndpoint();
|
checkEndpoint();
|
||||||
</script>
|
</script>
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
include_once(__DIR__ . '/../controller/RightsController.php');
|
||||||
|
include_once(__DIR__ . '/../config.php');
|
||||||
|
|
||||||
$configFile = 'config.php';
|
$configFile = 'config.php';
|
||||||
$backupDir = 'config_backups';
|
$backupDir = 'config_backups';
|
||||||
|
|
||||||
@@ -47,6 +50,7 @@ if (isset($_POST['save']) && ($_SESSION['logged_in'] ?? false)) {
|
|||||||
$serviceToGroupMapping[$s['targetGroup']][] = $finalId;
|
$serviceToGroupMapping[$s['targetGroup']][] = $finalId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
(new RightsController())->RightsHandler($s['name']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$newServiceGroups = [];
|
$newServiceGroups = [];
|
||||||
|
|||||||
Reference in new issue
Block a user