2024.11.26 09:59:03

This commit is contained in:
Christian Moser 2024-11-26 09:59:03 +01:00
parent 9832a9636a
commit 4b2918a931
24 changed files with 425 additions and 158 deletions

View File

@ -12,7 +12,7 @@
font-family: "JetBrains";
src:
local("JetBrainsMono-MediumItalic.ttf"),
url(".fonts/JetBrainsMono/JetBrainsMono-Italic.woff2") format("woff2");
url("./fonts/JetBrainsMono/JetBrainsMono-Italic.woff2") format("woff2");
font-style: italic;
font-weight: normal;
}

View File

@ -55,7 +55,9 @@
},
"autoload": {
"psr-4": {
"MyDevel\\": "src/MyDevel/",
"App\\": "src/"
}
},
"autoload-dev": {

View File

@ -13,12 +13,35 @@ services:
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
MyDevel\:
resource: '../src/MyDevel/'
#exclude:
#- '../src/MyDevel/Webroot/Entity/'
#MyDevel\Webroot\Controller\WebrootSetupController:
# abstract: true
# class: WebrootSetupController
# autowire: true
# arguments: ['%kernel.project_dir%']
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
- '../src/MyDevel/'
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241126043446 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE mydevel_webroot_file');
$this->addSql('DROP TABLE mydevel_webroot_file_permission');
$this->addSql('DROP TABLE mydevel_webroot_role');
$this->addSql('DROP TABLE mydevel_webroot_user');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE mydevel_webroot_file (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, owner_id INTEGER NOT NULL, url CLOB NOT NULL COLLATE "BINARY", abspath CLOB NOT NULL COLLATE "BINARY", CONSTRAINT FK_A7B135127E3C61F9 FOREIGN KEY (owner_id) REFERENCES mydevel_webroot_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_A7B135127E3C61F9 ON mydevel_webroot_file (owner_id)');
$this->addSql('CREATE TABLE mydevel_webroot_file_permission (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, role_id INTEGER NOT NULL, webroot_file_id INTEGER NOT NULL, is_readable BOOLEAN DEFAULT 1 NOT NULL, is_writeable BOOLEAN DEFAULT 0 NOT NULL, is_deleteable BOOLEAN DEFAULT 0 NOT NULL, CONSTRAINT FK_4D56CEFD60322AC FOREIGN KEY (role_id) REFERENCES mydevel_webroot_role (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_4D56CEF2800AFC9 FOREIGN KEY (webroot_file_id) REFERENCES mydevel_webroot_file (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('CREATE INDEX IDX_4D56CEF2800AFC9 ON mydevel_webroot_file_permission (webroot_file_id)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_4D56CEFD60322AC ON mydevel_webroot_file_permission (role_id)');
$this->addSql('CREATE TABLE mydevel_webroot_role (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, role VARCHAR(255) NOT NULL COLLATE "BINARY", name VARCHAR(255) NOT NULL COLLATE "BINARY", description VARCHAR(1023) DEFAULT NULL COLLATE "BINARY")');
$this->addSql('CREATE TABLE mydevel_webroot_user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, username VARCHAR(180) NOT NULL COLLATE "BINARY", roles CLOB NOT NULL COLLATE "BINARY" --(DC2Type:json)
, password VARCHAR(255) NOT NULL COLLATE "BINARY", email VARCHAR(255) NOT NULL COLLATE "BINARY")');
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_USERNAME ON mydevel_webroot_user (username)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_A6BDD54BE7927C74 ON mydevel_webroot_user (email)');
}
}

View File

@ -6,17 +6,18 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\HttpKernel\KernelInterface;
//use MyDevel\Webroot\Controller\WebrootMainController;
class MainController extends AbstractController
class MainController extends AbstractController //WebrootMainController
{
#[Route('/', name: 'app_main')]
public function index(KernelInterface $kernel): Response
{
//$project_root = $kernel->getProjectDir();
$project_root = $kernel->getProjectDir();
//if (!file_exists(join(DIRECTORY_SEPARATOR,[$project_root,'.env.local']))) {
//return $this->redirect('/setup');
//}
if (!file_exists(join(DIRECTORY_SEPARATOR,[$project_root,'.env.local']))) {
return $this->redirect('/setup');
}
return $this->render('main/index.html.twig', [
'controller_name' => $kernel->getProjectDir(),

View File

@ -6,13 +6,26 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class SetupController extends AbstractController
use MyDevel\Webroot\Controller\WebrootSetupController;
class SetupController extends WebrootSetupController
{
#[Route('/setup', name: 'app_setup')]
#[Route('/setup', name: 'webroot.setup')]
public function index(): Response
{
return $this->render('setup/index.html.twig', [
'controller_name' => 'SetupController',
return $this->render('setup/controller.html.twig', [
'controller_name' => "WebrootController",
'function' => __FUNCTION__,
]);
}
#[Route('/setup/login',name:'webroot.setup.login')]
public function login()
{
return $this->render('setup/controller.html.twig', [
'controller_name' => "WebrootController",
'function' => __FUNCTION__,
]);
}
}

View File

@ -0,0 +1,16 @@
<?php
function i18n_initialize(string $packge,string $localedir,?string $locale) {
if (function_exists("gettext")) {
if (!$locale) {
$locale="";
}
setlocale(LC_ALL, $locale);
bindtextdomain($package, $localedir);
if (function_exists("bind_textdomain_codeset")) {
bind_textdomain_codeset($package,"UTF-8");
}
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace MyDevel\Utility\i18n;
function i18n_initialize(string $packge, string $localedir, ?string $locale)
{
if (function_exists("gettext")) {
if (!$locale) {
$locale=getenv(LANG);
}
setlocale(LC_ALL, $locale);
bindtextdomain($package,$loclaedir);
if (function_exists("bind_textdomain_codeset")) {
bind_textdomain_codeset(package, "UTF-8");
}
texdomain(package);
}
}
if (function_exists("gettext")) {
function _(string $msgid): string
{
return gettext(msgid);
}
} else {
function _(string $msgid): string
{
return msgid;
}
}
function N_(string $msgid): string
{
return msgid;
}
if (function_exists("ngettext")) {
function X_(string $singular,string $plural,int $count): string
{
return ngettext($singular, $plural, $count);
}
} else {
function X_(string $singular,string $plural,int $count): string
{
if ($count == 1) {
return $singular;
}
return $plural;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace MyDevel\Webroot\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
abstract class WebrootController extends AbstractController
{
}

View File

@ -1,6 +1,6 @@
<?php
namespace App\Entity;
namespace MyDevel\Webroot\Entity;
use App\Repository\WebrootFileRepository;
use Doctrine\Common\Collections\ArrayCollection;

View File

@ -1,6 +1,6 @@
<?php
namespace App\Entity;
namespace MyDevel\Webroot\Entity;
use App\Repository\WebrootPermissionRepository;
use Doctrine\ORM\Mapping as ORM;

View File

@ -1,6 +1,6 @@
<?php
namespace App\Entity;
namespace MyDevel\Webroot\Entity;
use App\Repository\WebrootRoleRepository;
use Doctrine\ORM\Mapping as ORM;

View File

@ -1,6 +1,6 @@
<?php
namespace App\Entity;
namespace MyDevel\Webroot\Entity;
use App\Repository\WebrootUserRepository;
use Doctrine\ORM\Mapping as ORM;
@ -34,6 +34,9 @@ class WebrootUser implements UserInterface, PasswordAuthenticatedUserInterface
#[ORM\Column(length: 255, unique:true)]
private ?string $email = null;
#[ORM\Column(nullable:false,options:['default'=>false])]
private ?bool $is_admin = null;
public function getId(): ?int
{
@ -121,4 +124,16 @@ class WebrootUser implements UserInterface, PasswordAuthenticatedUserInterface
return $this;
}
public function isAdmin(): ?bool
{
return $this->is_admin;
}
public function setAdmin(bool $admin): static
{
$this->is_admin = $admin;
return $this;
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace App\Repository;
namespace MyDevel\Webroot\Repository;
use App\Entity\WebrootPermission;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;

View File

@ -1,6 +1,6 @@
<?php
namespace App\Repository;
namespace MyDevel\Webroot\Repository;
use App\Entity\WebrootFile;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;

View File

@ -1,6 +1,6 @@
<?php
namespace App\Repository;
namespace MyDevel\Webroot\Repository;
use App\Entity\WebrootRole;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;

View File

@ -1,6 +1,6 @@
<?php
namespace App\Repository;
namespace MyDevel\Webroot\Repository;
use App\Entity\WebrootUser;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
@ -33,21 +33,29 @@ class WebrootUserRepository extends ServiceEntityRepository implements PasswordU
$this->getEntityManager()->flush();
}
// /**
// * @return WebrootUser[] Returns an array of WebrootUser objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('w')
// ->andWhere('w.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('w.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
public function getAdministrators(): array
{
return $this->createQueryBuilder('w')
->andWhere('w.is_admin = :val')
->setParameter('val', true)
->orderBy('w.name','ASC')
->getQuery()
->getResult();
}
public function hasAdministrator(): array
{
return (count(
$this->createQueryBuilder('w')
->andWhere('w.is_admin = :val')
->setParameter('val', true)
->setMaxResults(1)
->getQuery()
->getResult()
) > 0);
}
// public function findOneBySomeField($value): ?WebrootUser
// {
// return $this->createQueryBuilder('w')

View File

@ -1,18 +0,0 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class WebrootMainController extends AbstractController
{
#[Route('/webroot/main', name: 'app_webroot_main')]
public function index(): Response
{
return $this->render('webroot_main/index.html.twig', [
'controller_name' => 'WebrootMainController',
]);
}
}

View File

@ -1,18 +1,205 @@
<?php
namespace App\Controller;
namespace MyDevel\Webroot\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\HttpKernel\KernelInterface;
class WebrootSetupController extends AbstractController
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use MyDevel\Webroot\Entity\WebrootUser;
abstract class WebrootSetupController extends AbstractController
{
#[Route('/webroot/setup', name: 'app_webroot_setup')]
public function index(): Response
/**
*
* @var string|null Project root directory.
*/
private ?KernelInterface $kernel = null;
private ?string $project_dir = null;
public function __construct(KernelInterface $kernel) {
$this->kernel = $kernel;
$this->project_dir = $kernel->getProjectDir();
}
/**
* @brief Get the root directory of the Project.
*
* This method gets the root directory of the project as an absolute path
* as returned by the Kernel->getProjectDir();
*
* @return string|null The root directory of the prject or null if the
* Kernel cannot determine it.
*/
public function getProjectDir(): ?string
{
return $this->render('webroot_setup/index.html.twig', [
'controller_name' => 'WebrootSetupController',
return $this->project_dir;
}
/**
* @brief Check if there is an Administrator account existing.
* @return bool `true` if an Administrator is existent in the database.
*/
protected function hasAdministrator(): bool
{
$repos = $this->getRpository(WebrootUser::class);
return $repos->hasAdministrator();
}
/**
* @brief Check if there is a *.setupkey*-file in the project root.
*
* @note This file will be deleted after the initial setup!
*
* @return bool `true` if there is a *.setupkey* file int the project
* root directory.
*/
protected function hasSetupKey(): bool
{
return file_exists(join(DIRECTORY_SEPARATOR,[$this->project_dir,".setupkey"]));
}
/**
* @Brief Get the actual setup key from file.
* @return string|null Returns `null` when there is no `.setupkey` file
* located in prject root else the first line of the file.
*/
protected function getSetupKey(): ?string
{
if (!$this->hasSetupKey()) {
return null;
}
$file = fopen($this->project_dir,".setupkey");
$str = fread($file, 4096);
fclose($file);
$pos = strpos($str,"\r\n",0);
if (!$pos) {
$pos = strpos($str,"\n",0);
}
if ($pos) {
$key=substr(string,0,pos);
} else {
$key=$str;
}
return key;
}
/**
* @brief Check if user is logged in for setup.
* @return bool Returns `true` if user is logged in and is allowed to view
* the setup pages.
*/
public function isLoggedIn(): bool
{
$user = $this->getUser();
if ($user && $user->isAdmin()) {
return true;
}
$has_setup_key = $this->hasSetupKey();
$has_administrator = $this->hasAdministrator();
if (!$has_administrator && !$has_setup_key) {
return true;
}
if ($has_setup_key && isset($_COOKIE["mydevel_webroot_sk"])) {
return (filter_input(INPUT_COOKIE, "mydevel_webroot_sk") === $this->getSetupKey());
}
return false;
}
/**
* @brief Check if we are already set up.
*
* This method checks if there is an .env.local file installed in the
* system and returns ture if the .env.local file is found.
*
* @return bool `true` if we are already installed.
*/
public function isInstalled(): bool
{
return (file_exists(join(DIRECTORY_SEPARATOR,[$this->project_dir,".env"]))
&& file_exists(join(DIRECTORY_SEPARATOR,[$this->project_dir,".env.local"])));
}
/**
* @brief Check if the key marches the one from file.
*
* @param string $key The key to check.
*
* @return bool|null Returns `null` if there is no *.setupkey* file present,
* `true` on match and false if the key does not match.
*/
protected function checkSetupKey(string $key): ?bool
{
if (!$this->hasSetupKey()) {
return null;
}
$file_key = $this->getSetupKey();
if ($key === $file_key) {
$cookie_domain = getenv("WEBROOT_HOST");
if (!$cookie_domain || !strlen("WEBROOT_HOST")) {
$request = $this->getRequest();
$cookie_domain = $request->getHost();
setcookie("mydevel_webroot_sk",$file_key,0,"/",$cookie_domain,1,0);
return true;
}
}
return false;
}
/**
*
* @return string
*/
protected function runMakeMigration(): string
{
$application = new Application($this->kernel);
$application->setAutoExit(false);
$input = new ArrayInput([
"command" => "make:migration",
"--formatted" => true,
"--no-interaction" => true,
]);
$output = new BufferedOutput();
$application->run($input,$output);
$content=$output->fetch();
return $content;
}
/**
*
* @return string
*/
protected function runMigrate(): string
{
$application = new Application($this->kernel);
$application->setAutoExit(false);
$input = new ArrayInput([
"command" => "doctrine:migrations:migrate",
"--no-interaction" => true,
]);
$output = new BufferedOutput();
$application->run($input,$output);
$content = $output->fetch();
return $content;
}
}

View File

@ -0,0 +1,16 @@
{% extends 'base.html.twig' %}
{% block title %}Hello SetupController!{% endblock %}
{% block body %}
<div class="example-wrapper">
<h1>Hello {{ controller_name }}::{{ function }}()!</h1>
This friendly message is coming from:
<ul>
<li>Your controller at <code>src/MyDevel/Controller/SetupController.php</code></li>
<li>Your template at <code>templates/setup/controller.html.twig</code></li>
</ul>
</div>
{% endblock %}

View File

@ -1,20 +0,0 @@
{% extends 'base.html.twig' %}
{% block title %}Hello SetupController!{% endblock %}
{% block body %}
<style>
.example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; }
.example-wrapper code { background: #F5F5F5; padding: 2px 6px; }
</style>
<div class="example-wrapper">
<h1>Hello {{ controller_name }}! ✅</h1>
This friendly message is coming from:
<ul>
<li>Your controller at <code>C:/msys64/home/c9mos/www/webroot/src/Controller/SetupController.php</code></li>
<li>Your template at <code>C:/msys64/home/c9mos/www/webroot/templates/setup/index.html.twig</code></li>
</ul>
</div>
{% endblock %}

View File

@ -1,20 +0,0 @@
{% extends 'base.html.twig' %}
{% block title %}Hello WebrootMainController!{% endblock %}
{% block body %}
<style>
.example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; }
.example-wrapper code { background: #F5F5F5; padding: 2px 6px; }
</style>
<div class="example-wrapper">
<h1>Hello {{ controller_name }}! ✅</h1>
This friendly message is coming from:
<ul>
<li>Your controller at <code>C:/msys64/home/c9mos/www/webroot/src/Controller/WebrootMainController.php</code></li>
<li>Your template at <code>C:/msys64/home/c9mos/www/webroot/templates/webroot_main/index.html.twig</code></li>
</ul>
</div>
{% endblock %}

View File

@ -1,20 +0,0 @@
{% extends 'base.html.twig' %}
{% block title %}Hello WebrootSetupController!{% endblock %}
{% block body %}
<style>
.example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; }
.example-wrapper code { background: #F5F5F5; padding: 2px 6px; }
</style>
<div class="example-wrapper">
<h1>Hello {{ controller_name }}! ✅</h1>
This friendly message is coming from:
<ul>
<li>Your controller at <code>C:/msys64/home/c9mos/www/webroot/src/Controller/WebrootSetupController.php</code></li>
<li>Your template at <code>C:/msys64/home/c9mos/www/webroot/templates/webroot_setup/index.html.twig</code></li>
</ul>
</div>
{% endblock %}

View File

@ -1,41 +0,0 @@
{% extends 'base.html.twig' %}
{% block title %}Log in!{% endblock %}
{% block body %}
<form method="post">
{% if error %}
<div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
{% if app.user %}
<div class="mb-3">
You are logged in as {{ app.user.userIdentifier }}, <a href="{{ path('app_logout') }}">Logout</a>
</div>
{% endif %}
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
<label for="username">Username</label>
<input type="text" value="{{ last_username }}" name="_username" id="username" class="form-control" autocomplete="username" required autofocus>
<label for="password">Password</label>
<input type="password" name="_password" id="password" class="form-control" autocomplete="current-password" required>
<input type="hidden" name="_csrf_token"
value="{{ csrf_token('authenticate') }}"
>
{#
Uncomment this section and add a remember_me option below your firewall to activate remember me functionality.
See https://symfony.com/doc/current/security/remember_me.html
<div class="checkbox mb-3">
<input type="checkbox" name="_remember_me" id="_remember_me">
<label for="_remember_me">Remember me</label>
</div>
#}
<button class="btn btn-lg btn-primary" type="submit">
Sign in
</button>
</form>
{% endblock %}