Update
This commit is contained in:
parent
aece0cb92a
commit
2ef4de0dba
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="PhpProjectSharedConfiguration" php_language_level="8.0">
|
<component name="PhpProjectSharedConfiguration" php_language_level="8.1">
|
||||||
<option name="suggestChangeDefaultLanguageLevel" value="false" />
|
<option name="suggestChangeDefaultLanguageLevel" value="false" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
@ -9,7 +9,7 @@ use Core\Objects\Search\SearchQuery;
|
|||||||
|
|
||||||
class Search extends Request {
|
class Search extends Request {
|
||||||
|
|
||||||
public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
|
public function __construct(Context $context, bool $externalCall = false) {
|
||||||
parent::__construct($context, $externalCall, [
|
parent::__construct($context, $externalCall, [
|
||||||
"text" => new StringType("text", 32)
|
"text" => new StringType("text", 32)
|
||||||
]);
|
]);
|
||||||
|
@ -155,37 +155,4 @@ namespace Core\API\Settings {
|
|||||||
$insert->addRow(self::getEndpoint(), [Group::ADMIN], "Allows users to modify site settings");
|
$insert->addRow(self::getEndpoint(), [Group::ADMIN], "Allows users to modify site settings");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GenerateJWT extends SettingsAPI {
|
|
||||||
|
|
||||||
public function __construct(Context $context, bool $externalCall = false) {
|
|
||||||
parent::__construct($context, $externalCall, [
|
|
||||||
"type" => new StringType("type", 32, true, "HS512")
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function _execute(): bool {
|
|
||||||
$algorithm = $this->getParam("type");
|
|
||||||
if (!Settings::isJwtAlgorithmSupported($algorithm)) {
|
|
||||||
return $this->createError("Algorithm is not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
$settings = $this->context->getSettings();
|
|
||||||
if (!$settings->generateJwtKey($algorithm)) {
|
|
||||||
return $this->createError("Error generating JWT-Key: " . $settings->getLogger()->getLastMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
$saveRequest = $settings->saveJwtKey($this->context);
|
|
||||||
if (!$saveRequest->success()) {
|
|
||||||
return $this->createError("Error saving JWT-Key: " . $saveRequest->getLastError());
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->result["jwt_public_key"] = $settings->getJwtPublicKey(false)?->getKeyMaterial();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getDefaultACL(Insert $insert): void {
|
|
||||||
$insert->addRow(self::getEndpoint(), [Group::ADMIN], "Allows users to regenerate the JWT key. This invalidates all sessions");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -26,11 +26,6 @@ class Settings {
|
|||||||
private array $allowedExtensions;
|
private array $allowedExtensions;
|
||||||
private string $timeZone;
|
private string $timeZone;
|
||||||
|
|
||||||
// jwt
|
|
||||||
private ?string $jwtPublicKey;
|
|
||||||
private ?string $jwtSecretKey;
|
|
||||||
private string $jwtAlgorithm;
|
|
||||||
|
|
||||||
// recaptcha
|
// recaptcha
|
||||||
private bool $recaptchaEnabled;
|
private bool $recaptchaEnabled;
|
||||||
private string $recaptchaPublicKey;
|
private string $recaptchaPublicKey;
|
||||||
@ -84,22 +79,6 @@ class Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getJwtPublicKey(bool $allowPrivate = true): ?\Firebase\JWT\Key {
|
|
||||||
if (empty($this->jwtPublicKey)) {
|
|
||||||
if ($allowPrivate && $this->jwtSecretKey) {
|
|
||||||
// we might have a symmetric key, should we instead return the private key?
|
|
||||||
return new \Firebase\JWT\Key($this->jwtSecretKey, $this->jwtAlgorithm);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return new \Firebase\JWT\Key($this->jwtPublicKey, $this->jwtAlgorithm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getJwtSecretKey(): ?\Firebase\JWT\Key {
|
|
||||||
return $this->jwtSecretKey ? new \Firebase\JWT\Key($this->jwtSecretKey, $this->jwtAlgorithm) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isInstalled(): bool {
|
public function isInstalled(): bool {
|
||||||
return $this->installationComplete;
|
return $this->installationComplete;
|
||||||
}
|
}
|
||||||
@ -124,11 +103,6 @@ class Settings {
|
|||||||
$settings->registrationAllowed = false;
|
$settings->registrationAllowed = false;
|
||||||
$settings->timeZone = date_default_timezone_get();
|
$settings->timeZone = date_default_timezone_get();
|
||||||
|
|
||||||
// JWT
|
|
||||||
$settings->jwtSecretKey = null;
|
|
||||||
$settings->jwtPublicKey = null;
|
|
||||||
$settings->jwtAlgorithm = "HS256";
|
|
||||||
|
|
||||||
// Recaptcha
|
// Recaptcha
|
||||||
$settings->recaptchaEnabled = false;
|
$settings->recaptchaEnabled = false;
|
||||||
$settings->recaptchaPublicKey = "";
|
$settings->recaptchaPublicKey = "";
|
||||||
@ -143,50 +117,6 @@ class Settings {
|
|||||||
return $settings;
|
return $settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generateJwtKey(string $algorithm = null): bool {
|
|
||||||
$this->jwtAlgorithm = $algorithm ?? $this->jwtAlgorithm;
|
|
||||||
|
|
||||||
// TODO: key encryption necessary?
|
|
||||||
if (in_array($this->jwtAlgorithm, ["HS256", "HS384", "HS512"])) {
|
|
||||||
$this->jwtSecretKey = generateRandomString(32);
|
|
||||||
$this->jwtPublicKey = null;
|
|
||||||
} else if (in_array($this->jwtAlgorithm, ["RS256", "RS384", "RS512"])) {
|
|
||||||
$bits = intval(substr($this->jwtAlgorithm, 2));
|
|
||||||
$private_key = openssl_pkey_new(["private_key_bits" => $bits]);
|
|
||||||
$this->jwtPublicKey = openssl_pkey_get_details($private_key)['key'];
|
|
||||||
openssl_pkey_export($private_key, $this->jwtSecretKey);
|
|
||||||
} else if (in_array($this->jwtAlgorithm, ["ES256", "ES384"])) {
|
|
||||||
// $ec = new \Elliptic\EC('secp256k1'); ??
|
|
||||||
$this->logger->error("JWT algorithm: '$this->jwtAlgorithm' is currently not supported.");
|
|
||||||
return false;
|
|
||||||
} else if ($this->jwtAlgorithm == "EdDSA") {
|
|
||||||
$keyPair = sodium_crypto_sign_keypair();
|
|
||||||
$this->jwtSecretKey = base64_encode(sodium_crypto_sign_secretkey($keyPair));
|
|
||||||
$this->jwtPublicKey = base64_encode(sodium_crypto_sign_publickey($keyPair));
|
|
||||||
} else {
|
|
||||||
$this->logger->error("Invalid JWT algorithm: '$this->jwtAlgorithm', expected one of: " .
|
|
||||||
implode(",", array_keys(\Firebase\JWT\JWT::$supported_algs)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isJwtAlgorithmSupported(string $algorithm): bool {
|
|
||||||
return in_array(strtoupper($algorithm), ["HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "EDDSA"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function saveJwtKey(Context $context): \Core\API\Settings\Set {
|
|
||||||
$req = new \Core\API\Settings\Set($context);
|
|
||||||
$req->execute(array("settings" => array(
|
|
||||||
"jwt_secret_key" => $this->jwtSecretKey,
|
|
||||||
"jwt_public_key" => $this->jwtSecretKey,
|
|
||||||
"jwt_algorithm" => $this->jwtAlgorithm,
|
|
||||||
)));
|
|
||||||
|
|
||||||
return $req;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function loadFromDatabase(Context $context): bool {
|
public function loadFromDatabase(Context $context): bool {
|
||||||
$this->logger = new Logger("Settings", $context->getSQL());
|
$this->logger = new Logger("Settings", $context->getSQL());
|
||||||
$req = new \Core\API\Settings\Get($context);
|
$req = new \Core\API\Settings\Get($context);
|
||||||
@ -199,9 +129,6 @@ class Settings {
|
|||||||
$this->registrationAllowed = $result["user_registration_enabled"] ?? $this->registrationAllowed;
|
$this->registrationAllowed = $result["user_registration_enabled"] ?? $this->registrationAllowed;
|
||||||
$this->installationComplete = $result["installation_completed"] ?? $this->installationComplete;
|
$this->installationComplete = $result["installation_completed"] ?? $this->installationComplete;
|
||||||
$this->timeZone = $result["time_zone"] ?? $this->timeZone;
|
$this->timeZone = $result["time_zone"] ?? $this->timeZone;
|
||||||
$this->jwtSecretKey = $result["jwt_secret_key"] ?? $this->jwtSecretKey;
|
|
||||||
$this->jwtPublicKey = $result["jwt_public_key"] ?? $this->jwtPublicKey;
|
|
||||||
$this->jwtAlgorithm = $result["jwt_algorithm"] ?? $this->jwtAlgorithm;
|
|
||||||
$this->recaptchaEnabled = $result["recaptcha_enabled"] ?? $this->recaptchaEnabled;
|
$this->recaptchaEnabled = $result["recaptcha_enabled"] ?? $this->recaptchaEnabled;
|
||||||
$this->recaptchaPublicKey = $result["recaptcha_public_key"] ?? $this->recaptchaPublicKey;
|
$this->recaptchaPublicKey = $result["recaptcha_public_key"] ?? $this->recaptchaPublicKey;
|
||||||
$this->recaptchaPrivateKey = $result["recaptcha_private_key"] ?? $this->recaptchaPrivateKey;
|
$this->recaptchaPrivateKey = $result["recaptcha_private_key"] ?? $this->recaptchaPrivateKey;
|
||||||
@ -210,13 +137,6 @@ class Settings {
|
|||||||
$this->mailFooter = $result["mail_footer"] ?? $this->mailFooter;
|
$this->mailFooter = $result["mail_footer"] ?? $this->mailFooter;
|
||||||
$this->mailAsync = $result["mail_async"] ?? $this->mailAsync;
|
$this->mailAsync = $result["mail_async"] ?? $this->mailAsync;
|
||||||
$this->allowedExtensions = explode(",", $result["allowed_extensions"] ?? strtolower(implode(",", $this->allowedExtensions)));
|
$this->allowedExtensions = explode(",", $result["allowed_extensions"] ?? strtolower(implode(",", $this->allowedExtensions)));
|
||||||
|
|
||||||
if (!isset($result["jwt_secret_key"])) {
|
|
||||||
if ($this->generateJwtKey()) {
|
|
||||||
$this->saveJwtKey($context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
date_default_timezone_set($this->timeZone);
|
date_default_timezone_set($this->timeZone);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,9 +149,6 @@ class Settings {
|
|||||||
->addRow("user_registration_enabled", $this->registrationAllowed ? "1" : "0", false, false)
|
->addRow("user_registration_enabled", $this->registrationAllowed ? "1" : "0", false, false)
|
||||||
->addRow("installation_completed", $this->installationComplete ? "1" : "0", true, true)
|
->addRow("installation_completed", $this->installationComplete ? "1" : "0", true, true)
|
||||||
->addRow("time_zone", $this->timeZone, false, false)
|
->addRow("time_zone", $this->timeZone, false, false)
|
||||||
->addRow("jwt_secret_key", $this->jwtSecretKey, true, false)
|
|
||||||
->addRow("jwt_public_key", $this->jwtPublicKey, false, false)
|
|
||||||
->addRow("jwt_algorithm", $this->jwtAlgorithm, false, false)
|
|
||||||
->addRow("recaptcha_enabled", $this->recaptchaEnabled ? "1" : "0", false, false)
|
->addRow("recaptcha_enabled", $this->recaptchaEnabled ? "1" : "0", false, false)
|
||||||
->addRow("recaptcha_public_key", $this->recaptchaPublicKey, false, false)
|
->addRow("recaptcha_public_key", $this->recaptchaPublicKey, false, false)
|
||||||
->addRow("recaptcha_private_key", $this->recaptchaPrivateKey, true, false)
|
->addRow("recaptcha_private_key", $this->recaptchaPrivateKey, true, false)
|
||||||
|
1
Core/External/composer.json
vendored
1
Core/External/composer.json
vendored
@ -6,7 +6,6 @@
|
|||||||
"christian-riesen/base32": "^1.6",
|
"christian-riesen/base32": "^1.6",
|
||||||
"spomky-labs/cbor-php": "2.1.0",
|
"spomky-labs/cbor-php": "2.1.0",
|
||||||
"web-auth/cose-lib": "3.3.12",
|
"web-auth/cose-lib": "3.3.12",
|
||||||
"firebase/php-jwt": "^6.2",
|
|
||||||
"html2text/html2text": "^4.3"
|
"html2text/html2text": "^4.3"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
@ -32,6 +32,7 @@ return [
|
|||||||
"not_supported" => "Nicht unterstützt",
|
"not_supported" => "Nicht unterstützt",
|
||||||
"yes" => "Ja",
|
"yes" => "Ja",
|
||||||
"no" => "Nein",
|
"no" => "Nein",
|
||||||
|
"create_new" => "Erstellen",
|
||||||
|
|
||||||
# dialog / actions
|
# dialog / actions
|
||||||
"action" => "Aktion",
|
"action" => "Aktion",
|
||||||
|
@ -15,6 +15,7 @@ return [
|
|||||||
"not_supported" => "Not supported",
|
"not_supported" => "Not supported",
|
||||||
"yes" => "Yes",
|
"yes" => "Yes",
|
||||||
"no" => "No",
|
"no" => "No",
|
||||||
|
"create_new" => "Create",
|
||||||
|
|
||||||
# dialog / actions
|
# dialog / actions
|
||||||
"action" => "Action",
|
"action" => "Action",
|
||||||
|
@ -9,7 +9,6 @@ use Core\Driver\SQL\Condition\CondLike;
|
|||||||
use Core\Driver\SQL\Condition\CondOr;
|
use Core\Driver\SQL\Condition\CondOr;
|
||||||
use Core\Driver\SQL\Join\InnerJoin;
|
use Core\Driver\SQL\Join\InnerJoin;
|
||||||
use Core\Driver\SQL\SQL;
|
use Core\Driver\SQL\SQL;
|
||||||
use Firebase\JWT\JWT;
|
|
||||||
use Core\Objects\DatabaseEntity\Language;
|
use Core\Objects\DatabaseEntity\Language;
|
||||||
use Core\Objects\DatabaseEntity\Session;
|
use Core\Objects\DatabaseEntity\Session;
|
||||||
use Core\Objects\DatabaseEntity\User;
|
use Core\Objects\DatabaseEntity\User;
|
||||||
@ -99,28 +98,14 @@ class Context {
|
|||||||
session_write_close();
|
session_write_close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function loadSession(int $userId, int $sessionId): void {
|
private function loadSession(string $sessionUUID): void {
|
||||||
$this->session = Session::init($this, $userId, $sessionId);
|
$this->session = Session::init($this, $sessionUUID);
|
||||||
$this->user = $this->session?->getUser();
|
$this->user = $this->session?->getUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function parseCookies(): void {
|
public function parseCookies(): void {
|
||||||
if (isset($_COOKIE['session']) && is_string($_COOKIE['session']) && !empty($_COOKIE['session'])) {
|
if (isset($_COOKIE['session']) && is_string($_COOKIE['session']) && !empty($_COOKIE['session'])) {
|
||||||
try {
|
$this->loadSession($_COOKIE['session']);
|
||||||
$token = $_COOKIE['session'];
|
|
||||||
$settings = $this->configuration->getSettings();
|
|
||||||
$jwtKey = $settings->getJwtSecretKey();
|
|
||||||
if ($jwtKey) {
|
|
||||||
$decoded = (array)JWT::decode($token, $jwtKey);
|
|
||||||
$userId = ($decoded['userId'] ?? NULL);
|
|
||||||
$sessionId = ($decoded['sessionId'] ?? NULL);
|
|
||||||
if (!is_null($userId) && !is_null($sessionId)) {
|
|
||||||
$this->loadSession($userId, $sessionId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// set language by priority: 1. GET parameter, 2. cookie, 3. user's settings
|
// set language by priority: 1. GET parameter, 2. cookie, 3. user's settings
|
||||||
|
@ -4,7 +4,6 @@ namespace Core\Objects\DatabaseEntity;
|
|||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Firebase\JWT\JWT;
|
|
||||||
use Core\Objects\Context;
|
use Core\Objects\Context;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\Json;
|
use Core\Objects\DatabaseEntity\Attribute\Json;
|
||||||
@ -21,6 +20,7 @@ class Session extends DatabaseEntity {
|
|||||||
private User $user;
|
private User $user;
|
||||||
private DateTime $expires;
|
private DateTime $expires;
|
||||||
#[MaxLength(45)] private string $ipAddress;
|
#[MaxLength(45)] private string $ipAddress;
|
||||||
|
#[MaxLength(36)] private string $uuid;
|
||||||
#[DefaultValue(true)] private bool $active;
|
#[DefaultValue(true)] private bool $active;
|
||||||
#[MaxLength(64)] private ?string $os;
|
#[MaxLength(64)] private ?string $os;
|
||||||
#[MaxLength(64)] private ?string $browser;
|
#[MaxLength(64)] private ?string $browser;
|
||||||
@ -32,15 +32,21 @@ class Session extends DatabaseEntity {
|
|||||||
parent::__construct();
|
parent::__construct();
|
||||||
$this->context = $context;
|
$this->context = $context;
|
||||||
$this->user = $user;
|
$this->user = $user;
|
||||||
|
$this->uuid = uuidv4();
|
||||||
$this->stayLoggedIn = false;
|
$this->stayLoggedIn = false;
|
||||||
$this->csrfToken = $csrfToken ?? generateRandomString(16);
|
$this->csrfToken = $csrfToken ?? generateRandomString(16);
|
||||||
$this->expires = (new DateTime())->modify(sprintf("+%d second", Session::DURATION));
|
$this->expires = (new DateTime())->modify(sprintf("+%d second", Session::DURATION));
|
||||||
$this->active = true;
|
$this->active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function init(Context $context, int $userId, int $sessionId): ?Session {
|
public static function init(Context $context, string $sessionUUID): ?Session {
|
||||||
$session = Session::find($context->getSQL(), $sessionId, true, true);
|
$sql = $context->getSQL();
|
||||||
if (!$session || !$session->active || $session->user->getId() !== $userId) {
|
$session = Session::findBy(Session::createBuilder($sql, true)
|
||||||
|
->fetchEntities(true)
|
||||||
|
->whereEq("Session.uuid", $sessionUUID)
|
||||||
|
->whereTrue("Session.active")
|
||||||
|
->whereGt("Session.expires", $sql->now()));
|
||||||
|
if (!$session) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,18 +88,13 @@ class Session extends DatabaseEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCookie(): string {
|
public function getUUID(): string {
|
||||||
$this->updateMetaData();
|
return $this->uuid;
|
||||||
$settings = $this->context->getSettings();
|
|
||||||
$token = ['userId' => $this->user->getId(), 'sessionId' => $this->getId()];
|
|
||||||
$jwtPublicKey = $settings->getJwtPublicKey();
|
|
||||||
return JWT::encode($token, $jwtPublicKey->getKeyMaterial(), $jwtPublicKey->getAlgorithm());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sendCookie(string $domain) {
|
public function sendCookie(string $domain) {
|
||||||
$sessionCookie = $this->getCookie();
|
|
||||||
$secure = strcmp(getProtocol(), "https") === 0;
|
$secure = strcmp(getProtocol(), "https") === 0;
|
||||||
setcookie('session', $sessionCookie, $this->getExpiresTime(), "/", $domain, $secure, true);
|
setcookie('session', $this->uuid, $this->getExpiresTime(), "/", $domain, $secure, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getExpiresTime(): int {
|
public function getExpiresTime(): int {
|
||||||
|
86
README.md
86
README.md
@ -3,7 +3,7 @@
|
|||||||
Web-Base is a php framework which provides basic web functionalities and a modern ReactJS Admin Dashboard.
|
Web-Base is a php framework which provides basic web functionalities and a modern ReactJS Admin Dashboard.
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
- PHP >= 7.4
|
- PHP >= 8.1
|
||||||
- One of these php extensions: mysqli, postgres
|
- One of these php extensions: mysqli, postgres
|
||||||
- Apache/nginx or docker
|
- Apache/nginx or docker
|
||||||
|
|
||||||
@ -31,16 +31,23 @@ I actually don't know what i want to implement here. There are quite to many CMS
|
|||||||
3. Open the webapp in your browser and follow the installation guide
|
3. Open the webapp in your browser and follow the installation guide
|
||||||
|
|
||||||
### Docker Installation
|
### Docker Installation
|
||||||
1. `docker-compose up`
|
1. `docker-compose build`
|
||||||
2. Open the webapp in your browser and follow the installation guide
|
2. `docker-compose up`
|
||||||
|
3. Open the webapp in your browser and follow the installation guide
|
||||||
|
|
||||||
### Afterwards
|
### Afterwards
|
||||||
|
|
||||||
For any changes made in [/adminPanel](/adminPanel), run:
|
For any changes made in [/react](/react), run:
|
||||||
1. once: `npm i`
|
1. once: `yarn i`
|
||||||
2. build: `npm run build`
|
2. build: `yarn run build`
|
||||||
The compiled dist files will be automatically moved to `/js`.
|
The compiled dist files will be automatically moved to `/js`.
|
||||||
|
|
||||||
|
To spawn a temporary development server, run:
|
||||||
|
```bash
|
||||||
|
cd react
|
||||||
|
yarn workspace $project run dev
|
||||||
|
```
|
||||||
|
|
||||||
To fulfill the requirements of data deletion for **GDPR**, add the following line to your `/etc/crontab`
|
To fulfill the requirements of data deletion for **GDPR**, add the following line to your `/etc/crontab`
|
||||||
or any other cron file:
|
or any other cron file:
|
||||||
```
|
```
|
||||||
@ -51,29 +58,25 @@ or any other cron file:
|
|||||||
|
|
||||||
### Adding API-Endpoints
|
### Adding API-Endpoints
|
||||||
|
|
||||||
Each API endpoint has usually one overlying category, for example all user and authorization endpoints belong to the [UserAPI](/core/Api/UserAPI.class.php).
|
Each API endpoint has usually one overlying category, for example all user and authorization endpoints belong to the [UserAPI](/Core/API/UserAPI.class.php).
|
||||||
These endpoints can be accessed by requesting URLs starting with `/api/user`, for example: `/api/user/login`. There are also endpoints, which don't have
|
These endpoints can be accessed by requesting URLs starting with `/api/user`, for example: `/api/user/login`. There are also endpoints, which don't have
|
||||||
a category, e.g. [VerifyCaptcha](/core/Api/VerifyCaptcha.class.php). These functions can be called directly, for example with `/api/verifyCaptcha`. Both methods have one thing in common:
|
a category, e.g. [VerifyCaptcha](/Core/API/VerifyCaptcha.class.php). These functions can be called directly, for example with `/api/verifyCaptcha`. Both methods have one thing in common:
|
||||||
Each endpoint is represented by a class inheriting the [Request Class](/core/Api/Request.class.php). An example endpoint looks like this:
|
Each endpoint is represented by a class inheriting the [Request Class](/Core/API/Request.class.php). An example endpoint looks like this:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
namespace Api;
|
namespace Core\API;
|
||||||
use Core\API\Parameter\Parameter;
|
use Core\Objects\Context;
|
||||||
use Core\Objects\DatabaseEntity\User;
|
|
||||||
|
|
||||||
class SingleEndpoint extends Request {
|
class SingleEndpoint extends Request {
|
||||||
|
|
||||||
public function __construct(User $user, bool $externalCall = false) {
|
public function __construct(Context $context, bool $externalCall = false) {
|
||||||
parent::__construct($user, $externalCall, array(
|
parent::__construct($context, $externalCall, array(
|
||||||
"someParameter" => new Parameter("someParameter", Parameter::TYPE_INT, true, 100)
|
"someParameter" => new Parameter("someParameter", Parameter::TYPE_INT, true, 100)
|
||||||
));
|
));
|
||||||
$this->forbidMethod("POST");
|
$this->forbidMethod("POST");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute($values = array()): bool {
|
public function _execute(): bool {
|
||||||
if (!parent::execute($values)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$this->result['someAttribute'] = $this->getParam("someParameter") * 2;
|
$this->result['someAttribute'] = $this->getParam("someParameter") * 2;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -87,7 +90,7 @@ An endpoint consists of two important functions:
|
|||||||
To create an API category containing multiple endpoints, a parent class inheriting from `Request`, e.g. `class MultipleAPI extends Request` is required.
|
To create an API category containing multiple endpoints, a parent class inheriting from `Request`, e.g. `class MultipleAPI extends Request` is required.
|
||||||
All endpoints inside this category then inherit from the `MultipleAPI` class.
|
All endpoints inside this category then inherit from the `MultipleAPI` class.
|
||||||
|
|
||||||
The classes must be present inside the [Api](/core/Api) directory according to the other endpoints.
|
The classes must be present inside the [API](/Core/API) directory according to the other endpoints.
|
||||||
|
|
||||||
### Access Control
|
### Access Control
|
||||||
|
|
||||||
@ -100,7 +103,7 @@ By default, and if not further specified or restricted, all endpoints have the f
|
|||||||
6. All user groups can access the method (Database, Table: `ApiPermission`)
|
6. All user groups can access the method (Database, Table: `ApiPermission`)
|
||||||
|
|
||||||
The first five restrictions can be modified inside the constructor, while the group permissions are changed using
|
The first five restrictions can be modified inside the constructor, while the group permissions are changed using
|
||||||
the [Admin Dashboard](/adminPanel). It's default values are set inside the [database script](/core/Configuration/CreateDatabase.class.php).
|
the [Admin Dashboard](/react/admin-panel). It's default values are set inside the [database script](/Core/Configuration/CreateDatabase.class.php).
|
||||||
|
|
||||||
### Using the API internally
|
### Using the API internally
|
||||||
|
|
||||||
@ -109,11 +112,11 @@ can be used by creating the desired request object, and calling the execute func
|
|||||||
|
|
||||||
```php
|
```php
|
||||||
$req = new \Core\API\Mail\Send($context);
|
$req = new \Core\API\Mail\Send($context);
|
||||||
$success = $req->execute(array(
|
$success = $req->execute([
|
||||||
"to" => "mail@example.org",
|
"to" => "mail@example.org",
|
||||||
"subject" => "Example Mail",
|
"subject" => "Example Mail",
|
||||||
"body" => "This is an example mail"
|
"body" => "This is an example mail"
|
||||||
));
|
]);
|
||||||
|
|
||||||
if (!$success) {
|
if (!$success) {
|
||||||
echo $req->getLastError();
|
echo $req->getLastError();
|
||||||
@ -127,9 +130,9 @@ If any result is expected from the api call, the `$req->getResult()` method can
|
|||||||
|
|
||||||
This step is not really required, as and changes made to the database must not be presented inside the code.
|
This step is not really required, as and changes made to the database must not be presented inside the code.
|
||||||
On the other hand, it is recommended to keep track of any modifications for later use or to deploy the application
|
On the other hand, it is recommended to keep track of any modifications for later use or to deploy the application
|
||||||
to other systems. Therefore, either the [default installation script](/core/Configuration/CreateDatabase.class.php) or
|
to other systems. Therefore, either the [default installation script](/Core/Configuration/CreateDatabase.class.php) or
|
||||||
an additional patch file, which can be executed using the [CLI](#CLI), can be created. The patch files are usually
|
an additional patch file, which can be executed using the [CLI](#CLI), can be created. The patch files are usually
|
||||||
located in [/core/Configuration/Patch](/core/Configuration/Patch) and have the following structure:
|
located in [/Core/Configuration/Patch](/Core/Configuration/Patch) and have the following structure:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
namespace Core\Configuration\Patch;
|
namespace Core\Configuration\Patch;
|
||||||
@ -175,7 +178,7 @@ Secondly, it passes the second group (`$2`), which is all the text after the las
|
|||||||
A frontend page consists of a document, which again consists of a head and a body. Furthermore, a document can have various views, which have to be implemented
|
A frontend page consists of a document, which again consists of a head and a body. Furthermore, a document can have various views, which have to be implemented
|
||||||
programmatically. Usually, all pages inside a document look somehow similar, for example share a common side- or navbar, a header or a footer. If we think of a web-shop,
|
programmatically. Usually, all pages inside a document look somehow similar, for example share a common side- or navbar, a header or a footer. If we think of a web-shop,
|
||||||
we could have one document, when showing different articles and products, and a view for various pages, e.g. the dashboard with all the products, a single product view and so on.
|
we could have one document, when showing different articles and products, and a view for various pages, e.g. the dashboard with all the products, a single product view and so on.
|
||||||
To create a new document, a class inside [/core/Documents](/core/Documents) is created with the following scheme:
|
To create a new document, a class inside [/Core/Documents](/Core/Documents) is created with the following scheme:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
namespace Documents {
|
namespace Documents {
|
||||||
@ -242,38 +245,29 @@ Of course, the head and body classes can be placed in any file, as the code migh
|
|||||||
### Localization
|
### Localization
|
||||||
|
|
||||||
Currently, there are two languages specified, which are stored in the database: `en_US` and `de_DE`.
|
Currently, there are two languages specified, which are stored in the database: `en_US` and `de_DE`.
|
||||||
A language is dynamically loaded according to the sent `Accept-Language`-Header, but can also be set using the `lang` parameter
|
A language is dynamically loaded according to the `Accept-Language`-Header received, but can also be set using the `lang` parameter
|
||||||
or [/api/language/set](/core/Api/LanguageAPI.class.php) endpoint. Localization of strings can be achieved using the [LanguageModule](/core/Objects/lang/LanguageModule.php)-Class.
|
or [/api/language/set](/Core/API/LanguageAPI.class.php) endpoint.
|
||||||
Let's look at this example:
|
|
||||||
|
|
||||||
```php
|
|
||||||
class ExampleLangModule extends \Objects\lang\LanguageModule {
|
|
||||||
public function getEntries(string $langCode) {
|
|
||||||
$entries = array();
|
|
||||||
switch ($langCode) {
|
|
||||||
case 'de_DE':
|
|
||||||
$entries["EXAMPLE_KEY"] = "Das ist eine Beispielübersetzung";
|
|
||||||
$entries["Welcome"] = "Willkommen";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$entries["EXAMPLE_KEY"] = "This is an example translation";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return $entries;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If any translation key is not defined, the key is returned, which means, we don't need to specify the string `Welcome` again. To access the translations,
|
If any translation key is not defined, the key is returned, which means, we don't need to specify the string `Welcome` again. To access the translations,
|
||||||
we firstly have to load the module. This is done by adding the class, or the object inside the constructor.
|
we firstly have to load the module. This is done by adding the class, or the object inside the constructor.
|
||||||
To translate the defined strings, we can use the global `L()` function. The following code snipped shows the use of
|
To translate the defined strings, we can use the global `L()` function. The following code snipped shows the use of
|
||||||
our sample language module:
|
our sample language module:
|
||||||
|
|
||||||
|
**/Core/Localization/de_DE/example.php**:
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
return [
|
||||||
|
"Welcome" => "Willkommen",
|
||||||
|
"EXAMPLE_KEY" => "Beispielübersetzung",
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
```php
|
```php
|
||||||
class SomeView extends \Elements\View {
|
class SomeView extends \Elements\View {
|
||||||
public function __construct(\Elements\Document $document) {
|
public function __construct(\Elements\Document $document) {
|
||||||
parent::__construct($document);
|
parent::__construct($document);
|
||||||
$this->langModules[] = ExampleModule::class;
|
$this->langModules[] = "example";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCode() : string{
|
public function getCode() : string{
|
||||||
|
56
cli.php
56
cli.php
@ -13,12 +13,14 @@ use Core\Driver\SQL\Condition\CondIn;
|
|||||||
use Core\Driver\SQL\Expression\DateSub;
|
use Core\Driver\SQL\Expression\DateSub;
|
||||||
use Core\Driver\SQL\SQL;
|
use Core\Driver\SQL\SQL;
|
||||||
use Core\Objects\ConnectionData;
|
use Core\Objects\ConnectionData;
|
||||||
|
use JetBrains\PhpStorm\NoReturn;
|
||||||
|
|
||||||
function printLine(string $line = "") {
|
function printLine(string $line = ""): void {
|
||||||
echo $line . PHP_EOL;
|
echo $line . PHP_EOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _exit(string $line = "") {
|
#[NoReturn]
|
||||||
|
function _exit(string $line = ""): void {
|
||||||
printLine($line);
|
printLine($line);
|
||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
@ -49,10 +51,11 @@ if ($database->getProperty("isDocker", false) && !is_file("/.dockerenv")) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($database->getProperty("isDocker", false) && !is_file("/.dockerenv")) {
|
if ($database->getProperty("isDocker", false) && !is_file("/.dockerenv")) {
|
||||||
|
printLine("Detected docker environment in config, running docker exec...");
|
||||||
if (count($argv) < 3 || $argv[1] !== "db" || !in_array($argv[2], ["shell", "import", "export"])) {
|
if (count($argv) < 3 || $argv[1] !== "db" || !in_array($argv[2], ["shell", "import", "export"])) {
|
||||||
$containerName = $dockerYaml["services"]["php"]["container_name"];
|
$containerName = $dockerYaml["services"]["php"]["container_name"];
|
||||||
$command = array_merge(["docker", "exec", "-it", $containerName, "php"], $argv);
|
$command = array_merge(["docker", "exec", "-it", $containerName, "php"], $argv);
|
||||||
$proc = proc_open($command, [1 => STDOUT, 2 => STDERR], $pipes, "/application");
|
$proc = proc_open($command, [1 => STDOUT, 2 => STDERR], $pipes);
|
||||||
exit(proc_close($proc));
|
exit(proc_close($proc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,8 +71,10 @@ function connectSQL(): ?SQL {
|
|||||||
return $sql;
|
return $sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
function printHelp() {
|
function printHelp(array $argv): void {
|
||||||
// TODO: help
|
printLine("=== WebBase CLI tool ===");
|
||||||
|
printLine("Usage: ");
|
||||||
|
var_dump($argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyPatch(\Core\Driver\SQL\SQL $sql, string $patchName): bool {
|
function applyPatch(\Core\Driver\SQL\SQL $sql, string $patchName): bool {
|
||||||
@ -99,13 +104,13 @@ function applyPatch(\Core\Driver\SQL\SQL $sql, string $patchName): bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDatabase(array $argv) {
|
function handleDatabase(array $argv): void {
|
||||||
global $dockerYaml;
|
global $dockerYaml;
|
||||||
$action = $argv[2] ?? "";
|
$action = $argv[2] ?? "";
|
||||||
|
|
||||||
if ($action === "migrate") {
|
if ($action === "migrate") {
|
||||||
$sql = connectSQL() or die();
|
$sql = connectSQL() or die();
|
||||||
|
_exit("Not implemented: migrate");
|
||||||
} else if (in_array($action, ["export", "import", "shell"])) {
|
} else if (in_array($action, ["export", "import", "shell"])) {
|
||||||
|
|
||||||
// database config
|
// database config
|
||||||
@ -257,7 +262,7 @@ function findPullBranch(array $output): ?string {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMaintenance(array $argv) {
|
function onMaintenance(array $argv): void {
|
||||||
$action = $argv[2] ?? "status";
|
$action = $argv[2] ?? "status";
|
||||||
$maintenanceFile = "MAINTENANCE";
|
$maintenanceFile = "MAINTENANCE";
|
||||||
$isMaintenanceEnabled = file_exists($maintenanceFile);
|
$isMaintenanceEnabled = file_exists($maintenanceFile);
|
||||||
@ -376,7 +381,7 @@ function getConsoleWidth(): int {
|
|||||||
return intval($width);
|
return intval($width);
|
||||||
}
|
}
|
||||||
|
|
||||||
function printTable(array $head, array $body) {
|
function printTable(array $head, array $body): void {
|
||||||
|
|
||||||
$columns = [];
|
$columns = [];
|
||||||
foreach ($head as $key) {
|
foreach ($head as $key) {
|
||||||
@ -409,7 +414,7 @@ function printTable(array $head, array $body) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSettings(array $argv) {
|
function onSettings(array $argv): void {
|
||||||
global $context;
|
global $context;
|
||||||
connectSQL() or die();
|
connectSQL() or die();
|
||||||
$action = $argv[2] ?? "list";
|
$action = $argv[2] ?? "list";
|
||||||
@ -455,7 +460,7 @@ function onSettings(array $argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onRoutes(array $argv) {
|
function onRoutes(array $argv): void {
|
||||||
global $context;
|
global $context;
|
||||||
connectSQL() or die();
|
connectSQL() or die();
|
||||||
$action = $argv[2] ?? "list";
|
$action = $argv[2] ?? "list";
|
||||||
@ -553,7 +558,7 @@ function onRoutes(array $argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onTest($argv) {
|
function onTest($argv): void {
|
||||||
$files = glob(WEBROOT . '/test/*.test.php');
|
$files = glob(WEBROOT . '/test/*.test.php');
|
||||||
$requestedTests = array_filter(array_slice($argv, 2), function ($t) {
|
$requestedTests = array_filter(array_slice($argv, 2), function ($t) {
|
||||||
return !startsWith($t, "-");
|
return !startsWith($t, "-");
|
||||||
@ -569,11 +574,11 @@ function onTest($argv) {
|
|||||||
|
|
||||||
$className = $baseName . "Test";
|
$className = $baseName . "Test";
|
||||||
if (class_exists($className)) {
|
if (class_exists($className)) {
|
||||||
echo "=== Running $className ===" . PHP_EOL;
|
printLine("=== Running $className ===");
|
||||||
$testClass = new \PHPUnit\Framework\TestSuite();
|
$testClass = new \PHPUnit\Framework\TestSuite();
|
||||||
$testClass->addTestSuite($className);
|
$testClass->addTestSuite($className);
|
||||||
$result = $testClass->run();
|
$result = $testClass->run();
|
||||||
echo "Done after " . $result->time() . "s" . PHP_EOL;
|
printLine("Done after " . $result->time() . "s");
|
||||||
$stats = [
|
$stats = [
|
||||||
"total" => $result->count(),
|
"total" => $result->count(),
|
||||||
"skipped" => $result->skippedCount(),
|
"skipped" => $result->skippedCount(),
|
||||||
@ -583,16 +588,19 @@ function onTest($argv) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Summary
|
// Summary
|
||||||
echo implode(", ", array_map(function ($key) use ($stats) {
|
printLine(
|
||||||
|
implode(", ", array_map(function ($key) use ($stats) {
|
||||||
return "$key: " . $stats[$key];
|
return "$key: " . $stats[$key];
|
||||||
}, array_keys($stats))) . PHP_EOL;
|
}, array_keys($stats)))
|
||||||
|
);
|
||||||
|
|
||||||
$reports = array_merge($result->errors(), $result->failures());
|
$reports = array_merge($result->errors(), $result->failures());
|
||||||
foreach ($reports as $error) {
|
foreach ($reports as $error) {
|
||||||
$exception = $error->thrownException();
|
$exception = $error->thrownException();
|
||||||
echo $error->toString();
|
echo $error->toString();
|
||||||
if ($verbose) {
|
if ($verbose) {
|
||||||
echo ". Stacktrace:" . PHP_EOL . $exception->getTraceAsString() . PHP_EOL;
|
printLine(". Stacktrace:");
|
||||||
|
printLine($exception->getTraceAsString());
|
||||||
} else {
|
} else {
|
||||||
$location = array_filter($exception->getTrace(), function ($t) use ($file) {
|
$location = array_filter($exception->getTrace(), function ($t) use ($file) {
|
||||||
return isset($t["file"]) && $t["file"] === $file;
|
return isset($t["file"]) && $t["file"] === $file;
|
||||||
@ -600,9 +608,9 @@ function onTest($argv) {
|
|||||||
$location = array_reverse($location);
|
$location = array_reverse($location);
|
||||||
$location = array_pop($location);
|
$location = array_pop($location);
|
||||||
if ($location) {
|
if ($location) {
|
||||||
echo " in " . substr($location["file"], strlen(WEBROOT)) . "#" . $location["line"] . PHP_EOL;
|
printLine(" in " . substr($location["file"], strlen(WEBROOT)) . "#" . $location["line"]);
|
||||||
} else {
|
} else {
|
||||||
echo PHP_EOL;
|
printLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -610,7 +618,7 @@ function onTest($argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMail($argv) {
|
function onMail($argv): void {
|
||||||
global $context;
|
global $context;
|
||||||
$action = $argv[2] ?? null;
|
$action = $argv[2] ?? null;
|
||||||
if ($action === "send_queue") {
|
if ($action === "send_queue") {
|
||||||
@ -625,7 +633,7 @@ function onMail($argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onImpersonate($argv) {
|
function onImpersonate($argv): void {
|
||||||
global $context;
|
global $context;
|
||||||
|
|
||||||
if (count($argv) < 3) {
|
if (count($argv) < 3) {
|
||||||
@ -651,7 +659,7 @@ function onImpersonate($argv) {
|
|||||||
$session = new \Core\Objects\DatabaseEntity\Session($context, $user);
|
$session = new \Core\Objects\DatabaseEntity\Session($context, $user);
|
||||||
$session->setData(["2faAuthenticated" => true]);
|
$session->setData(["2faAuthenticated" => true]);
|
||||||
$session->update();
|
$session->update();
|
||||||
echo "session=" . $session->getCookie() . PHP_EOL;
|
echo "session=" . $session->getUUID() . PHP_EOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
$argv = $_SERVER['argv'];
|
$argv = $_SERVER['argv'];
|
||||||
@ -662,7 +670,7 @@ if (count($argv) < 2) {
|
|||||||
$command = $argv[1];
|
$command = $argv[1];
|
||||||
switch ($command) {
|
switch ($command) {
|
||||||
case 'help':
|
case 'help':
|
||||||
printHelp();
|
printHelp($argv);
|
||||||
exit;
|
exit;
|
||||||
case 'db':
|
case 'db':
|
||||||
handleDatabase($argv);
|
handleDatabase($argv);
|
||||||
@ -688,6 +696,6 @@ switch ($command) {
|
|||||||
default:
|
default:
|
||||||
printLine("Unknown command '$command'");
|
printLine("Unknown command '$command'");
|
||||||
printLine();
|
printLine();
|
||||||
printHelp();
|
printHelp($argv);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
version: "3.9"
|
version: "3.9"
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
container_name: web
|
container_name: webbase-web
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
volumes:
|
volumes:
|
||||||
@ -13,7 +13,7 @@ services:
|
|||||||
- db
|
- db
|
||||||
- php
|
- php
|
||||||
db:
|
db:
|
||||||
container_name: db
|
container_name: webbase-db
|
||||||
image: mariadb:latest
|
image: mariadb:latest
|
||||||
ports:
|
ports:
|
||||||
- '3306:3306'
|
- '3306:3306'
|
||||||
@ -21,7 +21,7 @@ services:
|
|||||||
- "MYSQL_ROOT_PASSWORD=webbasedb"
|
- "MYSQL_ROOT_PASSWORD=webbasedb"
|
||||||
- "MYSQL_DATABASE=webbase"
|
- "MYSQL_DATABASE=webbase"
|
||||||
php:
|
php:
|
||||||
container_name: php
|
container_name: webbase-php
|
||||||
volumes:
|
volumes:
|
||||||
- .:/application:rw
|
- .:/application:rw
|
||||||
- ./docker/php/php.ini:/usr/local/etc/php/php.ini:ro
|
- ./docker/php/php.ini:/usr/local/etc/php/php.ini:ro
|
||||||
@ -29,5 +29,3 @@ services:
|
|||||||
context: './docker/php/'
|
context: './docker/php/'
|
||||||
links:
|
links:
|
||||||
- db
|
- db
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ server {
|
|||||||
location ~ \.php$ {
|
location ~ \.php$ {
|
||||||
try_files $uri =404;
|
try_files $uri =404;
|
||||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
fastcgi_pass php:9000;
|
fastcgi_pass webbase-php:9000;
|
||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
include fastcgi_params;
|
include fastcgi_params;
|
||||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
@ -23,7 +23,9 @@ RUN mkdir -p /usr/local/etc/php/extra/ && \
|
|||||||
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
|
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get -y install nodejs && \
|
apt-get -y install nodejs && \
|
||||||
npm install --global yarn
|
curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /usr/share/keyrings/yarnkey.gpg >/dev/null && \
|
||||||
|
echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian stable main" | tee /etc/apt/sources.list.d/yarn.list && \
|
||||||
|
apt-get update && apt-get -y install yarn
|
||||||
|
|
||||||
# Runkit for unit testing (no stable release available)
|
# Runkit for unit testing (no stable release available)
|
||||||
RUN pecl install runkit7-4.0.0a6 && docker-php-ext-enable runkit7 && \
|
RUN pecl install runkit7-4.0.0a6 && docker-php-ext-enable runkit7 && \
|
||||||
|
64
js/script.js
64
js/script.js
@ -8,24 +8,36 @@ let Core = function () {
|
|||||||
this.apiCall = function (func, params, callback) {
|
this.apiCall = function (func, params, callback) {
|
||||||
params = typeof params !== 'undefined' ? params : {};
|
params = typeof params !== 'undefined' ? params : {};
|
||||||
callback = typeof callback !== 'undefined' ? callback : function (data) {};
|
callback = typeof callback !== 'undefined' ? callback : function (data) {};
|
||||||
|
let config = { method: "POST" };
|
||||||
|
|
||||||
|
if (params instanceof FormData) {
|
||||||
|
config.body = params;
|
||||||
|
} else {
|
||||||
|
config.headers = { "Content-Type": "application/json" };
|
||||||
|
config.body = JSON.stringify(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = this;
|
||||||
const path = '/api' + (func.startsWith('/') ? '' : '/') + func;
|
const path = '/api' + (func.startsWith('/') ? '' : '/') + func;
|
||||||
$.post(path, params, function (data) {
|
fetch(path, config).then((data) => {
|
||||||
console.log(func + "(): success=" + data.success + " msg=" + data.msg);
|
data.json().then(data => {
|
||||||
callback.call(this, data);
|
callback.call(self, data);
|
||||||
}, "json").fail(function (jqXHR, textStatus, errorThrown) {
|
}).catch(reason => {
|
||||||
let msg = func + " Status: " + textStatus + " error thrown: " + errorThrown;
|
console.log("API-Function Error: " + reason);
|
||||||
console.log("API-Function Error: " + msg);
|
callback.call(self, {success: false, msg: "An error occurred. API-Function: " + reason });
|
||||||
callback.call(this, {success: false, msg: "An error occurred. API-Function: " + msg });
|
})
|
||||||
|
}).catch(reason => {
|
||||||
|
console.log("API-Function Error: " + reason);
|
||||||
|
callback.call(self, {success: false, msg: "An error occurred. API-Function: " + reason });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getCookie = function (cname) {
|
this.getCookie = function (cname) {
|
||||||
var name = cname + "=";
|
let name = cname + "=";
|
||||||
var decodedCookie = decodeURIComponent(document.cookie);
|
let decodedCookie = decodeURIComponent(document.cookie);
|
||||||
var ca = decodedCookie.split(";");
|
let ca = decodedCookie.split(";");
|
||||||
for (var i = 0; i < ca.length; i++) {
|
for (let i = 0; i < ca.length; i++) {
|
||||||
var c = ca[i];
|
let c = ca[i];
|
||||||
while (c.charAt(0) === ' ') {
|
while (c.charAt(0) === ' ') {
|
||||||
c = c.substring(1);
|
c = c.substring(1);
|
||||||
}
|
}
|
||||||
@ -84,9 +96,9 @@ let Core = function () {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setParameter = function (param, newvalue) {
|
this.setParameter = function (param, newValue) {
|
||||||
newvalue = typeof newvalue !== 'undefined' ? newvalue : '';
|
newValue = typeof newValue !== 'undefined' ? newValue : '';
|
||||||
this.parameters[param] = newvalue;
|
this.parameters[param] = newValue;
|
||||||
this.updateUrl();
|
this.updateUrl();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -95,15 +107,13 @@ let Core = function () {
|
|||||||
if (this.url.indexOf('?') === -1)
|
if (this.url.indexOf('?') === -1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var paramString = this.url.substring(this.url.indexOf('?') + 1);
|
let paramString = this.url.substring(this.url.indexOf('?') + 1);
|
||||||
var split = paramString.split('&');
|
let split = paramString.split('&');
|
||||||
for (var i = 0; i < split.length; i++) {
|
for (let i = 0; i < split.length; i++) {
|
||||||
var param = split[i];
|
let param = split[i];
|
||||||
var index = param.indexOf('=');
|
let index = param.indexOf('=');
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
var key = param.substring(0, index);
|
this.parameters[param.substring(0, index)] = param.substring(index + 1);
|
||||||
var val = param.substring(index + 1);
|
|
||||||
this.parameters[key] = val;
|
|
||||||
} else
|
} else
|
||||||
this.parameters[param] = '';
|
this.parameters[param] = '';
|
||||||
}
|
}
|
||||||
@ -112,7 +122,7 @@ let Core = function () {
|
|||||||
this.updateUrl = function () {
|
this.updateUrl = function () {
|
||||||
this.clearUrl();
|
this.clearUrl();
|
||||||
let i = 0;
|
let i = 0;
|
||||||
for (var parameter in this.parameters) {
|
for (let parameter in this.parameters) {
|
||||||
this.url += (i === 0 ? "?" : "&") + parameter;
|
this.url += (i === 0 ? "?" : "&") + parameter;
|
||||||
if (this.parameters.hasOwnProperty(parameter) && this.parameters[parameter].toString().length > 0) {
|
if (this.parameters.hasOwnProperty(parameter) && this.parameters[parameter].toString().length > 0) {
|
||||||
this.url += "=" + this.parameters[parameter];
|
this.url += "=" + this.parameters[parameter];
|
||||||
@ -127,10 +137,12 @@ let Core = function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.clearUrl = function () {
|
this.clearUrl = function () {
|
||||||
if (this.url.indexOf('#') !== -1)
|
if (this.url.indexOf('#') !== -1) {
|
||||||
this.url = this.url.substring(0, this.url.indexOf('#'));
|
this.url = this.url.substring(0, this.url.indexOf('#'));
|
||||||
if (this.url.indexOf('?') !== -1)
|
}
|
||||||
|
if (this.url.indexOf('?') !== -1) {
|
||||||
this.url = this.url.substring(0, this.url.indexOf('?'));
|
this.url = this.url.substring(0, this.url.indexOf('?'));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getJsonDateTime = function (date) {
|
this.getJsonDateTime = function (date) {
|
||||||
|
@ -43,7 +43,7 @@ export default function AdminDashboard(props) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
requestModules(api, ["general", "admin"], currentLocale).then(data => {
|
requestModules(api, ["general", "admin", "account"], currentLocale).then(data => {
|
||||||
if (!data.success) {
|
if (!data.success) {
|
||||||
alert(data.msg);
|
alert(data.msg);
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,7 @@ export default class Settings extends React.Component {
|
|||||||
|
|
||||||
this.hiddenKeys = [
|
this.hiddenKeys = [
|
||||||
"recaptcha_private_key",
|
"recaptcha_private_key",
|
||||||
"mail_password",
|
"mail_password"
|
||||||
"jwt_secret"
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user