diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..84ff2a7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +* text=auto + +*.js text +*.php text +*.html text +*.htm text + +*.png binary +*.svg binary +*.ico binary + diff --git a/assets/styles/app.css b/assets/styles/app.css index ad8375f..b8c6cf9 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -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; +} \ No newline at end of file diff --git a/assets/webroot/mydevel_webroot_setup_initial.js b/assets/webroot/mydevel_webroot_setup_initial.js new file mode 100644 index 0000000..b4a5a57 --- /dev/null +++ b/assets/webroot/mydevel_webroot_setup_initial.js @@ -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!"); +} diff --git a/assets/webroot/webroot.setup.run.js b/assets/webroot/webroot.setup.run.js new file mode 100644 index 0000000..e69de29 diff --git a/composer.json b/composer.json index c3ee750..5f29e91 100644 --- a/composer.json +++ b/composer.json @@ -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.*", diff --git a/composer.lock b/composer.lock index 453eb2c..432b419 100644 --- a/composer.lock +++ b/composer.lock @@ -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", diff --git a/config/bundles.php b/config/bundles.php index 4e3a560..1737743 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -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], ]; diff --git a/config/packages/translation.yaml b/config/packages/translation.yaml index a1d4d3e..aab528a 100644 --- a/config/packages/translation.yaml +++ b/config/packages/translation.yaml @@ -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] - diff --git a/config/packages/twig_component.yaml b/config/packages/twig_component.yaml new file mode 100644 index 0000000..fd17ac6 --- /dev/null +++ b/config/packages/twig_component.yaml @@ -0,0 +1,5 @@ +twig_component: + anonymous_template_directory: 'components/' + defaults: + # Namespace & directory for components + App\Twig\Components\: 'components/' diff --git a/config/packages/uid.yaml b/config/packages/uid.yaml new file mode 100644 index 0000000..0152094 --- /dev/null +++ b/config/packages/uid.yaml @@ -0,0 +1,4 @@ +framework: + uid: + default_uuid_version: 7 + time_based_uuid_version: 7 diff --git a/config/services.yaml b/config/services.yaml index c675c86..32db85d 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -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 diff --git a/src/Controller/SetupController.php b/src/Controller/SetupController.php index 8764eb0..d2c4bcf 100644 --- a/src/Controller/SetupController.php +++ b/src/Controller/SetupController.php @@ -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 = "" + . "" + . "
" + . "" + . "" + . "" + . "" . $result ."" + . "" + . ""; + 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 = "" + . "" + . "" + . "" + . "" + . "" + . "" . $result ."" + . "" + . ""; + + 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 = "" + . "" + . "" + . "" + . "" + . "" + . "" . $result ."" + . "" + . ""; + + 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__, + ]); + } } diff --git a/src/Controller/WebrootSetupController.php b/src/Controller/WebrootSetupController.php index 264e697..c22c012 100644 --- a/src/Controller/WebrootSetupController.php +++ b/src/Controller/WebrootSetupController.php @@ -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"); + } + } } diff --git a/symfony.lock b/symfony.lock index c53dbb6..76897ca 100644 --- a/symfony.lock +++ b/symfony.lock @@ -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": { diff --git a/templates/setup/initial-setup.html.twig b/templates/setup/initial-setup.html.twig index ed5d5af..b44aa9f 100644 --- a/templates/setup/initial-setup.html.twig +++ b/templates/setup/initial-setup.html.twig @@ -1,7 +1,11 @@ {% extends 'base.html.twig' %} +{% block javascripts %} + {% block importmap %}{{ importmap('app') }}{% endblock %} + +{% endblock %} {% block body %} -