2024.11.30 01:19:49

This commit is contained in:
Christian Moser 2024-11-30 01:19:49 +01:00
parent e59816de20
commit 6d540a6c71
20 changed files with 1103 additions and 320 deletions

11
.gitattributes vendored Normal file
View File

@ -0,0 +1,11 @@
* text=auto
*.js text
*.php text
*.html text
*.htm text
*.png binary
*.svg binary
*.ico binary

View File

@ -83,6 +83,12 @@
--color-code: white;
--color-link-visited: #E496E7;
--color-link-normal: #4880E7;
--color-select-background:#EAE3C4;
--color-optional-input:#B8B8B8;
--color-button-background:#645822;
--color-button-background-disabled:#645822;
--color-button-border:#EAE3C2;
--color-button-text: #E7E0BA;
/* fonts */
--font-family: "JetBrains";
@ -252,17 +258,49 @@ label {
input[type=email],
input[type=password],
input[type=text]
input[type=text],
input[type=number]
{
--color-text: #E0D4A4;
--background-color:
height: 20px;
font-size: 16px;
background-color: var(--color-text);
color: var(--color-background);
border-radius: 2px;
border-radius: 3px;
border-width: 0px;
width: 100%;
}
input[type=submit],
button
{
background-color: var(--color-button-background);
border: 1px solid var(--color-button-border);
border-radius: 3px;
padding:4px;
font-size: 16px;
color: var(--color-button-text);
}
input[type=submit]:disabled,
input[type=submit]:invalid,
button:disabled,
button:invalid
{
background-color: var(--color-button-background-disabled);
}
select {
height:20px;
font-size:16px;
background-color: var(--color-select-background);
color: var(--color-background);
border-radius:4px;
border-width: 0px;
}
.optional-input {
background-color: var(--color-optional-input);
}
code {
@ -285,8 +323,16 @@ code {
}
fieldset {
border: 2px var(--color-text) solid;
border-radius: 4px;
border: 3px solid var(--color-text);
border-radius: 8px;
margin-top: 5px;
margin-bottom: 5px;
}
.margin-5 {
margin: 5px;
}
.margin-10 {
margin: 10px;
}

View File

@ -0,0 +1,92 @@
function setOptional(element) {
element.removeAttribute("required");
element.style.backgroundColor = "var(--color-optional-input)";;
}
function setRequired(element) {
element.setAttribute("required","");
element.style.backgroundColor = "var(--color-text)";
}
function onDatabaseBackendChanged() {
const backend = document.getElementById("form_db_backend").value;
let database = document.getElementById("form_db_database");
let host = document.getElementById("form_db_host");
let port = document.getElementById("form_db_port");
let user = document.getElementById("form_db_user");
let password = document.getElementById("form_db_password");
let url = document.getElementById("form_db_url");
if (backend === "url") {
setOptional(database);
setOptional(host);
setOptional(port);
setOptional(user);
setOptional(password);
setRequired(url);
} else if (backend === "sqlite") {
setRequired(database);
setOptional(host);
setOptional(port);
setOptional(user);
setOptional(password);
setOptional(url);
} else if ((backend === "postgresql") || (backend === "mysql")) {
setRequired(database);
setRequired(host);
setRequired(port);
setRequired(user);
setRequired(password);
setOptional(url);
}
}
function onEmailBackendChanged() {
const backend = document.getElementById("form_email_backend").value;
let user = document.getElementById("form_email_user");
let passwd = document.getElementById("form_email_password");
let host = document.getElementById("form_email_host");
let port = document.getElementById("form_email_port");
let path = document.getElementById("form_email_path");
let dsn = document.getElementById("form_email_dsn");
if (backend === "dsn") {
setOptional(user);
setOptional(passwd);
setOptional(host);
setOptional(port);
setOptional(path);
setRequired(dsn);
} else if ((backend === "none") || (backend === "native")) {
setOptional(user);
setOptional(passwd);
setOptional(host);
setOptional(port);
setOptional(path);
setOptional(dsn);
} else if (backend === "smtp") {
setRequired(user);
setRequired(passwd);
setRequired(host);
setRequired(port);
setOptional(path);
setOptional(dsn);
} else if (backend === "sendmail") {
setOptional(user);
setOptional(passwd);
setOptional(host);
setOptional(port);
setRequired(path);
setOptional(dsn);
}
}
function initializeWebrootSetupForm() {
document.getElementById("form_db_backend").onchange = onDatabaseBackendChanged;
onDatabaseBackendChanged();
document.getElementById("form_email_backend").onchange = onEmailBackendChanged;
onEmailBackendChanged();
console.log("WebrootSetupForm initialized!");
}

View File

View File

@ -11,6 +11,7 @@
"doctrine/doctrine-bundle": "^2.13",
"doctrine/doctrine-migrations-bundle": "^3.3",
"doctrine/orm": "^3.3",
"easycorp/easyadmin-bundle": "^4.18",
"phpdocumentor/reflection-docblock": "^5.6",
"phpstan/phpdoc-parser": "^2.0",
"symfony/asset": "6.4.*",

334
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "261833b7e06c09ff39b49b9b6f4128c8",
"content-hash": "bc953808482a0b93397e27bda2670bdc",
"packages": [
{
"name": "composer/semver",
@ -1313,6 +1313,102 @@
},
"time": "2024-10-21T18:21:57+00:00"
},
{
"name": "easycorp/easyadmin-bundle",
"version": "v4.18.0",
"source": {
"type": "git",
"url": "https://github.com/EasyCorp/EasyAdminBundle.git",
"reference": "c1b694c1890d6f20858802d201cd7199212dc42a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/EasyCorp/EasyAdminBundle/zipball/c1b694c1890d6f20858802d201cd7199212dc42a",
"reference": "c1b694c1890d6f20858802d201cd7199212dc42a",
"shasum": ""
},
"require": {
"doctrine/doctrine-bundle": "^2.5",
"doctrine/orm": "^2.12|^3.0",
"ext-json": "*",
"php": ">=8.1",
"symfony/asset": "^5.4|^6.0|^7.0",
"symfony/cache": "^5.4|^6.0|^7.0",
"symfony/config": "^5.4|^6.0|^7.0",
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
"symfony/deprecation-contracts": "^3.0",
"symfony/doctrine-bridge": "^5.4|^6.0|^7.0",
"symfony/event-dispatcher": "^5.4|^6.0|^7.0",
"symfony/filesystem": "^5.4|^6.0|^7.0",
"symfony/form": "^5.4|^6.0|^7.0",
"symfony/framework-bundle": "^5.4|^6.0|^7.0",
"symfony/http-foundation": "^5.4|^6.0|^7.0",
"symfony/http-kernel": "^5.4|^6.0|^7.0",
"symfony/intl": "^5.4|^6.0|^7.0",
"symfony/property-access": "^5.4|^6.0|^7.0",
"symfony/security-bundle": "^5.4|^6.0|^7.0",
"symfony/string": "^5.4|^6.0|^7.0",
"symfony/translation": "^5.4|^6.0|^7.0",
"symfony/twig-bundle": "^5.4|^6.0|^7.0",
"symfony/uid": "^5.4|^6.0|^7.0",
"symfony/ux-twig-component": "^2.21",
"symfony/validator": "^5.4|^6.0|^7.0"
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.4|3.5.x-dev",
"phpstan/extension-installer": "^1.2",
"phpstan/phpstan": "^1.9",
"phpstan/phpstan-phpunit": "^1.2",
"phpstan/phpstan-strict-rules": "^1.4",
"phpstan/phpstan-symfony": "^1.2",
"psr/log": "^1.0",
"symfony/browser-kit": "^5.4|^6.0|^7.0",
"symfony/css-selector": "^5.4|^6.0|^7.0",
"symfony/debug-bundle": "^5.4|^6.0|^7.0",
"symfony/dom-crawler": "^5.4|^6.0|^7.0",
"symfony/expression-language": "^5.4|^6.0|^7.0",
"symfony/phpunit-bridge": "^6.1|^7.0"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-master": "4.0.x-dev"
}
},
"autoload": {
"psr-4": {
"EasyCorp\\Bundle\\EasyAdminBundle\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Project Contributors",
"homepage": "https://github.com/EasyCorp/EasyAdminBundle/graphs/contributors"
}
],
"description": "Admin generator for Symfony applications",
"homepage": "https://github.com/EasyCorp/EasyAdminBundle",
"keywords": [
"admin",
"backend",
"generator"
],
"support": {
"issues": "https://github.com/EasyCorp/EasyAdminBundle/issues",
"source": "https://github.com/EasyCorp/EasyAdminBundle/tree/v4.18.0"
},
"funding": [
{
"url": "https://github.com/javiereguiluz",
"type": "github"
}
],
"time": "2024-11-28T19:54:15+00:00"
},
{
"name": "egulias/email-validator",
"version": "4.0.2",
@ -5271,6 +5367,85 @@
],
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-uuid",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-uuid.git",
"reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2",
"reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"provide": {
"ext-uuid": "*"
},
"suggest": {
"ext-uuid": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Uuid\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Grégoire Pineau",
"email": "lyrixx@lyrixx.info"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for uuid functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"uuid"
],
"support": {
"source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/process",
"version": "v6.4.15",
@ -6772,6 +6947,80 @@
],
"time": "2024-09-25T14:18:03+00:00"
},
{
"name": "symfony/uid",
"version": "v6.4.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/uid.git",
"reference": "18eb207f0436a993fffbdd811b5b8fa35fa5e007"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/uid/zipball/18eb207f0436a993fffbdd811b5b8fa35fa5e007",
"reference": "18eb207f0436a993fffbdd811b5b8fa35fa5e007",
"shasum": ""
},
"require": {
"php": ">=8.1",
"symfony/polyfill-uuid": "^1.15"
},
"require-dev": {
"symfony/console": "^5.4|^6.0|^7.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Uid\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Grégoire Pineau",
"email": "lyrixx@lyrixx.info"
},
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides an object-oriented API to generate and represent UIDs",
"homepage": "https://symfony.com",
"keywords": [
"UID",
"ulid",
"uuid"
],
"support": {
"source": "https://github.com/symfony/uid/tree/v6.4.13"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-25T14:18:03+00:00"
},
{
"name": "symfony/ux-turbo",
"version": "v2.21.0",
@ -6870,6 +7119,89 @@
],
"time": "2024-10-21T19:07:02+00:00"
},
{
"name": "symfony/ux-twig-component",
"version": "v2.21.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/ux-twig-component.git",
"reference": "5b60b239fffcb04fc8bdb2a5a4001d19442d575d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/5b60b239fffcb04fc8bdb2a5a4001d19442d575d",
"reference": "5b60b239fffcb04fc8bdb2a5a4001d19442d575d",
"shasum": ""
},
"require": {
"php": ">=8.1",
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
"symfony/deprecation-contracts": "^2.2|^3.0",
"symfony/event-dispatcher": "^5.4|^6.0|^7.0",
"symfony/property-access": "^5.4|^6.0|^7.0",
"twig/twig": "^3.8"
},
"conflict": {
"symfony/config": "<5.4.0"
},
"require-dev": {
"symfony/console": "^5.4|^6.0|^7.0",
"symfony/css-selector": "^5.4|^6.0|^7.0",
"symfony/dom-crawler": "^5.4|^6.0|^7.0",
"symfony/framework-bundle": "^5.4|^6.0|^7.0",
"symfony/phpunit-bridge": "^6.0|^7.0",
"symfony/stimulus-bundle": "^2.9.1",
"symfony/twig-bundle": "^5.4|^6.0|^7.0",
"symfony/webpack-encore-bundle": "^1.15"
},
"type": "symfony-bundle",
"extra": {
"thanks": {
"name": "symfony/ux",
"url": "https://github.com/symfony/ux"
}
},
"autoload": {
"psr-4": {
"Symfony\\UX\\TwigComponent\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Twig components for Symfony",
"homepage": "https://symfony.com",
"keywords": [
"components",
"symfony-ux",
"twig"
],
"support": {
"source": "https://github.com/symfony/ux-twig-component/tree/v2.21.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-10-05T22:11:16+00:00"
},
{
"name": "symfony/validator",
"version": "v6.4.15",

View File

@ -13,4 +13,6 @@ return [
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Symfony\UX\TwigComponent\TwigComponentBundle::class => ['all' => true],
EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle::class => ['all' => true],
];

View File

@ -4,10 +4,4 @@ framework:
default_path: '%kernel.project_dir%/translations'
fallbacks:
- en
providers:
mo:
dsn: '%kernel.project_dir%/translations'
locales: [de]
domains: [mydevel_webroot,messages]

View File

@ -0,0 +1,5 @@
twig_component:
anonymous_template_directory: 'components/'
defaults:
# Namespace & directory for components
App\Twig\Components\: 'components/'

4
config/packages/uid.yaml Normal file
View File

@ -0,0 +1,4 @@
framework:
uid:
default_uuid_version: 7
time_based_uuid_version: 7

View File

@ -29,15 +29,6 @@ services:
tags: ['controller.service_arguments']
translation.loader.po:
class: Symfony\Component\Translation\Loader\PoFileLoader
tags:
- { name: translation.loader, alias: po }
tranlation.loader.mo:
class: Symfony\Component\Translation\Loader\MoFileLoader
tags:
- { name: translation.loader, alias: mo }
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones

View File

@ -5,6 +5,8 @@ namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Form\Extension\Core\Type as FormType;
use Symfony\Component\Dotenv\Dotenv;
@ -19,8 +21,7 @@ use App\Entity\WebrootFilePermission;
class SetupController extends WebrootSetupController
{
{
#[Route('/setup',name:'webroot.setup.initial')]
public function initialSetup(Request $request,
EntityManagerInterface $em,
@ -39,10 +40,11 @@ class SetupController extends WebrootSetupController
$have_migration_output=false;
$translations=[
'email'=>_("Email Settings"),
'site' => _("Site Settings"),
'db' => _("Database Settings"),
'user' => _("User Settings"),
'email'=>$this->trans("setupform.section.email"),
'site' => $this->trans("setupform.section.site"),
'db' => $this->trans("setupform.section.database"),
'user' => $this->trans("setupform.section.user"),
'title' => $this->trans("setupform.title"),
];
$form = $this->createSetupForm();
@ -71,40 +73,10 @@ class SetupController extends WebrootSetupController
}
$dotevnlocal = join(DIRECTORY_SEPARATOR,[$this->getProjectDir(),'.env.local']);
$file = fopen($dotevnlocal,"w");
$language=$form->get('language')->getNormData();
if ($language || strlen($language) > 1) {
fwrite($file,"LANG=\"" . $language . "\"\n");
}
fwrite($file,"APP_LOCALEDIR=\"%kernel.project_dir%/Translations\"");
fwrite($file,"SITE_NAME=\"" . $form->get("site_name")->getNormData() . "\"\n");
fwrite($file,"SITE_EMAIL=\"". $form->get('site_email')->getNormData() . "\"\n");
$db_backend=$form->get('db_backend')->getNormData();
if ($db_backend === "sqlite") {
fwrite($file,"DATABASE_URL=\"sqlite://" . $form->get('db_database')->getNormData() . "\"\n");
} elseif ($db_backend === "mysql") {
fwrite("DATABASE_URL=\"msysql://"
. urlencode($form.get('db_user')->getNormData())
. ":" . urlencode($form->get('db_password')->getNormData())
. "@" . urlencode($form->get('db_host')->getNormData())
. ":" . $form.get('db_port')->getNormData()
. "/" . urlencode($form->get('db_database')->getNormData())
. "?charset=utf8mb4\"\n");
} elseif ($db_backend === "postgresql") {
fwrite("DATABASE_URL=\"postgresql://"
. urlencode($form->get('db_user')->getNormData())
. ":" . urlencode($form->get('db_password')->getNormData())
. "@" . urlencode($form->get('db_host')->getNormData())
. ":" . $form->get('db_port')->getNormData()
. "/" . urlencode($form->get('db_database')->getNormData())
. "?charset=utf8");
}
fclose($file);
$this->setSetupDataCookie($form);
(new Dotenv())->loadEnv(join(DIRECTORY_SEPARATOR,[$this->getProjectDir(),".env"]));
$this->redirectToRoute("webroot.setup.run");
/*
try {
$have_migration_output=true;
@ -163,7 +135,7 @@ class SetupController extends WebrootSetupController
throw $ex;
} finally {
}
}*/
}
return $this->render('setup/initial-setup.html.twig', [
@ -176,7 +148,7 @@ class SetupController extends WebrootSetupController
}
#[Route('/setup', name: 'webroot.setup')]
public function index(): Response
public function setup(): Response
{
if (!$this->isInstalled()) {
return redirectToRoute('webroot.setup.initial');
@ -192,12 +164,114 @@ class SetupController extends WebrootSetupController
]);
}
#[Route('/setup/login',name:'webroot.setup.login')]
public function login()
#[ROUTE('/setup/inital/run',name:'setup.initial.run')]
public function setupInitialRun() : Response
{
return $this->render('setup/controller.html.twig', [
'controller_name' => "WebrootController",
'function' => __FUNCTION__,
]);
}
#[Route('/setup/run/make-migration',name:'setup.initial.run.mkmigrations')]
public function setupInitialRunMakeMigrations(): Response
{
if ($this->hasAdministrator()) {
$user = getUser();
if (!$user.isAdmin()) {
return redirectToRoute("app_login");
}
} elseif (!isset($_COOKIE["--webroot-setup--"])) {
throw AccessDeniedException("You do not seem to be the Administrator");
}
try {
$this->runMakeMigration();
$result="SUCCESS";
} catch (\Exception $ex) {
$result="FAILED";
}
$ret = "<!DOCTYPE html>"
. "<html>"
. "<head>"
. "<meta charset=\"utf-8\">"
. "</head>"
. "<body>"
. "<span id=\"run-result\">" . $result ."</span>"
. "<body>"
. "</html>";
return new Response($ret);
}
#[Route('/setup/run/migrate',name:'webroot.setup.run.migrate')]
public function setupRunMigrate()
{
if ($this->hasAdministrator()) {
$user = getUser();
if (!$user.isAdmin()) {
return redirectToRoute("app_login");
}
} elseif (!isset($_COOKIE["--webroot-setup--"])) {
throw AccessDeniedException("You do not seem to be the Administrator");
}
try {
$this->runMigrate();
$result = "SUCCESS";
} catch (\Exception $ex) {
$result = "FAILED";
}
$ret = "<!DOCTYPE html>"
. "<html>"
. "<head>"
. "<meta charset=\"utf-8\">"
. "</head>"
. "<body>"
. "<span id=\"run-result\">" . $result ."</span>"
. "<body>"
. "</html>";
return new Response($ret);
}
#[Route('/setup/run/createdb',name:'webroot.setup.run.createdb')]
public function createDatabase(): Response
{
if ($this->hasAdministrator()) {
$user = getUser();
if (!$user.isAdmin()) {
return redirectToRoute("app_login");
}
} elseif (!isset($_COOKIE["--webroot-setup--"])) {
throw AccessDeniedException("You do not seem to be the Administrator");
}
try {
$this->$this->runCreateDatabase();;
$retsult = "SUCCESS";
} catch (\Exception $ex) {
$result = "FAILED";
}
$ret = "<!DOCTYPE html>"
. "<html>"
. "<head>"
. "<meta charset=\"utf-8\">"
. "</head>"
. "<body>"
. "<span id=\"run-result\">" . $result ."</span>"
. "<body>"
. "</html>";
return new Response($ret);
}
#[Route('/setup/login',name:'webroot.setup.login')]
public function login(): Response
{
return $this->render('setup/controller.html.twig', [
'controller_name' => "WebrootController",
'function' => __F0UNCTION__,
]);
}
}

View File

@ -7,6 +7,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
@ -29,12 +30,16 @@ abstract class WebrootSetupController extends AbstractController
private ?KernelInterface $kernel = null;
private ?string $project_dir = null;
private ?UserPasswordHasherInterface $password_hasher;
private ?TranslatorInterface $translator;
public function __construct(KernelInterface $kernel, UserPasswordHasherInterface $password_hasher) {
public function __construct(KernelInterface $kernel,
UserPasswordHasherInterface $password_hasher,
TranslatorInterface $translator) {
$this->kernel = $kernel;
$this->project_dir = $kernel->getProjectDir();
$this->password_hasher = $password_hasher;
$this->translator = $translator;
Utility\gettext_lib_init(
WEBROOT_GETTEXT_DOMAIN,
join(DIRECTORY_SEPARATOR,[$this->project_dir,"translations"]),
@ -80,6 +85,26 @@ abstract class WebrootSetupController extends AbstractController
return false;
}
public function getKernel(): Kernel
{
return $this->kernel;
}
public function getTranslator(): TranslatorInterface
{
return $this->translator;
}
public function trans(string $message,array $args=[],?string $locale=null,?string $domain=null) : string
{
return $this->translator->trans($message,$args,locale:$locale,domain:$domain);
}
public function translate(string $message,array $args=[],?string $locale=null,?string $domain=null) :string
{
return $this->translator->trans($message,$args,locale:$locale,domain:$domain);
}
/**
* @brief Check if we are already set up.
@ -172,13 +197,27 @@ abstract class WebrootSetupController extends AbstractController
return $content;
}
public function getInitialRoles(): array
{
return [
["role"=>"ROLE_SUPERADMIN","name"=>"Super Administrator","description"=>"Super Administrator with all rights, always!"],
["role"=>"ROLE_ADMIN","name"=>"Administrator","description"=>"Role with administrative rights."],
["role"=>"ROLE_USER","name"=>"Common User","description"=>"Common user rights."],
["role"=>"ROLE_PUBLIC","name"=>"Public Access","description"=>"Role for public access."],
[
"role"=>"ROLE_SUPERADMIN",
"name"=>$this->trans("role.superadmin.name",locale:'en'),
"description"=>$this->trans("role.superadmin.descr",locale:'en'),
],[
"role"=>"ROLE_ADMIN",
"name"=>$this->trans("role.admin.name",locale:'en'),
"description"=>$this->trans("role.admin.descr"),
],[
"role"=>"ROLE_USER",
"name"=>$this->trans("role.user.name",locale:'en'),
"description"=>$this->trans("role.user.descr",locale:'en'),
],[
"role"=>"ROLE_PUBLIC",
"name"=>$this->trans("role.public.name",locale:'en'),
"description"=>$this->trans("role.public.descr",locale:'en'),
],
];
}
@ -186,133 +225,164 @@ abstract class WebrootSetupController extends AbstractController
{
return $this->createFormBuilder()
->add('env', FormType\ChoiceType::class,[
"label"=>"Environment",
"label"=>$this->translator->trans("setupform.env.label"),
"mapped"=>false,
"required"=>true,
"help"=>$this->trans("setupform.env.help"),
"choices"=>[
"Production" => "prod",
"Development" => "dev"
$this->translator->trans("setupform.env.choices.prod") => "prod",
$this->translator->trans("setupform.env.choices.dev") => "dev",
],
"attr"=>["class"=>"full-width"],
"data" => "prod"])
->add('locale',FormType\TextType::class,[
"mapped"=>false,
"required"=>false])
"label"=>$this->trans("setupform.locale.label"),
"help"=>$this->trans("setupform.locale.help"),
"required"=>false,
"attr"=>["class"=>"full-width"]])
->add('tempdir',FormType\TextType::class,[
"mapped"=>false,
"required"=>false])
"label"=>$this->trans("setupform.tempdir.label"),
"required"=>false,
"attr"=>["class"=>"full-width"]])
->add('site_name',FormType\TextType::class,[
"mapped"=>false,
"required"=>true,
"help"=>_("Enter the site name that should be displayed in titles."),
"label" => _("Site name")])
"help"=>$this->trans("setupform.site.name.help"),
"label" => $this->trans("setupform.site.name.label"),
"attr"=>["class"=>"full-width"]])
->add('site_rootdir',FormType\TextType::class,[
"mapped"=>false,
"required"=>true,
"label" => _("Site root directory")])
"help"=>$this->trans("setupform.site.root.help"),
"label" => $this->trans("setupform.site.root.label"),
"attr"=>["class"=>"full-width"]])
->add('site_email',FormType\EmailType::class,[
"mapped"=>false,
"required"=>false,
"label" => _("Contact email")])
"label" => $this->trans("setupform.site.email.label"),
"attr"=>["class"=>"full-width"]])
->add('user_username',FormType\TextType::class,[
"mapped"=>false,
"required"=>true,
"label"=>_("Username")])
"label"=>$this->trans("setupform.user.username.label"),
"attr"=>["class"=>"full-width"]])
->add('user_email',FormType\EmailType::class,[
"mapped"=>false,
"required"=>true,
"label"=> _("Email")])
"label"=> $this->trans("setupform.user.email.label"),
"attr"=>["class"=>"full-width"]])
->add("user_password0", FormType\PasswordType::class,[
"mapped"=>false,
"required"=>true,
"label"=>_("Password")])
"label"=>$this->trans("setupform.user.password.label"),
"attr"=>["class"=>"full-width"]])
->add("user_password1",FormType\PasswordType::class,[
"mapped"=>false,
"required"=>true,
"label"=>"Confirm Password"])
"label"=>$this->trans("setupform.user.confpasswd.label"),
"attr"=>["class"=>"full-width"]])
->add('db_migrate', FormType\CheckboxType::class,[
"mapped"=>false,
"label"=>"Run Migrations?",
"label"=>$this->trans("setupform.db.migrate.label"),
"attr"=>["checked"=>true],
"required"=>false])
->add('db_create', FormType\CheckboxType::class,[
"mapped"=>false,
"label"=>"Create Database",
"label"=>$this->trans("setupform.db.create.label"),
"required"=>false,
"attr"=>["checked"=>false]])
->add('db_backend',FormType\ChoiceType::class,[
"mapped" => false,
"label" => "Backend",
"label" => $this->trans("setupform.db.backend.label"),
"required" => true,
"choices" => [
_("SQLite3")=>"sqlite",
_("MySQL/MariaDB")=>"mysql",
_("PostgreSQL")=>"postgresql",
_("Database URL")=>"url",
]])
$this->trans("setupform.db.backend.choices.sqlite")=>"sqlite",
$this->trans("setupform.db.backend.choices.mysql")=>"mysql",
$this->trans("setupform.db.backend.choices.portgesql")=>"postgresql",
$this->trans("setupform.db.backend.choices.url")=>"url",
],
"attr"=>["class"=>"full-width"]])
->add('db_database',FormType\TextType::class,[
"mapped"=>false,
"required"=>true,
"label"=>_("Database")])
"label"=>$this->trans("setupform.db.database.label"),
"attr"=>["class"=>"full-width"]])
->add('db_host', FormType\TextType::class,[
"mapped"=>false,
"required"=>false,
"label"=>_("Host")])
"label"=>$this->trans("setupform.db.host.label"),
"attr"=>["class"=>"full-width"]])
->add('db_port', FormType\IntegerType::class,[
"mapped"=>false,
"required"=>false,
"label"=>_("Port")])
"label"=>$this->trans("setupform.db.port.label"),
"attr"=>["class"=>"full-width"]])
->add('db_user',FormType\TextType::class,[
"mapped"=>false,
"required"=>false,
"label"=>_("User")])
"label"=>$this->trans("setupform.db.user.label"),
"attr"=>["class"=>"full-width"]])
->add('db_password',FormType\TextType::class,[
"mapped"=>false,
"required"=>false,
"label"=>_("Password")])
"label"=>$this->trans("setupform.db.password.label"),
"attr"=>["class"=>"full-width"]])
->add('db_url',FormType\TextType::class,[
"mapped"=>false,
"label"=>_("Database URL"),
"required"=>false])
"label"=>$this->trans("setupform.db.url.label"),
"required"=>false,
"attr"=>["class"=>"full-width"]])
->add('email_backend', FormType\ChoiceType::class, [
"mapped"=>false,
"label" => _("Backend"),
"label" => $this->trans("setupform.email.backend.label"),
"required"=>true,
"choices"=> [
_("No email support")=>"none",
_("SMTP")=>"smtp",
_("Sendmail")=>"sendmail",
_("Naitve")=>"native",
_("User DSN")=>"dsn",
]])
$this->trans("setupform.email.backend.choices.none")=>"none",
$this->trans("setupform.email.backend.choices.smtp")=>"smtp",
$this->trans("setupform.email.backend.choices.sendmail")=>"sendmail",
$this->trans("setupform.email.backend.choices.native")=>"native",
$this->trans("setupform.email.backend.choices.dsn")=>"dsn",
],
"attr"=>["class"=>"full-width"]])
->add('email_path',FormType\TextType::class,[
"mapped"=>false,
"required"=>false,
"label"=>_("Email Path")])
"label"=>$this->trans("setupform.email.path.label"),
"attr"=>["class"=>"full-width"]])
->add('email_user',FormType\TextType::class,[
"mapped"=>false,
"required"=>false,
"label"=>_("User")])
"label"=>$this->trans("setupform.email.user.label"),
"attr"=>["class"=>"full-width"]])
->add('email_password', FormType\PasswordType::class,[
"mapped"=>false,
"required"=>false,
"label"=> _("Password")])
"label"=> $this->trans("setupform.email.password.label"),
"attr"=>["class"=>"full-width"]])
->add('email_host',FormType\TextType::class,[
"mapped"=>false,
"required"=>false,
"label"=>_("SMTP Host")])
"label"=>$this->trans("setupform.email.host.label"),
"attr"=>["class"=>"full-width"]])
->add('email_port',FormType\TextType::class,[
"mapped"=>false,
"required"=>false,
"label"=>_("SMTP Port")])
"label"=>$this->trans("setupform.email.smtp-port.label"),
"attr"=>["class"=>"full-width"]])
->add('email_dsn',FormType\TextType::class,[
"mapped"=>false,
"required"=>false,
"label"=>_("DSN")])
"label"=>$this->trans("setupform.email.dsn.label"),
"attr"=>["class"=>"full-width"]])
->add('email_address', FormType\EmailType::class,[
"mapped"=>false,
"required"=>true,
"label"=>_("Sender address")])
->add('submit', FormType\SubmitType::class)
"label"=>$this->trans("setupform.email.sender.label"),
"attr"=>["class"=>"full-width"]])
->add('submit', FormType\SubmitType::class,[
"label"=>$this->trans("setupform.submit")])
->getForm();
}
@ -549,6 +619,18 @@ abstract class WebrootSetupController extends AbstractController
$fclose($file);
}
function generateRandomString($length = 10) : string
{
$x='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$x_len=strlen($x);
$ret="";
for ($i=0; $i< $length; ++$i) {
$ret .= x[rand() % $x_len];
}
return ret;
}
public function setSetupFormFromFile(Form $form,string $yaml_file)
{
$this->setSetupFormFromData($form,(new Yaml())->parseFile($yaml_file));
@ -564,4 +646,40 @@ abstract class WebrootSetupController extends AbstractController
$this->exportSetupFormToFile($form, $filename);
setcookie("--webroot-setup--", $filename);
}
public function writePrivEnv(array $data,bool $generate_app_secret=true) {
$file = fopen(join(DIRECTORY_SEPARATOR,[$this->project_dir,".env.local"]));
fwrite($file,"ENV=" . $data["env"] . "\n");
if ($generate_app_secret) {
fwrite($file,"APP_SECRET=\"" . $this->generateRandomString(60) . "\"\n");
} else {
fwrite($file,"APP_SECET=\"" . getenv("APP_SECRET") . "\"\n");
}
fwrite($file,"SITE_NAME=\"" . $data['site']['site_name'] . "\"\n");
fwrite($file,"SITE_EMAIL=\"". $data['site']['email'] . "\"\n");
$db_backend=$data['database']['backend'];
if ($db_backend === "sqlite") {
fwrite($file,"DATABASE_URL=\"sqlite://" . $data['database']['database'] . "\"\n");
} elseif ($db_backend === "mysql") {
fwrite($file,"DATABASE_URL=\"msysql://"
. urlencode($data['database']['user'])
. ":" . urlencode($data['database']['password'])
. "@" . urlencode($data['database']['host'])
. ":" . $data['database']['port']
. "/" . urlencode($data['database']['database'])
. "?charset=utf8mb4\"\n");
} elseif ($db_backend === "postgresql") {
fwrite($file,"DATABASE_URL=\"postgresql://"
. urlencode($data['database']['user'])
. ":" . urlencode($data['database']['password'])
. "@" . urlencode($data['database']['host'])
. ":" . $data['database']['port']
. "/" . urlencode($data['database']['database'])
. "?charset=utf8");
} elseif ($db_backend === "url") {
fwrite($file,"DATABASE_URL=\"" . $data["database"]["url"] . "\"\n");
}
}
}

View File

@ -26,6 +26,15 @@
"./migrations/.gitignore"
]
},
"easycorp/easyadmin-bundle": {
"version": "4.18",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "3.0",
"ref": "b131e6cbfe1b898a508987851963fff485986285"
}
},
"phpunit/phpunit": {
"version": "9.6",
"recipe": {
@ -248,6 +257,18 @@
"./templates/base.html.twig"
]
},
"symfony/uid": {
"version": "6.4",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.2",
"ref": "d294ad4add3e15d7eb1bae0221588ca89b38e558"
},
"files": [
"./config/packages/uid.yaml"
]
},
"symfony/ux-turbo": {
"version": "2.21",
"recipe": {
@ -257,6 +278,18 @@
"ref": "9dd2778a116b6e5e01e5e1582d03d5a9e82630de"
}
},
"symfony/ux-twig-component": {
"version": "2.21",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "2.13",
"ref": "67814b5f9794798b885cec9d3f48631424449a01"
},
"files": [
"./config/packages/twig_component.yaml"
]
},
"symfony/validator": {
"version": "6.4",
"recipe": {

View File

@ -1,7 +1,11 @@
{% extends 'base.html.twig' %}
{% block javascripts %}
{% block importmap %}{{ importmap('app') }}{% endblock %}
<script src="{{ asset('webroot/mydevel_webroot_setup_initial.js') }}"></script>
{% endblock %}
{% block body %}
<h1>Webroot Setup</h1>
<h1>{{ translations.title }}</h1>
<div class="form-box">
<div class="error-message">
<ul>
@ -12,48 +16,49 @@
</div>
{{ form_start(form) }}
<fieldset>
<fieldset class="margin-10">
<legend><strong>{{ translations.site }}</strong></legend>
<table class="full-width">
<tr>
<th class="standard-background left shrink-to-fit">{{ form_label(form.locale) }}</th>
<td>{{ form_widget(form.locale) }}</td>
<th class="standard-background left shrink-to-fit">{{ form_label(form.env) }}</th>
<td>{{ form_widget(form.env) }}</td>
<td>{{ form_widget(form.env,{'attr':{'tabindex':1}}) }}</td>
<th class="standard-background left shrink-to-fit">{{ form_label(form.site_name) }}</th>
<td>{{ form_widget(form.site_name,{'attr':{'tabindex':4}}) }}</td>
</tr>
<tr>
<th class="standard-background left shrink-to-fit">{{ form_label(form.site_name) }}</th>
<td>{{ form_widget(form.site_name) }}</td>
<th class="standard-background left shrink-to-fit">{{ form_label(form.site_email) }}</th>
<td>{{ form_widget(form.site_email) }}</td>
<th class="standard-background left shrink-to-fit">{{ form_label(form.locale) }}</th>
<td>{{ form_widget(form.locale,{'attr':{'tabindex':2}}) }}</td>
<th class="standard-background left shrink-to-fit">{{ form_label(form.site_rootdir) }}</th>
<td>{{ form_widget(form.site_rootdir,{'attr':{'tabindex':5}}) }}</td>
</tr>
<tr>
<th class="standard-background left shrink-to-fit">{{ form_label(form.site_rootdir) }}</th>
<td>{{ form_widget(form.site_rootdir) }}</td>
<th class="standard-background left shrink-to-fit">{{ form_label(form.site_email) }}</th>
<td>{{ form_widget(form.site_email,{'attr':{'tabindex':3}}) }}</td>
<th class="standard-background left shrink-to-fit">{{ form_label(form.tempdir) }}</th>
<td>{{ form_widget(form.tempdir) }}</td>
<td>{{ form_widget(form.tempdir,{'attr':{'tabindex':6}}) }}</td>
</tr>
</table>
</fieldset>
<fieldset>
<fieldset class="margin-10">
<legend><strong>{{ translations.user }}</strong></legend>
<table class="full-width">
<tr>
<th class="standard-background left shrink-to-fit">{{ form_label(form.user_username) }}</th>
<td>{{ form_widget(form.user_username) }}</td>
<td>{{ form_widget(form.user_username,{'attr':{'tabindex':7}}) }}</td>
<th class="standard-background left shrink-to-fit">{{ form_label(form.user_password0) }}</th>
<td>{{ form_widget(form.user_password0) }}</td>
<td>{{ form_widget(form.user_password0,{'attr':{'tabindex':9}}) }}</td>
</tr>
<tr>
<th class="standard-background left shrink-to-fit">{{ form_label(form.user_email) }}</th>
<td>{{ form_widget(form.user_email) }}</td>
<td>{{ form_widget(form.user_email,{'attr':{'tabindex':8}}) }}</td>
<th class="standard-background left shrink-to-fit">{{ form_label(form.user_password1) }}</th>
<td>{{ form_widget(form.user_password1) }}</td>
<td>{{ form_widget(form.user_password1,{'attr':{'tabindex':10}}) }}</td>
</tr>
</table>
</fieldset>
<fieldset>
<fieldset class="margin-10" onload="setupform.initDatabaseSettings();">
<legend><strong>{{ translations.db }}</strong></legend>
<table class="full-width">
<tr>
@ -83,7 +88,7 @@
</tr>
</table>
</fieldset>
<fieldset>
<fieldset class="margin-10" onload="setupform.initEmailSettings()">
<legend><strong>{{ translations.email }}</strong></legend>
<table class="full-width">
<tr>
@ -114,8 +119,8 @@
</tr>
</table>
</fieldset>
{{ form_widget(form.submit) }}
<div class="full-width right">{{ form_widget(form.submit) }}</div>
{{ form_end(form) }}
<div>
<script>initializeWebrootSetupForm();</script>
{% endblock %}

View File

@ -0,0 +1,72 @@
{% extends 'base.html.twig' %}
{% block body %}
<script>
const setup_commands = [
for {{ cmd in setup_commands }}; do
{ "url":{{ cmd.url }}, "output": {{ cmd.output }} },
{{ endfor }}
];
redirect_error="{{ setup_url }}";
function endCommands(success) {
element = document.getElementById("setup-command");
p = document.createElement("p");
if (success) {
p.innerText = "Commands executed successfully!";
} else {
p.innerText = "Executing commands failed!";
}
element.appendChild(p);
}
function appendCommand(n) {
element = document.getElementById("setup-command");
p = document.createElement("p");
span = document.createElement("span");
span.innerText = setup_commands[n]["output"];
iframe = document.createElement("iframe");
iframe.setAttribute("id","setup-command-" + n);
iframe.setAttribute("onload","onCommandLoaded(n);");
iframe.setAttribute("src",setup_commands[n]["url"]);
p.appendChild(span);
p.appendChild(iframe);
}
function onCommandLoaded(n) {
let element = document.getElementById("setup-command");
let cmd_iframe = document.getElementById("setup-command-" + n);
let cmd_result = iframe.contentWindow.document.getElementById("command-result");
if (cmd_result === "SUCCESS") {
next = n + 1;
if (n < setup_commands.length) {
appendCommand(next);
} else {
endCommands(true);
document.getElementById("commands-finished-button").removeAttribute("disabled");
}
} else {
endCommands(false);
let button = document.getElementById("commands-finished-button");
button.innerText("Return to setup");
button.removeAttribute("disabled");
}
}
function onCommandInit()
{
if (setup_commands.length > 0) {
appendCommand(0);
} else {
endCommands(true);
}
}
</script>
<h1>Running Setup</h1>
<div class="command-output">
<code id="setup-commands" onload="">
<p id="null-command">Running commands...</p>
</code>
</div>
<button id="commands-finished-button" onclick="window.replace(\"{{ success_url }}\")" disabled>Finish</button>
{% endblock %}

View File

@ -0,0 +1,97 @@
role:
superadmin:
name: Superadministrator
descr: 'Superadministrator hat alle Rechte.'
admin:
name: Administrator
descr: 'Rolle für Administrator rechte.'
user:
name: 'Common User'
descr: 'Standardnutzer Role'
public:
name: Public
descr: 'Role für öffentlichen Zugang'
setupform:
env:
label: Umgebung
help: 'Applikationsumgebung. Nutze Produktion in öffentlich zugänglichen Netzerken! "Entiwcklung" ist nur für die Entwicklung gedacht!!!'
choices:
prod: Produktion
dev: Entwicklung
locale:
label: Locale
help: 'Gibt die bevorzugte Sprache ein.'
tempdir:
label: 'Temp Ordner'
site:
name:
help: 'Gib den Namen der Site an, welche in den Titeln angezeigt werden soll.'
label: 'Name der Site'
root:
help: 'Das Stammverzeichnis der Site.'
label: Stammverzeichnis
email:
label: Kontaktemail
user:
username:
label: Nutzername
email:
label: Email
password:
label: Passwort
confpasswd:
label: 'Passwort bestätigen'
db:
migrate:
label: 'Migriere Datenbank'
backend:
choices:
sqlite: SQLite3
mysql: MySQL/MariaDB
portgesql: PostgreSQL
url: URL/DSN
label: Backend
database:
label: Datenbank
host:
label: Host
port:
label: Port
user:
label: Nutzer
password:
label: Passwort
url:
label: URL/DSN
create:
label: 'Erstelle Datenbank'
email:
backend:
choices:
none: 'Keine Emailunterstützung'
smtp: SMTP
sendmail: Sendmail
native: Nativ
dsn: 'Eigener DSN'
label: Backend
path:
label: Pfad
user:
label: Nutzer
password:
label: Passwort
host:
label: 'SMTP Host'
smtp-port:
label: 'SMTP Port'
sender:
label: Senderemail
dsn:
label: DSN
section:
email: Emailunterstützung
site: Site
database: Datenbank
user: 'Neuer Administrator'
title: 'Webroot Installation'
submit: 'Einstellungen andwenden und Setup ausführen'

View File

@ -0,0 +1,92 @@
'Email Settings': '__Email Settings'
'Site Settings': '__Site Settings'
'Database Settings': '__Database Settings'
'User Settings': '__User Settings'
role:
superadmin:
name: __role.superadmin.name
descr: __role.superadmin.descr
admin:
name: __role.admin.name
descr: __role.admin.descr
user:
name: __role.user.name
descr: __role.user.descr
public:
name: __role.public.name
descr: __role.public.descr
setupform:
env:
label: __setupform.env.label
help: __setupform.env.help
choices:
prod: __setupform.env.choices.prod
dev: __setupform.env.choices.dev
locale:
label: __setupform.locale.label
help: __setupform.locale.help
tempdir:
label: __setupform.tempdir.label
site:
name:
help: __setupform.site.name.help
label: __setupform.site.name.label
root:
help: __setupform.site.root.help
label: __setupform.site.root.label
email:
label: __setupform.site.email.label
user:
username:
label: __setupform.user.username.label
email:
label: __setupform.user.email.label
password:
label: __setupform.user.password.label
confpasswd:
label: __setupform.user.confpasswd.label
db:
migrate:
label: __setupform.db.migrate.label
backend:
choices:
sqlite: __setupform.db.backend.choices.sqlite
mysql: __setupform.db.backend.choices.mysql
portgesql: __setupform.db.backend.choices.portgesql
url: __setupform.db.backend.choices.url
database:
label: __setupform.db.database.label
host:
label: __setupform.db.host.label
port:
label: __setupform.db.port.label
user:
label: __setupform.db.user.label
password:
label: __setupform.db.password.label
url:
label: __setupform.db.url.label
email:
backend:
choices:
none: __setupform.email.backend.choices.none
smtp: __setupform.email.backend.choices.smtp
sendmail: __setupform.email.backend.choices.sendmail
native: __setupform.email.backend.choices.native
dsn: __setupform.email.backend.choices.dsn
path:
label: __setupform.email.path.label
user:
label: __setupform.email.user.label
password:
label: __setupform.email.password.label
host:
label: __setupform.email.host.label
smtp-port:
label: __setupform.email.smtp-port.label
sender:
label: __setupform.email.sender.label
stupform:
db:
create:
label: __stupform.db.create.label

View File

@ -1,187 +1 @@
'This value should be false.': 'Dieser Wert sollte false sein.'
'This value should be true.': 'Dieser Wert sollte true sein.'
'This value should be of type {{ type }}.': 'Dieser Wert sollte vom Typ {{ type }} sein.'
'This value should be blank.': 'Dieser Wert sollte leer sein.'
'The value you selected is not a valid choice.': 'Sie haben einen ungültigen Wert ausgewählt.'
'You must select at least {{ limit }} choice':
'|You must select at least {{ limit }} choices.': 'Sie müssen mindestens {{ limit }} Möglichkeit wählen.|Sie müssen mindestens {{ limit }} Möglichkeiten wählen.'
'You must select at most {{ limit }} choice':
'|You must select at most {{ limit }} choices.': 'Sie dürfen höchstens {{ limit }} Möglichkeit wählen.|Sie dürfen höchstens {{ limit }} Möglichkeiten wählen.'
'One or more of the given values is invalid.': 'Einer oder mehrere der angegebenen Werte sind ungültig.'
'This field was not expected.': 'Dieses Feld wurde nicht erwartet.'
'This field is missing.': 'Dieses Feld fehlt.'
'This value is not a valid date.': 'Dieser Wert entspricht keiner gültigen Datumsangabe.'
'This value is not a valid datetime.': 'Dieser Wert entspricht keiner gültigen Datums- und Zeitangabe.'
'This value is not a valid email address.': 'Dieser Wert ist keine gültige E-Mail-Adresse.'
'The file could not be found.': 'Die Datei wurde nicht gefunden.'
'The file is not readable.': 'Die Datei ist nicht lesbar.'
'The file is too large ({{ size }} {{ suffix }})':
' Allowed maximum size is {{ limit }} {{ suffix }}.': 'Die Datei ist zu groß ({{ size }} {{ suffix }}). Die maximal zulässige Größe beträgt {{ limit }} {{ suffix }}.'
'The mime type of the file is invalid ({{ type }})':
' Allowed mime types are {{ types }}.': 'Der Dateityp ist ungültig ({{ type }}). Erlaubte Dateitypen sind {{ types }}.'
'This value should be {{ limit }} or less.': 'Dieser Wert sollte kleiner oder gleich {{ limit }} sein.'
'This value is too long':
' It should have {{ limit }} character or less':
'|This value is too long':
' It should have {{ limit }} characters or less.': 'Diese Zeichenkette ist zu lang. Sie sollte höchstens {{ limit }} Zeichen haben.|Diese Zeichenkette ist zu lang. Sie sollte höchstens {{ limit }} Zeichen haben.'
' It should contain one word':
'|This value is too long':
' It should contain {{ max }} words or less.': 'Dieser Wert ist zu lang. Er darf maximal aus einem Wort bestehen.|Dieser Wert ist zu lang. Er darf maximal {{ max }} Wörter enthalten.'
'This value should be {{ limit }} or more.': 'Dieser Wert sollte größer oder gleich {{ limit }} sein.'
'This value is too short':
' It should have {{ limit }} character or more':
'|This value is too short':
' It should have {{ limit }} characters or more.': 'Diese Zeichenkette ist zu kurz. Sie sollte mindestens {{ limit }} Zeichen haben.|Diese Zeichenkette ist zu kurz. Sie sollte mindestens {{ limit }} Zeichen haben.'
' It should contain at least one word':
'|This value is too short':
' It should contain at least {{ min }} words.': 'Dieser Wert ist zu kurz. Er muss aus mindestens einem Wort bestehen.|Dieser Wert ist zu kurz. Er muss mindestens {{ min }} Wörter enthalten.'
'This value should not be blank.': 'Dieser Wert sollte nicht leer sein.'
'This value should not be null.': 'Dieser Wert sollte nicht null sein.'
'This value should be null.': 'Dieser Wert sollte null sein.'
'This value is not valid.': 'Dieser Wert ist nicht gültig.'
'This value is not a valid time.': 'Dieser Wert entspricht keiner gültigen Zeitangabe.'
'This value is not a valid URL.': 'Dieser Wert ist keine gültige URL.'
'The two values should be equal.': 'Die beiden Werte sollten identisch sein.'
'The file is too large':
' Allowed maximum size is {{ limit }} {{ suffix }}.': 'Die Datei ist zu groß. Die maximal zulässige Größe beträgt {{ limit }} {{ suffix }}.'
'The file is too large.': 'Die Datei ist zu groß.'
'The file could not be uploaded.': 'Die Datei konnte nicht hochgeladen werden.'
'This value should be a valid number.': 'Dieser Wert sollte eine gültige Zahl sein.'
'This file is not a valid image.': 'Diese Datei ist kein gültiges Bild.'
'This is not a valid IP address.': 'Dieser Wert ist keine gültige IP-Adresse.'
'This value is not a valid language.': 'Dieser Wert entspricht keiner gültigen Sprache.'
'This value is not a valid locale.': 'Dieser Wert entspricht keinem gültigen Gebietsschema.'
'This value is not a valid country.': 'Dieser Wert entspricht keinem gültigen Land.'
'This value is already used.': 'Dieser Wert wird bereits verwendet.'
'The size of the image could not be detected.': 'Die Größe des Bildes konnte nicht ermittelt werden.'
'The image width is too big ({{ width }}px)':
' Allowed maximum width is {{ max_width }}px.': 'Die Bildbreite ist zu groß ({{ width }}px). Die maximal zulässige Breite beträgt {{ max_width }}px.'
'The image width is too small ({{ width }}px)':
' Minimum width expected is {{ min_width }}px.': 'Die Bildbreite ist zu gering ({{ width }}px). Die erwartete Mindestbreite beträgt {{ min_width }}px.'
'The image height is too big ({{ height }}px)':
' Allowed maximum height is {{ max_height }}px.': 'Die Bildhöhe ist zu groß ({{ height }}px). Die maximal zulässige Höhe beträgt {{ max_height }}px.'
'The image height is too small ({{ height }}px)':
' Minimum height expected is {{ min_height }}px.': 'Die Bildhöhe ist zu gering ({{ height }}px). Die erwartete Mindesthöhe beträgt {{ min_height }}px.'
"This value should be the user's current password.": 'Dieser Wert sollte dem aktuellen Benutzerpasswort entsprechen.'
'This value should have exactly {{ limit }} character':
'|This value should have exactly {{ limit }} characters.': 'Dieser Wert sollte genau {{ limit }} Zeichen lang sein.|Dieser Wert sollte genau {{ limit }} Zeichen lang sein.'
'The file was only partially uploaded.': 'Die Datei wurde nur teilweise hochgeladen.'
'No file was uploaded.': 'Es wurde keine Datei hochgeladen.'
'No temporary folder was configured in php':
ini.: 'Es wurde kein temporärer Ordner in der php.ini konfiguriert oder der temporäre Ordner existiert nicht.'
'Cannot write temporary file to disk.': 'Kann die temporäre Datei nicht speichern.'
'A PHP extension caused the upload to fail.': 'Eine PHP-Erweiterung verhinderte den Upload.'
'This collection should contain {{ limit }} element or more':
'|This collection should contain {{ limit }} elements or more.': 'Diese Sammlung sollte {{ limit }} oder mehr Elemente beinhalten.|Diese Sammlung sollte {{ limit }} oder mehr Elemente beinhalten.'
'This collection should contain {{ limit }} element or less':
'|This collection should contain {{ limit }} elements or less.': 'Diese Sammlung sollte {{ limit }} oder weniger Elemente beinhalten.|Diese Sammlung sollte {{ limit }} oder weniger Elemente beinhalten.'
'This collection should contain exactly {{ limit }} element':
'|This collection should contain exactly {{ limit }} elements.': 'Diese Sammlung sollte genau {{ limit }} Element beinhalten.|Diese Sammlung sollte genau {{ limit }} Elemente beinhalten.'
'Invalid card number.': 'Ungültige Kartennummer.'
'Unsupported card type or invalid card number.': 'Nicht unterstützter Kartentyp oder ungültige Kartennummer.'
'This is not a valid International Bank Account Number (IBAN).': 'Dieser Wert ist keine gültige Internationale Bankkontonummer (IBAN).'
'This value is not a valid ISBN-10.': 'Dieser Wert entspricht keiner gültigen ISBN-10.'
'This value is not a valid ISBN-13.': 'Dieser Wert entspricht keiner gültigen ISBN-13.'
'This value is neither a valid ISBN-10 nor a valid ISBN-13.': 'Dieser Wert ist weder eine gültige ISBN-10 noch eine gültige ISBN-13.'
'This value is not a valid ISSN.': 'Dieser Wert ist keine gültige ISSN.'
'This value is not a valid currency.': 'Dieser Wert ist keine gültige Währung.'
'This value should be equal to {{ compared_value }}.': 'Dieser Wert sollte gleich {{ compared_value }} sein.'
'This value should be greater than {{ compared_value }}.': 'Dieser Wert sollte größer als {{ compared_value }} sein.'
'This value should be greater than or equal to {{ compared_value }}.': 'Dieser Wert sollte größer oder gleich {{ compared_value }} sein.'
'This value should be identical to {{ compared_value_type }} {{ compared_value }}.': 'Dieser Wert sollte identisch sein mit {{ compared_value_type }} {{ compared_value }}.'
'This value should be less than {{ compared_value }}.': 'Dieser Wert sollte kleiner als {{ compared_value }} sein.'
'This value should be less than or equal to {{ compared_value }}.': 'Dieser Wert sollte kleiner oder gleich {{ compared_value }} sein.'
'This value should not be equal to {{ compared_value }}.': 'Dieser Wert sollte nicht {{ compared_value }} sein.'
'This value should not be identical to {{ compared_value_type }} {{ compared_value }}.': 'Dieser Wert sollte nicht identisch sein mit {{ compared_value_type }} {{ compared_value }}.'
'The image ratio is too big ({{ ratio }})':
' Allowed maximum ratio is {{ max_ratio }}.': 'Das Seitenverhältnis des Bildes ist zu groß ({{ ratio }}). Der erlaubte Maximalwert ist {{ max_ratio }}.'
'The image ratio is too small ({{ ratio }})':
' Minimum ratio expected is {{ min_ratio }}.': 'Das Seitenverhältnis des Bildes ist zu klein ({{ ratio }}). Der erwartete Minimalwert ist {{ min_ratio }}.'
'The image is square ({{ width }}x{{ height }}px)':
' Square images are not allowed.': 'Das Bild ist quadratisch ({{ width }}x{{ height }}px). Quadratische Bilder sind nicht erlaubt.'
'The image is landscape oriented ({{ width }}x{{ height }}px)':
' Landscape oriented images are not allowed.': 'Das Bild ist im Querformat ({{ width }}x{{ height }}px). Bilder im Querformat sind nicht erlaubt.'
'The image is portrait oriented ({{ width }}x{{ height }}px)':
' Portrait oriented images are not allowed.': 'Das Bild ist im Hochformat ({{ width }}x{{ height }}px). Bilder im Hochformat sind nicht erlaubt.'
'An empty file is not allowed.': 'Eine leere Datei ist nicht erlaubt.'
'The host could not be resolved.': 'Der Hostname konnte nicht aufgelöst werden.'
'This value does not match the expected {{ charset }} charset.': 'Dieser Wert entspricht nicht dem erwarteten Zeichensatz {{ charset }}.'
'This is not a valid Business Identifier Code (BIC).': 'Dieser Wert ist keine gültige internationale Bankleitzahl (BIC).'
Error: Fehler
'This is not a valid UUID.': 'Dieser Wert ist keine gültige UUID.'
'This value should be a multiple of {{ compared_value }}.': 'Dieser Wert sollte ein Vielfaches von {{ compared_value }} sein.'
'This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}.': 'Diese internationale Bankleitzahl (BIC) ist nicht mit der IBAN {{ iban }} assoziiert.'
'This value should be valid JSON.': 'Dieser Wert sollte gültiges JSON sein.'
'This collection should contain only unique elements.': 'Diese Sammlung darf keine doppelten Elemente enthalten.'
'This value should be positive.': 'Diese Zahl sollte positiv sein.'
'This value should be either positive or zero.': 'Diese Zahl sollte entweder positiv oder 0 sein.'
'This value should be negative.': 'Diese Zahl sollte negativ sein.'
'This value should be either negative or zero.': 'Diese Zahl sollte entweder negativ oder 0 sein.'
'This value is not a valid timezone.': 'Dieser Wert ist keine gültige Zeitzone.'
'This password has been leaked in a data breach, it must not be used':
' Please use another password.': 'Dieses Passwort ist Teil eines Datenlecks, es darf nicht verwendet werden.'
'This value should be between {{ min }} and {{ max }}.': 'Dieser Wert sollte zwischen {{ min }} und {{ max }} sein.'
'This value is not a valid hostname.': 'Dieser Wert ist kein gültiger Hostname.'
'The number of elements in this collection should be a multiple of {{ compared_value }}.': 'Die Anzahl an Elementen in dieser Sammlung sollte ein Vielfaches von {{ compared_value }} sein.'
'This value should satisfy at least one of the following constraints:': 'Dieser Wert sollte eine der folgenden Bedingungen erfüllen:'
'Each element of this collection should satisfy its own set of constraints.': 'Jedes Element dieser Sammlung sollte seine eigene Menge an Bedingungen erfüllen.'
'This value is not a valid International Securities Identification Number (ISIN).': 'Dieser Wert ist keine gültige Internationale Wertpapierkennnummer (ISIN).'
'This value should be a valid expression.': 'Dieser Wert sollte eine gültige Expression sein.'
'This value is not a valid CSS color.': 'Dieser Wert ist keine gültige CSS-Farbe.'
'This value is not a valid CIDR notation.': 'Dieser Wert entspricht nicht der CIDR-Notation.'
'The value of the netmask should be between {{ min }} and {{ max }}.': 'Der Wert der Subnetzmaske sollte zwischen {{ min }} und {{ max }} liegen.'
'The filename is too long':
' It should have {{ filename_max_length }} character or less':
'|The filename is too long':
' It should have {{ filename_max_length }} characters or less.': 'Der Dateiname ist zu lang. Er sollte nicht länger als {{ filename_max_length }} Zeichen sein.|Der Dateiname ist zu lang. Er sollte nicht länger als {{ filename_max_length }} Zeichen sein.'
'The password strength is too low':
' Please use a stronger password.': 'Das Passwort ist zu schwach.'
'This value contains characters that are not allowed by the current restriction-level.': 'Der Wert enthält Zeichen, die auf der aktuellen Einschränkungsstufe nicht erlaubt sind.'
'Using invisible characters is not allowed.': 'Unsichtbare Zeichen sind nicht erlaubt.'
'Mixing numbers from different scripts is not allowed.': 'Das Mischen von Zahlen aus verschiedenen Skripten ist nicht erlaubt.'
'Using hidden overlay characters is not allowed.': 'Verstecke Overlay-Zeichen sind nicht erlaubt.'
'The extension of the file is invalid ({{ extension }})':
' Allowed extensions are {{ extensions }}.': 'Die Dateiendung ist ungültig ({{ extension }}). Gültige Dateiendungen sind {{ extensions }}.'
'The detected character encoding is invalid ({{ detected }})':
' Allowed encodings are {{ encodings }}.': 'Der erkannte Zeichensatz ist nicht gültig ({{ detected }}). Gültige Zeichensätze sind {{ encodings }}.'
'This value is not a valid MAC address.': 'Dieser Wert ist keine gültige MAC-Adresse.'
'This URL is missing a top-level domain.': 'Dieser URL fehlt eine Top-Level-Domain.'
'This value does not represent a valid week in the ISO 8601 format.': 'Dieser Wert ist keine Wochenangabe im ISO 8601-Format.'
'This value is not a valid week.': 'Dieser Wert ist keine gültige Woche.'
'This value should not be before week "{{ min }}".': 'Dieser Wert darf nicht vor der Woche "{{ min }}" sein.'
'This value should not be after week "{{ max }}".': 'Dieser Wert darf nicht nach der Woche "{{ max }}" sein.'
'This form should not contain extra fields.': 'Dieses Formular sollte keine zusätzlichen Felder enthalten.'
'The uploaded file was too large':
' Please try to upload a smaller file.': 'Die hochgeladene Datei ist zu groß. Versuchen Sie bitte eine kleinere Datei hochzuladen.'
'The CSRF token is invalid':
' Please try to resubmit the form.': 'Der CSRF-Token ist ungültig. Versuchen Sie bitte, das Formular erneut zu senden.'
'This value is not a valid HTML5 color.': 'Dieser Wert ist keine gültige HTML5 Farbe.'
'Please enter a valid birthdate.': 'Bitte geben Sie ein gültiges Geburtsdatum ein.'
'The selected choice is invalid.': 'Die Auswahl ist ungültig.'
'The collection is invalid.': 'Diese Gruppe von Feldern ist ungültig.'
'Please select a valid color.': 'Bitte geben Sie eine gültige Farbe ein.'
'Please select a valid country.': 'Bitte wählen Sie ein gültiges Land aus.'
'Please select a valid currency.': 'Bitte wählen Sie eine gültige Währung aus.'
'Please choose a valid date interval.': 'Bitte wählen Sie ein gültiges Datumsintervall.'
'Please enter a valid date and time.': 'Bitte geben Sie ein gültiges Datum samt Uhrzeit ein.'
'Please enter a valid date.': 'Bitte geben Sie ein gültiges Datum ein.'
'Please select a valid file.': 'Bitte wählen Sie eine gültige Datei.'
'The hidden field is invalid.': 'Das versteckte Feld ist ungültig.'
'Please enter an integer.': 'Bitte geben Sie eine ganze Zahl ein.'
'Please select a valid language.': 'Bitte wählen Sie eine gültige Sprache.'
'Please select a valid locale.': 'Bitte wählen Sie eine gültige Locale-Einstellung aus.'
'Please enter a valid money amount.': 'Bitte geben Sie einen gültigen Geldbetrag ein.'
'Please enter a number.': 'Bitte geben Sie eine gültige Zahl ein.'
'The password is invalid.': 'Das Kennwort ist ungültig.'
'Please enter a percentage value.': 'Bitte geben Sie einen gültigen Prozentwert ein.'
'The values do not match.': 'Die Werte stimmen nicht überein.'
'Please enter a valid time.': 'Bitte geben Sie eine gültige Uhrzeit ein.'
'Please select a valid timezone.': 'Bitte wählen Sie eine gültige Zeitzone.'
'Please enter a valid URL.': 'Bitte geben Sie eine gültige URL ein.'
'Please enter a valid search term.': 'Bitte geben Sie einen gültigen Suchbegriff ein.'
'Please provide a valid phone number.': 'Bitte geben Sie eine gültige Telefonnummer ein.'
'The checkbox has an invalid value.': 'Das Kontrollkästchen hat einen ungültigen Wert.'
'Please enter a valid email address.': 'Bitte geben Sie eine gültige E-Mail-Adresse ein.'
'Please select a valid option.': 'Bitte wählen Sie eine gültige Option.'
'Please select a valid range.': 'Bitte wählen Sie einen gültigen Bereich.'
'Please enter a valid week.': 'Bitte geben Sie eine gültige Woche ein.'