synced-timer/src/theme.php

535 lines
23 KiB
PHP

<?php
defined('L_EXEC') or die();
class LandingpageTheme
{
private $_storageHelper;
private $_globalConfig;
private $_translations;
private $_resultLevels;
public function __construct($config, $storageHelper, $translations)
{
$this->_globalConfig = $config;
$this->_storageHelper = $storageHelper;
$this->_translations = $translations;
$this->_resultLevels['loginSuccess'] = "success";
$this->_resultLevels['loginFailed'] = "danger";
$this->_resultLevels['ldapConnectFailed'] = "danger";
$this->_resultLevels['ldapSearchFailed'] = "danger";
$this->_resultLevels['ldapTlsInitializationFailed'] = "danger";
$this->_resultLevels['bindingToLdapAdminFailed'] = "danger";
$this->_resultLevels['loginRequired'] = "warning";
$this->_resultLevels['oldPasswordIsWrong'] = "danger";
$this->_resultLevels['newPasswordMustNotBeEqualToOldPassword'] = "danger";
$this->_resultLevels['newPasswordAndRepeatDidNotMatch'] = "danger";
$this->_resultLevels['passwordIsTooShort'] = "danger";
$this->_resultLevels['passwordDoesNotContainANumberOrSpecialCharacter'] = "danger";
$this->_resultLevels['passwordDoesNotContainALetter'] = "danger";
$this->_resultLevels['passwordDoesNotContainAnUppercaseLetter'] = "danger";
$this->_resultLevels['passwordDoesNotContainALowercaseLetter'] = "danger";
$this->_resultLevels['passwordChangeLdapError'] = "danger";
$this->_resultLevels['newPasswordMustNotBeOldPassword'] = "danger";
$this->_resultLevels['passwordChangedSuccessfully'] = 'success';
$this->_resultLevels['emailChangedSuccessfully'] = 'success';
$this->_resultLevels['emailChangeLdapError'] = 'danger';
$this->_resultLevels['invalidEmailError'] = 'danger';
$this->_resultLevels['permissionDenied'] = 'danger';
$this->_resultLevels['generateJitsiLinkRoomMustNotBeEmpty'] = 'danger';
$this->_resultLevels['generateJitsiLinkSuccessfull'] = 'success';
$this->_resultLevels['timerSetSuccessfully'] = 'success';
}
public function printPage($page, $parameters)
{
switch ($page) {
case 'login':
$this->_printLogin();
break;
case "manage":
$this->_printManagePage();
break;
case "t":
$this->_printTimerPage($parameters);
}
}
private function _printLogin()
{
$this->_printHeader(true);
?>
<style>
html,
body {
height: 100%;
}
body {
display: flex;
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
}
.form-signin {
width: 100%;
max-width: 330px;
padding: 15px;
margin: auto;
}
.form-signin .checkbox {
font-weight: 400;
}
.form-signin .form-control {
position: relative;
box-sizing: border-box;
height: auto;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
</style>
</head>
<body class="text-center">
<main class="form-signin">
<form action="login/submit" method="post">
<img class="mb-4" src="<?= $this->_globalConfig['mainIcon'] ?>" alt="" height="150">
<h1 class="h3 mb-3 fw-normal"><?= $this->_trId("login.title"); ?></h1>
<?php $this->_printResultAlert(); ?>
<label for="inputEmail" class="visually-hidden"><?= $this->_trId("globals.usernameLabel"); ?></label>
<input type="text" id="inputUsername" class="form-control" placeholder="<?= $this->_trId("globals.usernameLabel"); ?>" name="username" required autofocus>
<label for="inputPassword" class="visually-hidden"><?= $this->_trId("globals.passwordLabel"); ?></label>
<input type="password" id="inputPassword" class="form-control" placeholder="<?= $this->_trId("globals.passwordLabel"); ?>" name="password" required>
<button class="w-100 btn btn-lg btn-primary" type="submit"><?= $this->_trId("login.submitLabel"); ?></button>
<p class="mt-5 mb-3 text-muted"><?= $this->_trId("login.footnote"); ?></p>
</form>
</main>
</body>
<?php
$this->_printFooter();
}
private function _printManagePage()
{
$userData = $this->_storageHelper->loadUserdata()[$_SESSION["auth"]["username"]];
$this->_printHeader();
?>
<style>
.mr-2, .mx-2 {
margin-right: 0.5rem !important;
}
.ml-auto {
margin-left: auto !important;
}
.flex-equal>* {
flex: 1;
}
@media (min-width: 768px) {
.flex-md-equal>* {
flex: 1;
}
}
</style>
<body x-data>
<main>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a href="<?= $this->_globalConfig["basePath"] ?>/manage">
<img class="d-inline-block align-top mr-2" src="<?= $this->_globalConfig['mainIcon'] ?>" alt="" width="40px">
</a>
<a class="navbar-brand" href="<?= $this->_globalConfig["basePath"] ?>/manage"><?= $this->_trId("globals.title") ?></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarToggler" aria-controls="navbarToggler" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarToggler">
<ul class="navbar-nav ml-auto mb-2 mb-lg-0">
<li class="nav-item mr-2">
<a class="nav-link active" aria-current="page" target="blank" href="<?= $this->_globalConfig["basePath"] ?>/t/<?= $_SESSION["auth"]["username"] ?>"><?= $this->_trId("home.menu.openTimerLabel") ?></a>
</li>
</ul>
<form action="logout/submit" method="post" class="d-felx">
<button type="submit" class="btn btn-outline-light"><?= $this->_trId("home.menu.logoutLabel") ?></button>
</form>
</div>
</div>
</nav>
<div class="container mt-3">
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="info-fill" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/>
</symbol>
<symbol id="exclamation-triangle-fill" fill="currentColor" viewBox="0 0 16 16">
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
</symbol>
</svg>
<template x-if="(!$store.audioPlayback.isAvailable || $store.audioPlayback.isDisabledByUser) && !$store.audioPlayback.isDisabledByServer && !$store.banners.hide">
<div class="alert alert-danger alert-dismissible d-flex align-items-center" role="alert" style="position: relative;">
<svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Danger:"><use xlink:href="#exclamation-triangle-fill"/></svg>
<div>
<?= $this->_trId("audioAlert.body") ?>
<a href="#" class="alert-link" x-bind="audioPlaybackEnableButton"><?= $this->_trId("audioModal.enable") ?></a>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</template>
<div class="card text-white bg-dark mb-3">
<div class="card-header w-100"><?= $this->_trId("currentTimer") ?></div>
<div class="card-body">
<h3 id="header" x-text="$store.siteData.header"></h3>
<h1 class="card-title" id="timer" x-html="$store.siteData.time"></h1>
</div>
</div>
<div class="card text-dark bg-light mb-3">
<div class="card-header w-100"><?= $this->_trId("manageTimer") ?></div>
<div class="card-body w-100" style="text-align: left;">
<?php $this->_printResultAlert(); ?>
<form method="post" action="manage/submit">
<div class="mb-3">
<label for="header"><?= $this->_trId("header") ?></label>
<input type="text" class="form-control" name="header" value="<?= $userData["header"] ?>" required>
</div>
<div class="mb-3">
<label for="time"><?= $this->_trId("time") ?></label>
<input type="number" name="time" class="form-control" value="<?= $userData["time"] ?>" required>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" role="switch" name="soundEnabled" id="soundEnabledSwitch" <?= $userData["soundEnabled"] ? "checked":"" ?>>
<label class="form-check-label" for="soundEnabledSwitch">Enable sound</label>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" role="switch" name="repeatEnabled" id="repeatEnabledSwitch" <?= $userData["repeatEnabled"] ? "checked":"" ?>>
<label class="form-check-label" for="repeatEnabledSwitch">Repeat</label>
</div>
<button type="submit" x-bind="audioPlaybackDisableButton" class="btn btn-primary"><?= $this->_trId("startTimer") ?></button>
</form>
</div>
</div>
<footer class="pt-3 mt-4 text-muted border-top">
Version: <?= $GLOBALS["VERSION"] ?>
</footer>
</div>
</main>
</body>
<?php
$this->_printFooter();
}
private function _printTimerPage($username)
{
$userData = $this->_storageHelper->loadUserdata();
if (!array_key_exists($username, $userData)) {
$this->_printNotFoundPage();
}
?>
<!DOCTYPE html>
<html lang="en" class="h-100">
<!-- Bootstrap -->
<link href="/static/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<script src="/static/js/lib/bootstrap.min.js" crossorigin="anonymous" defer></script>
<style>
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
body {
text-shadow: 0 .05rem .1rem rgba(0, 0, 0, .5);
box-shadow: inset 0 0 5rem rgba(0, 0, 0, .5);
}
.cover-container {
max-width: 42em;
}
.modal-header {
border-bottom: 1px solid #414549;
}
.modal-footer {
border-top: 1px solid #414549;
}
.btn-close.btn-close-dark {
filter: invert(100%)
}
.unselectable {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
</style>
<title><?= $this->_trId("timerOf") . " " . $username ?></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name=”apple-mobile-web-app-capable” content=”yes”>
</head>
<body x-data class="d-flex h-100 text-center text-white bg-dark">
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="info-fill" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/>
</symbol>
<symbol id="exclamation-triangle-fill" fill="currentColor" viewBox="0 0 16 16">
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
</symbol>
</svg>
<div class="d-flex w-100 h-100 p-3 mx-auto flex-column">
<template x-if="(!$store.audioPlayback.isAvailable || $store.audioPlayback.isDisabledByUser) && !$store.audioPlayback.isDisabledByServer && !$store.banners.hide">
<div class="alert alert-danger alert-dismissible d-flex align-items-center" role="alert" style="position: relative;">
<svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Danger:"><use xlink:href="#exclamation-triangle-fill"/></svg>
<div>
<?= $this->_trId("audioAlert.body") ?>
<a href="#" class="alert-link" x-bind="audioPlaybackEnableButton"><?= $this->_trId("audioModal.enable") ?></a>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</template>
<template x-if="!$store.fullscreen.enabled && !$store.banners.hide">
<div class="alert alert-info alert-dismissible d-flex align-items-center" role="alert" style="position: relative;">
<svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Danger:"><use xlink:href="#info-fill"/></svg>
<div>
<?= $this->_trId("fullscreenAlert.body") ?>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</template>
<main class="px-3 h-100 mt-auto w-100" @click.prevent @dblclick="$store.fullscreen.toggle()">
<h4 id="header" class="unselectable" x-text="$store.siteData.header"></h4>
<div class="h-80 unselectable" style="justify-content:center; align-items:center; display:flex;">
<h4 id="timer" x-html="$store.siteData.time"></h4>
</div>
</main>
<footer class="mt-auto text-white-50">
<p>Synced timer by <img height="40px" src="<?= $this->_globalConfig["basePath"] ?>/static/img/IconBig.png" />&nbsp; <a href="https://www.itsblue.de" class="text-white">www.itsblue.de</a></p>
</footer>
</div>
</body>
<footer>
<script>
const username = "<?= $username ?>";
var textElem = document.getElementById("timer");
var headerTextElem = document.getElementById("header");
var targetWidth = 0.9; // Proportion of full screen width
var curFontSize = 20; // Do not change
function updateTextSize() {
for (var i = 0; 3 > i; i++) { // Iterate for better better convergence
curFontSize *= targetWidth / (textElem.offsetWidth / textElem.parentNode.offsetWidth);
textElem.style.fontSize = curFontSize + "pt";
headerTextElem.style.fontSize = curFontSize * 0.3 + "pt"
}
}
document.addEventListener('alpine:initialized', () => {
updateTextSize()
})
window.onresize = updateTextSize;
</script>
<!-- JavaScript -->
<script src="/static/js/lib/alpine.min.js" defer></script>
<script src="/static/js/synced-timer.js"></script>
</footer>
</html>
<?php
}
private function _printNotFoundPage()
{
?>
<h1>Not found!</h1>
<?php
}
// -----------
// - Helpers -
// -----------
private function _printHeader($printOnlySkeleton = false)
{
?>
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
<!-- JavaScript -->
<script src="/static/js/lib/apline.min.js" defer></script>
<script src="/static/js/lib/purify.min.js" defer></script>
<script src="/static/js/synced-timer.js" defer></script>
<style>
:root {
--primary_500: 255, 0, 0;
}
.mr-4 {
margin-right: 1.5rem !important;
}
.ml-4 {
margin-left: 1.5rem !important;
}
.card {
align-items: center;
text-align: center;
}
.card-footer {
width: 100%;
}
a:focus {
outline: none;
}
</style>
<title><?= $this->_trId("globals.title"); ?></title>
<?php
if (!$printOnlySkeleton) :
?>
</head>
<?php
endif;
}
private function _printResultAlert()
{
if (!isset($_SESSION['lastResult']) || $_SESSION['lastResult'] === 'loginSuccess')
return;
$this->_printAlert($this->_resultToMessage($_SESSION['lastResult']), $this->_resultLevels[$_SESSION['lastResult']]);
}
private function _printAlert($content, $level = 'waring', $dismissible = true)
{
?>
<div class="alert alert-<?= $level ?> <?php if ($dismissible) echo "alert-dismissible"; ?> fade show" role="alert">
<strong><?= $content ?></strong>
<?php if ($dismissible) : ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<?php endif; ?>
</div>
<?php
}
private function _printFooter()
{
?>
<script>
const username = "<?= $_SESSION["auth"]["username"] ?>";
var forms = document.getElementsByTagName('form')
for (const form of forms) {
var formButtons = form.getElementsByTagName("button");
for (const button of formButtons) {
if (button.type === "submit") {
form.addEventListener("submit", () => {
button.innerHTML += ' <div class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></div>'
button.disabled = true
})
}
}
var formInputs = form.getElementsByTagName("input")
for (const input of formInputs) {
form.addEventListener("submit", () => {
input.readonly = true
})
}
}
</script>
<script src="/static/js/lib/alpine.min.js" defer></script>
<script src="/static/js/synced-timer.js"></script>
</html>
<?php
}
private function _resultToMessage($result)
{
return $this->_translations['results'][$result];
}
private function _trId($id)
{
$result = $this->_translations;
foreach (explode(".", $id) as $sub) {
$result = $result[$sub];
}
return $result;
}
}