Cleanup + Login View

This commit is contained in:
Roman 2022-02-21 14:04:49 +01:00
parent f46705cd84
commit 872ef4099a
21 changed files with 54 additions and 799 deletions

20
cli.php

@ -621,13 +621,29 @@ function onMail($argv) {
function onImpersonate($argv) { function onImpersonate($argv) {
if (count($argv) < 3) { if (count($argv) < 3) {
_exit("Usage: cli.php impersonate <user_id>"); _exit("Usage: cli.php impersonate <user_id|user_name>");
} }
$user = getUser() or exit; $user = getUser() or exit;
$user->createSession(intval($argv[2]));
$userId = $argv[2];
if (!is_numeric($userId)) {
$sql = $user->getSQL();
$res = $sql->select("uid")
->from("User")
->where(new Compare("name", $userId))
->execute();
if ($res === false) {
_exit("SQL error: " . $sql->getLastError());
} else {
$userId = $res[0]["uid"];
}
}
$user->createSession(intval($userId));
$session = $user->getSession(); $session = $user->getSession();
$session->setData(["2faAuthenticated" => true]); $session->setData(["2faAuthenticated" => true]);
$session->update(false);
echo "session=" . $session->getCookie() . PHP_EOL; echo "session=" . $session->getCookie() . PHP_EOL;
} }

@ -160,6 +160,7 @@ class CreateDatabase extends DatabaseScript {
->addRow("^/confirmEmail/?$", "dynamic", "\\Documents\\Account", "account/confirm_email.twig") ->addRow("^/confirmEmail/?$", "dynamic", "\\Documents\\Account", "account/confirm_email.twig")
->addRow("^/acceptInvite/?$", "dynamic", "\\Documents\\Account", "account/accept_invite.twig") ->addRow("^/acceptInvite/?$", "dynamic", "\\Documents\\Account", "account/accept_invite.twig")
->addRow("^/resetPassword/?$", "dynamic", "\\Documents\\Account", "account/reset_password.twig") ->addRow("^/resetPassword/?$", "dynamic", "\\Documents\\Account", "account/reset_password.twig")
->addRow("^/login/?$", "dynamic", "\\Documents\\Account", "account/login.twig")
->addRow("^/resendConfirmEmail/?$", "dynamic", "\\Documents\\Account", "account/resend_confirm_email.twig") ->addRow("^/resendConfirmEmail/?$", "dynamic", "\\Documents\\Account", "account/resend_confirm_email.twig")
->addRow("^/$", "static", "/static/welcome.html", NULL); ->addRow("^/$", "static", "/static/welcome.html", NULL);

@ -40,6 +40,9 @@ class Account extends TemplateDocument {
} else if (!$settings->isRegistrationAllowed()) { } else if (!$settings->isRegistrationAllowed()) {
$this->createError("Registration is not enabled on this website."); $this->createError("Registration is not enabled on this website.");
} }
} else if ($this->getTemplateName() === "account/login.twig" && $this->user->isLoggedIn()) {
header("Location: /admin");
exit();
} else if ($this->getTemplateName() === "account/accept_invite.twig") { } else if ($this->getTemplateName() === "account/accept_invite.twig") {
if (isset($_GET["token"]) && is_string($_GET["token"]) && !empty($_GET["token"])) { if (isset($_GET["token"]) && is_string($_GET["token"]) && !empty($_GET["token"])) {
$this->parameters["view"]["token"] = $_GET["token"]; $this->parameters["view"]["token"] = $_GET["token"];

@ -1,65 +0,0 @@
<?php
namespace Documents {
use Documents\Files\FilesBody;
use Documents\Files\FilesHead;
use Elements\Document;
use Objects\User;
class Files extends Document {
public function __construct(User $user, string $view = NULL) {
parent::__construct($user, FilesHead::class, FilesBody::class, $view);
}
}
}
namespace Documents\Files {
use Elements\Head;
use Elements\Link;
use Elements\Script;
use Elements\SimpleBody;
class FilesHead extends Head {
protected function initSources() {
$this->addCSS(Link::BOOTSTRAP);
$this->loadFontawesome();
}
protected function initMetas(): array {
return array(
array('name' => 'viewport', 'content' => 'width=device-width, initial-scale=1.0'),
array('name' => 'format-detection', 'content' => 'telephone=yes'),
array('charset' => 'utf-8'),
array('http-equiv' => 'expires', 'content' => '0'),
array('name' => 'robots', 'content' => 'noarchive'),
array('name' => 'referrer', 'content' => 'origin')
);
}
protected function initRawFields(): array {
return array();
}
protected function initTitle(): string {
return "File Control Panel";
}
}
class FilesBody extends SimpleBody {
public function __construct($document) {
parent::__construct($document);
}
protected function getContent(): string {
$html = "<noscript>" . $this->createErrorText("Javascript is required for this site to render.") . "</noscript>";
$html .= "<div id=\"root\"></div>";
$html .= new Script(Script::MIME_TEXT_JAVASCRIPT, Script::FILES);
return $html;
}
}
}

@ -58,6 +58,7 @@ class TemplateDocument extends Document {
$params["site"] = [ $params["site"] = [
"name" => $settings->getSiteName(), "name" => $settings->getSiteName(),
"baseUrl" => $settings->getBaseUrl(), "baseUrl" => $settings->getBaseUrl(),
"registrationEnabled" => $settings->isRegistrationAllowed(),
"title" => $this->title, "title" => $this->title,
"recaptcha" => [ "recaptcha" => [
"key" => $settings->isRecaptchaEnabled() ? $settings->getRecaptchaSiteKey() : null, "key" => $settings->isRecaptchaEnabled() ? $settings->getRecaptchaSiteKey() : null,

@ -1,13 +0,0 @@
<?php
namespace Objects;
class KeyBasedTwoFactorToken extends TwoFactorToken {
const TYPE = "fido2";
public function __construct(string $secret, ?int $id = null, bool $confirmed = false) {
parent::__construct(self::TYPE, $secret, $id, $confirmed);
}
}

@ -1,52 +0,0 @@
<?php
namespace Objects;
use Base32\Base32;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
class TimeBasedTwoFactorToken extends TwoFactorToken {
const TYPE = "totp";
public function __construct(string $secret, ?int $id = null, bool $confirmed = false) {
parent::__construct(self::TYPE, $secret, $id, $confirmed);
}
public function getUrl(User $user): string {
$otpType = self::TYPE;
$name = rawurlencode($user->getUsername());
$settings = $user->getConfiguration()->getSettings();
$urlArgs = [
"secret" => $this->getSecret(),
"issuer" => $settings->getSiteName(),
];
$urlArgs = http_build_query($urlArgs);
return "otpauth://$otpType/$name?$urlArgs";
}
public function generateQRCode(User $user) {
$options = new QROptions(['outputType' => QRCode::OUTPUT_IMAGE_PNG, "imageBase64" => false]);
$qrcode = new QRCode($options);
return $qrcode->render($this->getUrl($user));
}
public function generate(?int $at = null, int $length = 6, int $period = 30): string {
if ($at === null) {
$at = time();
}
$seed = intval($at / $period);
$secret = Base32::decode($this->getSecret());
$hmac = hash_hmac('sha1', pack("J", $seed), $secret, true);
$offset = ord($hmac[-1]) & 0xF;
$code = (unpack("N", substr($hmac, $offset, 4))[1] & 0x7fffffff) % intval(pow(10, $length));
return substr(str_pad(strval($code), $length, "0", STR_PAD_LEFT), -1 * $length);
}
public function verify(string $code): bool {
return $this->generate() === $code;
}
}

@ -1,63 +0,0 @@
<?php
namespace Objects;
abstract class TwoFactorToken extends ApiObject {
private ?int $id;
private string $type;
private string $secret;
private bool $confirmed;
private bool $authenticated;
public function __construct(string $type, string $secret, ?int $id = null, bool $confirmed = false) {
$this->id = $id;
$this->type = $type;
$this->secret = $secret;
$this->confirmed = $confirmed;
$this->authenticated = $_SESSION["2faAuthenticated"] ?? false;
}
public function jsonSerialize(): array {
return [
"id" => $this->id,
"type" => $this->type,
"confirmed" => $this->confirmed,
"authenticated" => $this->authenticated,
];
}
public function authenticate() {
$this->authenticated = true;
$_SESSION["2faAuthenticated"] = true;
}
public function getType(): string {
return $this->type;
}
public function getSecret(): string {
return $this->secret;
}
public function isConfirmed(): bool {
return $this->confirmed;
}
public function getId(): int {
return $this->id;
}
public static function newInstance(string $type, string $secret, ?int $id = null, bool $confirmed = false) {
if ($type === TimeBasedTwoFactorToken::TYPE) {
return new TimeBasedTwoFactorToken($secret, $id, $confirmed);
} else {
// TODO: error message
return null;
}
}
public function isAuthenticated(): bool {
return $this->authenticated;
}
}

@ -1,16 +0,0 @@
{% extends "base.twig" %}
{% block head %}
<script src="/js/jquery.min.js" nonce="{{ site.csp.nonce }}"></script>
<script src="/js/script.js" nonce="{{ site.csp.nonce }}"></script>
<script src="/js/account.js" nonce="{{ site.csp.nonce }}"></script>
<link rel="stylesheet" href="/css/bootstrap.min.css" nonce="{{ site.csp.nonce }}">
<script src="/js/bootstrap.bundle.min.js" nonce="{{ site.csp.nonce }}"></script>
<link rel="stylesheet" href="/css/fontawesome.min.css" nonce="{{ site.csp.nonce }}">
<link rel="stylesheet" href="/css/account.css" nonce="{{ site.csp.nonce }}">
<title>Account - {{ title }}</title>
{% endblock %}
{% block body %}
{% endblock %}

@ -0,0 +1,31 @@
{% extends "account.twig" %}
{% set view_title = 'Sign In' %}
{% set view_icon = 'user-lock' %}
{% set view_description = 'Sign In into your account' %}
{% block view_content %}
<h4 class="pb-4">Please fill with your details</h4>
<form>
<div class="input-group">
<div class="input-group-append">
<span class="input-group-text"><i class="fas fa-hashtag"></i></span>
</div>
<input id="username" autocomplete='username' name="username" placeholder="Username or E-Mail" class="form-control" type="text" maxlength="32">
</div>
<div class="input-group mt-3">
<div class="input-group-append">
<span class="input-group-text"><i class="fas fa-key"></i></span>
</div>
<input type="password" autocomplete='password' name='password' id='password' class="form-control" placeholder="Password">
</div>
<div class="input-group mt-5 mb-4">
<button type="button" class="btn btn-primary" id='btnLogin'>Sign In</button>
{% if site.registrationEnabled %}
<div class="ml-2" style="line-height: 38px;">Don't have an account yet? <a href="/register">Click here</a> to register.</div>
{% endif %}
</div>
</form>
{% endblock %}

@ -1,8 +0,0 @@
Hello {{ username }},<br><br>
You recently created an account on {{ site_name }}. Please click on the following link to confirm your email address and complete your registration.<br>
If you haven't registered an account, you can simply ignore this email. The link is valid for the next 48 hours:<br><br>
<a href="{{ link }}">{{ link }}</a><br><br>
Best Regards<br>
{{ site_name }} Administration

@ -1,89 +0,0 @@
<?php
namespace Views\Account;
use Elements\Document;
use Elements\View;
class AcceptInvite extends AccountView {
private bool $success;
private string $message;
private array $invitedUser;
public function __construct(Document $document, $loadView = true) {
parent::__construct($document, $loadView);
$this->title = "Invitation";
$this->description = "Finnish your account registration by choosing a password.";
$this->icon = "user-check";
$this->success = false;
$this->message = "No content";
$this->invitedUser = array();
}
public function loadView() {
parent::loadView();
if (isset($_GET["token"]) && is_string($_GET["token"]) && !empty($_GET["token"])) {
$req = new \Api\User\CheckToken($this->getDocument()->getUser());
$this->success = $req->execute(array("token" => $_GET["token"]));
if ($this->success) {
if (strcmp($req->getResult()["token"]["type"], "invite") !== 0) {
$this->success = false;
$this->message = "The given token has a wrong type.";
} else {
$this->invitedUser = $req->getResult()["user"];
}
} else {
$this->message = "Error confirming e-mail address: " . $req->getLastError();
}
} else {
$this->success = false;
$this->message = "The link you visited is no longer valid";
}
}
protected function getAccountContent() {
if (!$this->success) {
return $this->createErrorText($this->message);
}
$token = htmlspecialchars($_GET["token"], ENT_QUOTES);
$username = $this->invitedUser["name"];
$emailAddress = $this->invitedUser["email"];
return "<h4 class=\"pb-4\">Please fill with your details</h4>
<form>
<input name='token' id='token' type='hidden' value='$token'/>
<div class=\"input-group\">
<div class=\"input-group-append\">
<span class=\"input-group-text\"><i class=\"fas fa-hashtag\"></i></span>
</div>
<input id=\"username\" name=\"username\" placeholder=\"Username\" class=\"form-control\" type=\"text\" maxlength=\"32\" value='$username' disabled>
</div>
<div class=\"input-group mt-3\">
<div class=\"input-group-append\">
<span class=\"input-group-text\"><i class=\"fas fa-at\"></i></span>
</div>
<input type=\"email\" name='email' id='email' class=\"form-control\" placeholder=\"Email\" maxlength=\"64\" value='$emailAddress' disabled>
</div>
<div class=\"input-group mt-3\">
<div class=\"input-group-append\">
<span class=\"input-group-text\"><i class=\"fas fa-key\"></i></span>
</div>
<input type=\"password\" autocomplete='new-password' name='password' id='password' class=\"form-control\" placeholder=\"Password\">
</div>
<div class=\"input-group mt-3\">
<div class=\"input-group-append\">
<span class=\"input-group-text\"><i class=\"fas fa-key\"></i></span>
</div>
<input type=\"password\" autocomplete='new-password' name='confirmPassword' id='confirmPassword' class=\"form-control\" placeholder=\"Confirm Password\">
</div>
<div class=\"input-group mt-3\">
<button type=\"button\" class=\"btn btn-success\" id='btnAcceptInvite'>Submit</button>
</div>
</form>";
}
}

@ -1,61 +0,0 @@
<?php
namespace Views\Account;
use Elements\Document;
use Elements\View;
abstract class AccountView extends View {
protected string $description;
protected string $icon;
public function __construct(Document $document, $loadView = true) {
parent::__construct($document, $loadView);
$this->description = "";
$this->icon = "image";
}
public function loadView() {
parent::loadView();
$document = $this->getDocument();
$settings = $document->getUser()->getConfiguration()->getSettings();
if ($settings->isRecaptchaEnabled()) {
$document->getHead()->loadGoogleRecaptcha($settings->getRecaptchaSiteKey());
}
}
public function getCode(): string {
$html = parent::getCode();
$content = $this->getAccountContent();
$icon = $this->createIcon($this->icon, "fas", "fa-3x");
$html .= "<div class=\"container mt-5\">
<div class=\"row\">
<div class=\"col-md-3 py-5 bg-primary text-white text-center\" style='border-top-left-radius:.4em;border-bottom-left-radius:.4em;margin-left: auto'>
<div class=\"card-body\">
$icon
<h2 class=\"py-3\">$this->title</h2>
<p>$this->description</p>
</div>
</div>
<div class=\"col-md-5 pt-5 pb-2 border border-info\" style='border-top-right-radius:.4em;border-bottom-right-radius:.4em;margin-right:auto'>
$content
<div class='alert mt-2' style='display:none' id='alertMessage'></div>
</div>
</div>
</div>";
$settings = $this->getDocument()->getUser()->getConfiguration()->getSettings();
if ($settings->isRecaptchaEnabled()) {
$siteKey = $settings->getRecaptchaSiteKey();
$html .= "<input type='hidden' value='$siteKey' id='siteKey' />";
}
return $html;
}
protected abstract function getAccountContent();
}

@ -1,55 +0,0 @@
<?php
namespace Views\Account;
use Elements\Document;
use Elements\Script;
class ConfirmEmail extends AccountView {
public function __construct(Document $document, $loadView = true) {
parent::__construct($document, $loadView);
$this->title = "Confirm Email";
$this->description = "Request a password reset, once you got the e-mail address, you can choose a new password";
$this->icon = "user-check";
}
public function loadView() {
parent::loadView();
$this->getDocument()->getHead()->addScript(Script::MIME_TEXT_JAVASCRIPT, "", '
$(document).ready(function() {
var token = jsCore.getParameter("token");
if (token) {
jsCore.apiCall("/user/confirmEmail", { token: token }, (res) => {
$("#confirm-status").removeClass("alert-info");
if (!res.success) {
$("#confirm-status").addClass("alert-danger");
$("#confirm-status").text("Error confirming e-mail address: " + res.msg);
} else {
$("#confirm-status").addClass("alert-success");
$("#confirm-status").text("Your e-mail address was successfully confirmed, you may now log in.");
}
});
} else {
$("#confirm-status").removeClass("alert-info");
$("#confirm-status").addClass("alert-danger");
$("#confirm-status").text("The link you visited is no longer valid");
}
});'
);
}
protected function getAccountContent() {
$spinner = $this->createIcon("spinner");
$html = "<noscript><div class=\"alert alert-danger\">Javascript is required</div></noscript>
<div class=\"alert alert-info\" id=\"confirm-status\">
Confirming email… $spinner
</div>";
$html .= "<a href='/login'><button class='btn btn-primary' style='position: absolute; bottom: 10px' type='button'>Proceed to Login</button></a>";
return $html;
}
}

@ -1,70 +0,0 @@
<?php
namespace Views\Account;
use Elements\Document;
class Register extends AccountView {
public function __construct(Document $document, $loadView = true) {
parent::__construct($document, $loadView);
$this->title = "Registration";
$this->description = "Create a new account";
$this->icon = "user-plus";
}
public function getAccountContent() {
$user = $this->getDocument()->getUser();
if ($user->isLoggedIn()) {
header(302);
header("Location: /");
die("You are already logged in.");
}
$settings = $user->getConfiguration()->getSettings();
if (!$settings->isRegistrationAllowed()) {
return $this->createErrorText(
"Registration is not enabled on this website. If you are an administrator,
goto <a href=\"/admin/settings\">/admin/settings</a>, to enable the user registration"
);
}
return "<h4 class=\"pb-4\">Please fill with your details</h4>
<form>
<div class=\"input-group\">
<div class=\"input-group-append\">
<span class=\"input-group-text\"><i class=\"fas fa-hashtag\"></i></span>
</div>
<input id=\"username\" autocomplete='username' name=\"username\" placeholder=\"Username\" class=\"form-control\" type=\"text\" maxlength=\"32\">
</div>
<div class=\"input-group mt-3\">
<div class=\"input-group-append\">
<span class=\"input-group-text\"><i class=\"fas fa-at\"></i></span>
</div>
<input type=\"email\" autocomplete='email' name='email' id='email' class=\"form-control\" placeholder=\"Email\" maxlength=\"64\">
</div>
<div class=\"input-group mt-3\">
<div class=\"input-group-append\">
<span class=\"input-group-text\"><i class=\"fas fa-key\"></i></span>
</div>
<input type=\"password\" autocomplete='new-password' name='password' id='password' class=\"form-control\" placeholder=\"Password\">
</div>
<div class=\"input-group mt-3\">
<div class=\"input-group-append\">
<span class=\"input-group-text\"><i class=\"fas fa-key\"></i></span>
</div>
<input type=\"password\" autocomplete='new-password' name='confirmPassword' id='confirmPassword' class=\"form-control\" placeholder=\"Confirm Password\">
</div>
<div class=\"input-group mt-3\">
<button type=\"button\" class=\"btn btn-primary\" id='btnRegister'>Submit</button>
<a href='/login' style='margin-left: 10px'>
<button class='btn btn-secondary' type='button'>
Back to Login
</button>
</a>
</div>
</form>";
}
}

@ -1,39 +0,0 @@
<?php
namespace Views\Account;
use Elements\Document;
class ResendConfirmEmail extends AccountView {
public function __construct(Document $document, $loadView = true) {
parent::__construct($document, $loadView);
$this->title = "Resend Confirm Email";
$this->description = "Request a new confirmation email to finalize the account creation";
$this->icon = "envelope";
}
protected function getAccountContent() {
return "<p class='lead'>Enter your E-Mail address, to receive a new e-mail to confirm your registration.</p>
<form>
<div class=\"input-group\">
<div class=\"input-group-append\">
<span class=\"input-group-text\"><i class=\"fas fa-at\"></i></span>
</div>
<input id=\"email\" autocomplete='email' name=\"email\" placeholder=\"E-Mail address\" class=\"form-control\" type=\"email\" maxlength=\"64\" />
</div>
<div class=\"input-group mt-2\" style='position: absolute;bottom: 15px'>
<button id='btnResendConfirmEmail' class='btn btn-primary'>
Request
</button>
<a href='/login' style='margin-left: 10px'>
<button class='btn btn-secondary' type='button'>
Back to Login
</button>
</a>
</div>
";
}
}

@ -1,99 +0,0 @@
<?php
namespace Views\Account;
use Elements\Document;
class ResetPassword extends AccountView {
private bool $success;
private string $message;
private ?string $token;
public function __construct(Document $document, $loadView = true) {
parent::__construct($document, $loadView);
$this->title = "Reset Password";
$this->description = "Request a password reset, once you got the e-mail address, you can choose a new password";
$this->icon = "user-lock";
$this->success = true;
$this->message = "";
$this->token = NULL;
}
public function loadView() {
parent::loadView();
if (isset($_GET["token"]) && is_string($_GET["token"]) && !empty($_GET["token"])) {
$this->token = $_GET["token"];
$req = new \Api\User\CheckToken($this->getDocument()->getUser());
$this->success = $req->execute(array("token" => $_GET["token"]));
if ($this->success) {
if (strcmp($req->getResult()["token"]["type"], "password_reset") !== 0) {
$this->success = false;
$this->message = "The given token has a wrong type.";
}
} else {
$this->message = "Error requesting password reset: " . $req->getLastError();
}
}
}
protected function getAccountContent() {
if (!$this->success) {
$html = $this->createErrorText($this->message);
if ($this->token !== null) {
$html .= "<a href='/resetPassword' class='btn btn-primary'>Go back</a>";
}
return $html;
}
if ($this->token === null) {
return "<p class='lead'>Enter your E-Mail address, to receive a password reset token.</p>
<form>
<div class=\"input-group\">
<div class=\"input-group-append\">
<span class=\"input-group-text\"><i class=\"fas fa-at\"></i></span>
</div>
<input id=\"email\" autocomplete='email' name=\"email\" placeholder=\"E-Mail address\" class=\"form-control\" type=\"email\" maxlength=\"64\" />
</div>
<div class=\"input-group mt-2\" style='position: absolute;bottom: 15px'>
<button id='btnRequestPasswordReset' class='btn btn-primary'>
Request
</button>
<a href='/login' style='margin-left: 10px'>
<button class='btn btn-secondary' type='button'>
Back to Login
</button>
</a>
</div>
";
} else {
return "<h4 class=\"pb-4\">Choose a new password</h4>
<form>
<input name='token' id='token' type='hidden' value='$this->token'/>
<div class=\"input-group mt-3\">
<div class=\"input-group-append\">
<span class=\"input-group-text\"><i class=\"fas fa-key\"></i></span>
</div>
<input type=\"password\" autocomplete='new-password' name='password' id='password' class=\"form-control\" placeholder=\"Password\">
</div>
<div class=\"input-group mt-3\">
<div class=\"input-group-append\">
<span class=\"input-group-text\"><i class=\"fas fa-key\"></i></span>
</div>
<input type=\"password\" autocomplete='new-password' name='confirmPassword' id='confirmPassword' class=\"form-control\" placeholder=\"Confirm Password\">
</div>
<div class=\"input-group mt-3\">
<button type=\"button\" class=\"btn btn-primary\" id='btnResetPassword'>Submit</button>
<a href='/login' style='margin-left: 10px; display: none' id='backToLogin'>
<button class='btn btn-success' type='button'>
Back to Login
</button>
</a>
</div>
</form>";
}
}
}

@ -1,20 +0,0 @@
<?php
namespace Views\Admin;
use Elements\Body;
use Elements\Script;
class AdminDashboardBody extends Body {
public function __construct($document) {
parent::__construct($document);
}
public function getCode(): string {
$html = parent::getCode();
$script = $this->getDocument()->createScript(Script::MIME_TEXT_JAVASCRIPT, "/js/admin.min.js");
$html .= "<body><div class=\"wrapper\" id=\"root\">$script</div></body>";
return $html;
}
}

@ -1,72 +0,0 @@
<?php
namespace Views\Admin;
use Elements\Body;
use Elements\Link;
use Elements\Script;
use Views\LanguageFlags;
class LoginBody extends Body {
public function __construct($document) {
parent::__construct($document);
}
public function loadView() {
parent::loadView();
$head = $this->getDocument()->getHead();
$head->loadJQuery();
$head->loadBootstrap();
$head->addJS(Script::CORE);
$head->addCSS(Link::CORE);
$head->addJS(Script::ACCOUNT);
$head->addCSS(Link::ACCOUNT);
}
public function getCode(): string {
$html = parent::getCode();
$username = L("Username");
$password = L("Password");
$login = L("Login");
$backToStartPage = L("Back to Start Page");
$stayLoggedIn = L("Stay logged in");
$flags = $this->load(LanguageFlags::class);
$iconBack = $this->createIcon("arrow-circle-left");
$domain = $_SERVER['HTTP_HOST'];
$protocol = getProtocol();
$html .= "
<body>
<div class=\"container mt-4\">
<div class=\"title text-center\">
<h2>Admin Control Panel</h2>
</div>
<div class=\"row\">
<div class=\"col-lg-6 col-12 m-auto\">
<form class=\"loginForm\">
<label for=\"username\">$username</label>
<input type=\"text\" class=\"form-control\" name=\"username\" id=\"username\" placeholder=\"$username\" required autofocus />
<label for=\"password\">$password</label>
<input type=\"password\" class=\"form-control\" name=\"password\" id=\"password\" placeholder=\"$password\" required />
<div class=\"form-check\">
<input type=\"checkbox\" class=\"form-check-input\" id=\"stayLoggedIn\" name=\"stayLoggedIn\">
<label class=\"form-check-label\" for=\"stayLoggedIn\">$stayLoggedIn</label>
</div>
<button class=\"btn btn-lg btn-primary btn-block\" id=\"btnLogin\" type=\"button\">$login</button>
<div class=\"alert alert-danger\" style='display:none' role=\"alert\" id=\"alertMessage\"></div>
<span class=\"flags position-absolute\">$flags</span>
</form>
<div class=\"p-1\">
<a href=\"$protocol://$domain\">$iconBack&nbsp;$backToStartPage</a>
</div>
</div>
</div>
</div>
</body>";
return $html;
}
}

@ -1,62 +0,0 @@
<?php
namespace Views;
use Elements\View;
class LanguageFlags extends View {
private array $languageFlags;
public function __construct($document) {
parent::__construct($document);
$this->languageFlags = array();
$this->searchable = false;
}
public function loadView() {
parent::loadView();
$request = new \Api\Language\Get($this->getDocument()->getUser());
if ($request->execute()) {
$requestUri = $_SERVER["REQUEST_URI"];
$queryString = $_SERVER['QUERY_STRING'];
$params = explode("&", $queryString);
$query = array();
foreach ($params as $param) {
$aParam = explode("=", $param);
$key = $aParam[0];
if ($key === "site" &&
(!startsWith($_SERVER["REQUEST_URI"], "/index.php") || $_SERVER["REQUEST_URI"] === "/")) {
continue;
}
$val = (isset($aParam[1]) ? $aParam[1] : "");
if (!empty($key)) {
$query[$key] = $val;
}
}
$url = parse_url($requestUri, PHP_URL_PATH) . "?";
foreach ($request->getResult()['languages'] as $lang) {
$langCode = $lang['code'];
$langName = $lang['name'];
$query['lang'] = $langCode;
$queryString = http_build_query($query);
$this->languageFlags[] = $this->createLink(
"$url$queryString",
"<img class=\"p-1 clickable\" src=\"/img/icons/lang/$langCode.gif\" alt=\"$langName\" title=\"$langName\">"
);
}
}
}
public function getCode(): string {
return implode('', $this->languageFlags);
}
}

@ -1,13 +0,0 @@
<?php
namespace Views;
use Elements\View;
class View404 extends View {
public function getCode(): string {
return parent::getCode() . "<b>Not found</b>";
}
};