finished user prfile and passwords
This commit is contained in:
parent
a488e489da
commit
560e96cf18
28
.eslintrc
28
.eslintrc
@ -2,9 +2,7 @@
|
|||||||
"extends": [
|
"extends": [
|
||||||
// add more generic rulesets here, such as:
|
// add more generic rulesets here, such as:
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"airbnb-base",
|
"airbnb-base"
|
||||||
"plugin:vue/recommended",
|
|
||||||
"plugin:vue/vue3-recommended"
|
|
||||||
],
|
],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"parser": "espree",
|
"parser": "espree",
|
||||||
@ -13,35 +11,13 @@
|
|||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"es6": true,
|
"es6": true,
|
||||||
"browser": true,
|
"browser": true
|
||||||
"vue/setup-compiler-macros": true
|
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"semi": [
|
"semi": [
|
||||||
"error",
|
"error",
|
||||||
"never"
|
"never"
|
||||||
],
|
],
|
||||||
"vue/no-v-html": "off",
|
|
||||||
"vue/html-indent": [
|
|
||||||
"error",
|
|
||||||
"tab"
|
|
||||||
],
|
|
||||||
"vue/html-closing-bracket-spacing": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"startTag": "never",
|
|
||||||
"endTag": "never",
|
|
||||||
"selfClosingTag": "never"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"vue/component-api-style": [
|
|
||||||
"error",
|
|
||||||
[
|
|
||||||
"script-setup",
|
|
||||||
"composition"
|
|
||||||
]
|
|
||||||
// "script-setup", "composition", "composition-vue2", or "options"
|
|
||||||
],
|
|
||||||
"indent": [
|
"indent": [
|
||||||
"error",
|
"error",
|
||||||
"tab"
|
"tab"
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -21,3 +21,4 @@ yarn-error.log
|
|||||||
/public/media/cache/
|
/public/media/cache/
|
||||||
###< liip/imagine-bundle ###
|
###< liip/imagine-bundle ###
|
||||||
/tools/php-cs-fixer/vendor/
|
/tools/php-cs-fixer/vendor/
|
||||||
|
/public/uploads/
|
||||||
|
4
TODO
4
TODO
@ -1,7 +1,7 @@
|
|||||||
accent color #d43934
|
accent color #d43934
|
||||||
|
|
||||||
remove vue
|
merge RequestController/SecurityController
|
||||||
use bootsrap
|
don't use html in easyadmin / quotes
|
||||||
use turbo
|
use turbo
|
||||||
make unit tests
|
make unit tests
|
||||||
|
|
||||||
|
@ -9,9 +9,9 @@ import 'bootswatch/dist/slate/bootstrap.min.css'
|
|||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
console.log('ready')
|
console.log('ready')
|
||||||
$('#toggleSidebar').on('click', function () {
|
$('#toggleSidebar').on('click', function () {
|
||||||
$('#toggleIcon').toggleClass('fa fa-lg fa-fw fa-caret-square-o-left')
|
let toggleIcon = $('#toggleIcon')
|
||||||
$('#toggleIcon').toggleClass('fa fa-lg fa-fw fa-caret-square-o-right')
|
toggleIcon.toggleClass('fa fa-lg fa-fw fa-caret-square-o-left')
|
||||||
|
toggleIcon.toggleClass('fa fa-lg fa-fw fa-caret-square-o-right')
|
||||||
$('#sidebar').toggleClass('active')
|
$('#sidebar').toggleClass('active')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ $jet-black: #0e0e10;
|
|||||||
#sidebar {
|
#sidebar {
|
||||||
min-width: 180px;
|
min-width: 180px;
|
||||||
max-width: 180px;
|
max-width: 180px;
|
||||||
|
height: 100%;
|
||||||
display: inline;
|
display: inline;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -54,3 +55,44 @@ $jet-black: #0e0e10;
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#main_content {
|
||||||
|
overflow-y: auto;
|
||||||
|
height: calc(100vh - 160px)
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 330px;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 50px auto auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin .checkbox {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin .form-control {
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: auto;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin .form-control:focus {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin input {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin input[id="registration_form_username"] {
|
||||||
|
margin-bottom: -1px;
|
||||||
|
border-radius: 0.375rem 0.375rem 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin input[id="registration_form_password_second"] {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-radius: 0 0 0.375rem 0.375rem;
|
||||||
|
}
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
"php": ">=8.1.0",
|
"php": ">=8.1.0",
|
||||||
"ext-ctype": "*",
|
"ext-ctype": "*",
|
||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
"api-platform/core": "^2.6",
|
|
||||||
"doctrine/annotations": "^1.0",
|
"doctrine/annotations": "^1.0",
|
||||||
"doctrine/doctrine-bundle": "^2.7",
|
"doctrine/doctrine-bundle": "^2.7",
|
||||||
"doctrine/doctrine-migrations-bundle": "^3.2",
|
"doctrine/doctrine-migrations-bundle": "^3.2",
|
||||||
@ -42,6 +41,7 @@
|
|||||||
"symfony/proxy-manager-bridge": "6.1.*",
|
"symfony/proxy-manager-bridge": "6.1.*",
|
||||||
"symfony/runtime": "6.1.*",
|
"symfony/runtime": "6.1.*",
|
||||||
"symfony/security-bundle": "6.1.*",
|
"symfony/security-bundle": "6.1.*",
|
||||||
|
"symfony/security-csrf": "6.1.*",
|
||||||
"symfony/serializer": "6.1.*",
|
"symfony/serializer": "6.1.*",
|
||||||
"symfony/string": "6.1.*",
|
"symfony/string": "6.1.*",
|
||||||
"symfony/translation": "6.1.*",
|
"symfony/translation": "6.1.*",
|
||||||
@ -51,8 +51,11 @@
|
|||||||
"symfony/webapp-meta": "^1.0",
|
"symfony/webapp-meta": "^1.0",
|
||||||
"symfony/webpack-encore-bundle": "^1.14",
|
"symfony/webpack-encore-bundle": "^1.14",
|
||||||
"symfony/yaml": "6.1.*",
|
"symfony/yaml": "6.1.*",
|
||||||
|
"symfonycasts/reset-password-bundle": "^1.16",
|
||||||
|
"symfonycasts/verify-email-bundle": "^1.12",
|
||||||
|
"twig/extra-bundle": "^3.4",
|
||||||
"twig/markdown-extra": "^3.4",
|
"twig/markdown-extra": "^3.4",
|
||||||
"twig/twig": "3.3.10"
|
"twig/twig": "3.4.*"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
|
662
composer.lock
generated
662
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -15,7 +15,7 @@ return [
|
|||||||
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
|
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
|
||||||
EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle::class => ['all' => true],
|
EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle::class => ['all' => true],
|
||||||
Knp\Bundle\TimeBundle\KnpTimeBundle::class => ['all' => true],
|
Knp\Bundle\TimeBundle\KnpTimeBundle::class => ['all' => true],
|
||||||
FOS\CKEditorBundle\FOSCKEditorBundle::class => ['all' => true],
|
|
||||||
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
|
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
|
||||||
ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
|
SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle::class => ['all' => true],
|
||||||
|
SymfonyCasts\Bundle\ResetPassword\SymfonyCastsResetPasswordBundle::class => ['all' => true],
|
||||||
];
|
];
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
api_platform:
|
|
||||||
mapping:
|
|
||||||
paths: ['%kernel.project_dir%/src/Entity']
|
|
||||||
patch_formats:
|
|
||||||
json: ['application/merge-patch+json']
|
|
||||||
swagger:
|
|
||||||
versions: [3]
|
|
||||||
show_webby: false
|
|
@ -1,5 +0,0 @@
|
|||||||
# Read the documentation: https://symfony.com/doc/current/bundles/FOSCKEditorBundle/index.html
|
|
||||||
|
|
||||||
twig:
|
|
||||||
form_themes:
|
|
||||||
- '@FOSCKEditor/Form/ckeditor_widget.html.twig'
|
|
@ -1,7 +1,7 @@
|
|||||||
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
||||||
framework:
|
framework:
|
||||||
secret: '%env(APP_SECRET)%'
|
secret: '%env(APP_SECRET)%'
|
||||||
#csrf_protection: true
|
csrf_protection: true
|
||||||
http_method_override: false
|
http_method_override: false
|
||||||
|
|
||||||
# Enables session support. Note that the session will ONLY be started if you read or write from it.
|
# Enables session support. Note that the session will ONLY be started if you read or write from it.
|
||||||
|
@ -16,7 +16,7 @@ framework:
|
|||||||
# sync: 'sync://'
|
# sync: 'sync://'
|
||||||
|
|
||||||
routing:
|
routing:
|
||||||
Symfony\Component\Mailer\Messenger\SendEmailMessage: async
|
#Symfony\Component\Mailer\Messenger\SendEmailMessage: async
|
||||||
Symfony\Component\Notifier\Message\ChatMessage: async
|
Symfony\Component\Notifier\Message\ChatMessage: async
|
||||||
Symfony\Component\Notifier\Message\SmsMessage: async
|
Symfony\Component\Notifier\Message\SmsMessage: async
|
||||||
|
|
||||||
|
4
config/packages/reset_password.yaml
Normal file
4
config/packages/reset_password.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
symfonycasts_reset_password:
|
||||||
|
request_password_repository: App\Repository\ResetPasswordRequestRepository
|
||||||
|
lifetime: 3600
|
||||||
|
throttle_limit: 1
|
@ -22,13 +22,11 @@ security:
|
|||||||
main:
|
main:
|
||||||
lazy: true
|
lazy: true
|
||||||
provider: app_user_provider
|
provider: app_user_provider
|
||||||
# custom_authenticator: App\Security\LoginFormAuthenticator
|
custom_authenticator: App\Security\LoginFormAuthenticator
|
||||||
|
|
||||||
json_login:
|
|
||||||
check_path: app_login
|
|
||||||
username_path: username
|
|
||||||
password_path: password
|
|
||||||
|
|
||||||
|
##form_login:
|
||||||
|
# login_path: app_login
|
||||||
|
# check_path: app_login
|
||||||
logout:
|
logout:
|
||||||
path: app_logout
|
path: app_logout
|
||||||
switch_user: true
|
switch_user: true
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
twig:
|
twig:
|
||||||
default_path: '%kernel.project_dir%/templates'
|
# default_path: '%kernel.project_dir%/templates'
|
||||||
|
paths:
|
||||||
|
'%kernel.project_dir%/templates/themes/default': default
|
||||||
|
'%kernel.project_dir%/templates/admin': admin
|
||||||
form_themes:
|
form_themes:
|
||||||
- '@FOSCKEditor/Form/ckeditor_widget.html.twig'
|
- bootstrap_5_layout.html.twig
|
||||||
|
|
||||||
when@test:
|
when@test:
|
||||||
twig:
|
twig:
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
api_platform:
|
|
||||||
resource: .
|
|
||||||
type: api_platform
|
|
||||||
prefix: /api
|
|
@ -1,31 +0,0 @@
|
|||||||
<?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 Version20220409135404 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('ALTER TABLE user ADD first_name VARCHAR(255) DEFAULT NULL, ADD last_name VARCHAR(255) DEFAULT NULL');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('ALTER TABLE `user` DROP first_name, DROP last_name');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
<?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 Version20220409150258 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('CREATE TABLE quotes (id INT AUTO_INCREMENT NOT NULL, quote LONGTEXT NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('DROP TABLE quotes');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
<?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 Version20220410123550 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('CREATE TABLE projects (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
|
||||||
$this->addSql('ALTER TABLE user CHANGE first_name first_name VARCHAR(255) DEFAULT NULL');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('DROP TABLE projects');
|
|
||||||
$this->addSql('ALTER TABLE `user` CHANGE first_name first_name VARCHAR(255) DEFAULT \'\'');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
<?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 Version20220410131552 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('CREATE TABLE projects_user (projects_id INT NOT NULL, user_id INT NOT NULL, INDEX IDX_B38D6A811EDE0F55 (projects_id), INDEX IDX_B38D6A81A76ED395 (user_id), PRIMARY KEY(projects_id, user_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
|
||||||
$this->addSql('ALTER TABLE projects_user ADD CONSTRAINT FK_B38D6A811EDE0F55 FOREIGN KEY (projects_id) REFERENCES projects (id) ON DELETE CASCADE');
|
|
||||||
$this->addSql('ALTER TABLE projects_user ADD CONSTRAINT FK_B38D6A81A76ED395 FOREIGN KEY (user_id) REFERENCES `user` (id) ON DELETE CASCADE');
|
|
||||||
$this->addSql('ALTER TABLE projects ADD teaser_image VARCHAR(255) DEFAULT NULL');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('DROP TABLE projects_user');
|
|
||||||
$this->addSql('ALTER TABLE projects DROP teaser_image');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
<?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 Version20220412144008 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('ALTER TABLE user ADD avatar VARCHAR(255) DEFAULT NULL');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('ALTER TABLE `user` DROP avatar');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
<?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 Version20220423085724 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('CREATE TABLE pages (id INT AUTO_INCREMENT NOT NULL, owner_id INT NOT NULL, name VARCHAR(255) NOT NULL, content LONGTEXT NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', modified_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_2074E5757E3C61F9 (owner_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
|
||||||
$this->addSql('ALTER TABLE pages ADD CONSTRAINT FK_2074E5757E3C61F9 FOREIGN KEY (owner_id) REFERENCES `user` (id)');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('DROP TABLE pages');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
<?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 Version20220425082917 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('ALTER TABLE user ADD created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', ADD modified_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\'');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('ALTER TABLE `user` DROP created_at, DROP modified_at');
|
|
||||||
}
|
|
||||||
}
|
|
73
migrations/Version20221028172857.php
Normal file
73
migrations/Version20221028172857.php
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<?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 Version20221028172857 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('CREATE TABLE pages (id INT AUTO_INCREMENT NOT NULL, owner_id INT NOT NULL, name VARCHAR(255) NOT NULL, content LONGTEXT NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', modified_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', slug VARCHAR(255) NOT NULL, INDEX IDX_2074E5757E3C61F9 (owner_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||||
|
$this->addSql('CREATE TABLE projects (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', teaser_image VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||||
|
$this->addSql('CREATE TABLE projects_user (projects_id INT NOT NULL, user_id INT NOT NULL, INDEX IDX_B38D6A811EDE0F55 (projects_id), INDEX IDX_B38D6A81A76ED395 (user_id), PRIMARY KEY(projects_id, user_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||||
|
$this->addSql('CREATE TABLE quotes (id INT AUTO_INCREMENT NOT NULL, quote LONGTEXT NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||||
|
$this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL, available_at DATETIME NOT NULL, delivered_at DATETIME DEFAULT NULL, INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||||
|
$this->addSql('ALTER TABLE pages ADD CONSTRAINT FK_2074E5757E3C61F9 FOREIGN KEY (owner_id) REFERENCES user (id)');
|
||||||
|
$this->addSql('ALTER TABLE projects_user ADD CONSTRAINT FK_B38D6A811EDE0F55 FOREIGN KEY (projects_id) REFERENCES projects (id) ON DELETE CASCADE');
|
||||||
|
$this->addSql('ALTER TABLE projects_user ADD CONSTRAINT FK_B38D6A81A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE');
|
||||||
|
$this->addSql('ALTER TABLE reset_password_request DROP FOREIGN KEY FK_7CE748AA76ED395');
|
||||||
|
$this->addSql('ALTER TABLE blog DROP FOREIGN KEY FK_C0155143DD7B2EBC');
|
||||||
|
$this->addSql('ALTER TABLE blog DROP FOREIGN KEY FK_C0155143F675F31B');
|
||||||
|
$this->addSql('ALTER TABLE comment DROP FOREIGN KEY FK_9474526CDAE07E97');
|
||||||
|
$this->addSql('ALTER TABLE comment DROP FOREIGN KEY FK_9474526CDD7B2EBC');
|
||||||
|
$this->addSql('ALTER TABLE comment DROP FOREIGN KEY FK_9474526CF675F31B');
|
||||||
|
$this->addSql('ALTER TABLE blog_section DROP FOREIGN KEY FK_C185C76CD823E37A');
|
||||||
|
$this->addSql('ALTER TABLE blog_section DROP FOREIGN KEY FK_C185C76CDAE07E97');
|
||||||
|
$this->addSql('DROP TABLE reset_password_request');
|
||||||
|
$this->addSql('DROP TABLE blog');
|
||||||
|
$this->addSql('DROP TABLE comment');
|
||||||
|
$this->addSql('DROP TABLE blog_section');
|
||||||
|
$this->addSql('DROP TABLE section');
|
||||||
|
$this->addSql('ALTER TABLE user ADD first_name VARCHAR(255) DEFAULT NULL, ADD last_name VARCHAR(255) DEFAULT NULL, ADD modified_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', DROP last_login_at, CHANGE created_at created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\'');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TABLE reset_password_request (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, selector VARCHAR(20) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, hashed_token VARCHAR(100) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, requested_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', expires_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_7CE748AA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' ');
|
||||||
|
$this->addSql('CREATE TABLE blog (id INT AUTO_INCREMENT NOT NULL, author_id INT NOT NULL, edited_by_id INT DEFAULT NULL, title VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, teaser LONGTEXT CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, teaser_image VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, content LONGTEXT CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, created_at DATETIME NOT NULL, edited_at DATETIME DEFAULT NULL, edit_reason VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, slug VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, INDEX IDX_C0155143DD7B2EBC (edited_by_id), UNIQUE INDEX UNIQ_C0155143989D9B62 (slug), INDEX IDX_C0155143F675F31B (author_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' ');
|
||||||
|
$this->addSql('CREATE TABLE comment (id INT AUTO_INCREMENT NOT NULL, blog_id INT NOT NULL, author_id INT NOT NULL, edited_by_id INT DEFAULT NULL, title VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, content LONGTEXT CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, created_at DATETIME NOT NULL, edited_at DATETIME DEFAULT NULL, edit_reason VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, INDEX IDX_9474526CDD7B2EBC (edited_by_id), INDEX IDX_9474526CDAE07E97 (blog_id), INDEX IDX_9474526CF675F31B (author_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' ');
|
||||||
|
$this->addSql('CREATE TABLE blog_section (blog_id INT NOT NULL, section_id INT NOT NULL, INDEX IDX_C185C76CDAE07E97 (blog_id), INDEX IDX_C185C76CD823E37A (section_id), PRIMARY KEY(blog_id, section_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' ');
|
||||||
|
$this->addSql('CREATE TABLE section (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, description VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, teaser_image VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' ');
|
||||||
|
$this->addSql('ALTER TABLE reset_password_request ADD CONSTRAINT FK_7CE748AA76ED395 FOREIGN KEY (user_id) REFERENCES user (id)');
|
||||||
|
$this->addSql('ALTER TABLE blog ADD CONSTRAINT FK_C0155143DD7B2EBC FOREIGN KEY (edited_by_id) REFERENCES user (id)');
|
||||||
|
$this->addSql('ALTER TABLE blog ADD CONSTRAINT FK_C0155143F675F31B FOREIGN KEY (author_id) REFERENCES user (id)');
|
||||||
|
$this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526CDAE07E97 FOREIGN KEY (blog_id) REFERENCES blog (id)');
|
||||||
|
$this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526CDD7B2EBC FOREIGN KEY (edited_by_id) REFERENCES user (id)');
|
||||||
|
$this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526CF675F31B FOREIGN KEY (author_id) REFERENCES user (id)');
|
||||||
|
$this->addSql('ALTER TABLE blog_section ADD CONSTRAINT FK_C185C76CD823E37A FOREIGN KEY (section_id) REFERENCES section (id) ON DELETE CASCADE');
|
||||||
|
$this->addSql('ALTER TABLE blog_section ADD CONSTRAINT FK_C185C76CDAE07E97 FOREIGN KEY (blog_id) REFERENCES blog (id) ON DELETE CASCADE');
|
||||||
|
$this->addSql('ALTER TABLE pages DROP FOREIGN KEY FK_2074E5757E3C61F9');
|
||||||
|
$this->addSql('ALTER TABLE projects_user DROP FOREIGN KEY FK_B38D6A811EDE0F55');
|
||||||
|
$this->addSql('ALTER TABLE projects_user DROP FOREIGN KEY FK_B38D6A81A76ED395');
|
||||||
|
$this->addSql('DROP TABLE pages');
|
||||||
|
$this->addSql('DROP TABLE projects');
|
||||||
|
$this->addSql('DROP TABLE projects_user');
|
||||||
|
$this->addSql('DROP TABLE quotes');
|
||||||
|
$this->addSql('DROP TABLE messenger_messages');
|
||||||
|
$this->addSql('ALTER TABLE user ADD last_login_at DATETIME DEFAULT NULL, DROP first_name, DROP last_name, DROP modified_at, CHANGE created_at created_at DATETIME NOT NULL');
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,7 @@ use Doctrine\Migrations\AbstractMigration;
|
|||||||
/**
|
/**
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
*/
|
*/
|
||||||
final class Version20220424100610 extends AbstractMigration
|
final class Version20221030105205 extends AbstractMigration
|
||||||
{
|
{
|
||||||
public function getDescription(): string
|
public function getDescription(): string
|
||||||
{
|
{
|
||||||
@ -20,12 +20,12 @@ final class Version20220424100610 extends AbstractMigration
|
|||||||
public function up(Schema $schema): void
|
public function up(Schema $schema): void
|
||||||
{
|
{
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
$this->addSql('ALTER TABLE pages ADD slug VARCHAR(255) NOT NULL');
|
$this->addSql('ALTER TABLE user ADD agreed_terms_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\'');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
public function down(Schema $schema): void
|
||||||
{
|
{
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
$this->addSql('ALTER TABLE pages DROP slug');
|
$this->addSql('ALTER TABLE user DROP agreed_terms_at');
|
||||||
}
|
}
|
||||||
}
|
}
|
33
migrations/Version20221030161847.php
Normal file
33
migrations/Version20221030161847.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?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 Version20221030161847 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('CREATE TABLE reset_password_request (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, selector VARCHAR(20) NOT NULL, hashed_token VARCHAR(100) NOT NULL, requested_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', expires_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_7CE748AA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||||
|
$this->addSql('ALTER TABLE reset_password_request ADD CONSTRAINT FK_7CE748AA76ED395 FOREIGN KEY (user_id) REFERENCES user (id)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('ALTER TABLE reset_password_request DROP FOREIGN KEY FK_7CE748AA76ED395');
|
||||||
|
$this->addSql('DROP TABLE reset_password_request');
|
||||||
|
}
|
||||||
|
}
|
16
src/Controller/BaseController.php
Normal file
16
src/Controller/BaseController.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use App\Entity\User;
|
||||||
|
|
||||||
|
abstract class BaseController extends AbstractController
|
||||||
|
{
|
||||||
|
protected function getUser(): User
|
||||||
|
{
|
||||||
|
return parent::getUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -10,27 +10,30 @@ use Exception;
|
|||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
use Symfony\Component\Serializer\SerializerInterface;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class FrontendController extends AbstractController
|
class FrontendController extends AbstractController
|
||||||
{
|
{
|
||||||
const TEMPLATE_DIR = 'themes/default/';
|
#[Route(path: '/', name: 'app_main')]
|
||||||
|
public function quote(QuotesRepository $quotesRepository): Response
|
||||||
|
{
|
||||||
|
$user = $this->getUser();
|
||||||
|
$quote = $quotesRepository->findOneRandom();
|
||||||
|
|
||||||
|
return $this->render(view: '@default/base.html.twig', parameters: [
|
||||||
|
'user' => $user,
|
||||||
|
'quote' => json_encode(value: $quote->getQuote())
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
#[Route(path: '/', name: 'app_main')]
|
#[Route(path: '/logout', name: 'app_logout')]
|
||||||
public function quote(SerializerInterface $serializer, QuotesRepository $quotesRepository): Response
|
public function logout(): never
|
||||||
{
|
{
|
||||||
$quote = $quotesRepository->findOneRandom();
|
throw new Exception(message: 'Logout should never be reached.');
|
||||||
|
|
||||||
return $this->render(view: self::TEMPLATE_DIR . 'base.html.twig', parameters: [
|
|
||||||
'template_dir' => self::TEMPLATE_DIR,
|
|
||||||
'user' => $serializer->serialize(data: $this->getUser(), format: 'jsonld'),
|
|
||||||
'quote' => json_encode(value: $quote->getQuote())
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,6 @@ use Symfony\Component\Routing\Annotation\Route;
|
|||||||
|
|
||||||
class PagesController extends AbstractController
|
class PagesController extends AbstractController
|
||||||
{
|
{
|
||||||
const TEMPLATE_DIR = 'themes/default/';
|
|
||||||
|
|
||||||
#[Route(path: '/pages/{name}', name: 'pages_display')]
|
#[Route(path: '/pages/{name}', name: 'pages_display')]
|
||||||
public function display(PagesRepository $pagesRepository, string $name): Response
|
public function display(PagesRepository $pagesRepository, string $name): Response
|
||||||
{
|
{
|
||||||
@ -25,7 +23,7 @@ class PagesController extends AbstractController
|
|||||||
$page->setContent(content: 'The requested page was not found.');
|
$page->setContent(content: 'The requested page was not found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render(view: 'pages/display.html.twig', parameters: [
|
return $this->render(view: '@default/pages/display.html.twig', parameters: [
|
||||||
'page' => $page,
|
'page' => $page,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -33,18 +31,12 @@ class PagesController extends AbstractController
|
|||||||
#[Route(path: '/imprint', name: 'app_imprint')]
|
#[Route(path: '/imprint', name: 'app_imprint')]
|
||||||
public function imprint(): Response
|
public function imprint(): Response
|
||||||
{
|
{
|
||||||
return $this->render(view: self::TEMPLATE_DIR . 'pages/imprint.html.twig', parameters: [
|
return $this->render(view: '@default/pages/imprint.html.twig');
|
||||||
'template_dir' => self::TEMPLATE_DIR,
|
|
||||||
'controller_name' => 'PagesController',
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route(path: '/privacy', name: 'app_privacy')]
|
#[Route(path: '/privacy', name: 'app_privacy')]
|
||||||
public function privacy(): Response
|
public function privacy(): Response
|
||||||
{
|
{
|
||||||
return $this->render(view: self::TEMPLATE_DIR . 'pages/privacy.html.twig', parameters: [
|
return $this->render(view: '@default/pages/privacy.html.twig');
|
||||||
'template_dir' => self::TEMPLATE_DIR,
|
|
||||||
'controller_name' => 'PagesController',
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ class ProjectsController extends AbstractController
|
|||||||
public function index(ProjectsRepository $projectsRepository, string $name = ''): Response
|
public function index(ProjectsRepository $projectsRepository, string $name = ''): Response
|
||||||
{
|
{
|
||||||
if ($name == '') {
|
if ($name == '') {
|
||||||
return $this->render(view: 'projects/index.html.twig', parameters: [
|
return $this->render(view: '@default/projects/index.html.twig', parameters: [
|
||||||
'projects' => $projectsRepository->findAll(),
|
'projects' => $projectsRepository->findAll(),
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
@ -22,7 +22,7 @@ class ProjectsController extends AbstractController
|
|||||||
|
|
||||||
// $parsedReadMe = $markdownParser->transformMarkdown(text: $readMe);
|
// $parsedReadMe = $markdownParser->transformMarkdown(text: $readMe);
|
||||||
|
|
||||||
return $this->render(view: 'projects/show.html.twig', parameters: [
|
return $this->render(view: '@default/projects/show.html.twig', parameters: [
|
||||||
'project' => $project,
|
'project' => $project,
|
||||||
'readme' => $readMe,
|
'readme' => $readMe,
|
||||||
]);
|
]);
|
||||||
|
179
src/Controller/ResetPasswordController.php
Normal file
179
src/Controller/ResetPasswordController.php
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Form\ChangePasswordFormType;
|
||||||
|
use App\Form\ResetPasswordRequestFormType;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Mailer\MailerInterface;
|
||||||
|
use Symfony\Component\Mime\Address;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
use SymfonyCasts\Bundle\ResetPassword\Controller\ResetPasswordControllerTrait;
|
||||||
|
use SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface;
|
||||||
|
use SymfonyCasts\Bundle\ResetPassword\ResetPasswordHelperInterface;
|
||||||
|
|
||||||
|
class ResetPasswordController extends AbstractController
|
||||||
|
{
|
||||||
|
use ResetPasswordControllerTrait;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly ResetPasswordHelperInterface $resetPasswordHelper,
|
||||||
|
private readonly EntityManagerInterface $entityManager
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// empty body
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display & process form to request a password reset.
|
||||||
|
*/
|
||||||
|
#[Route(path: '/security/forgot/password', name: 'security_forgot_password')]
|
||||||
|
public function request(Request $request, MailerInterface $mailer): Response
|
||||||
|
{
|
||||||
|
$form = $this->createForm(type: ResetPasswordRequestFormType::class);
|
||||||
|
$form->handleRequest(request: $request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
return $this->processSendingPasswordResetEmail(
|
||||||
|
formData: $form->get(name: 'account')->getData(),
|
||||||
|
mailer: $mailer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render(view: '@default/security/forgot_password.html.twig', parameters: [
|
||||||
|
'requestForm' => $form->createView(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirmation page after a user has requested a password reset.
|
||||||
|
*/
|
||||||
|
#[Route(path: '/security/recovery/mail/sent', name: 'security_recovery_mail_sent')]
|
||||||
|
public function checkEmail(): Response
|
||||||
|
{
|
||||||
|
// Generate a fake token if the user does not exist or someone hit this page directly.
|
||||||
|
// This prevents exposing whether a user was found with the given email address or username or not
|
||||||
|
if (null === ($resetToken = $this->getTokenObjectFromSession())) {
|
||||||
|
$resetToken = $this->resetPasswordHelper->generateFakeResetToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render(view: '@default/security/recovery_mail_sent.html.twig', parameters: [
|
||||||
|
'resetToken' => $resetToken,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates and process the reset URL that the user clicked in their email.
|
||||||
|
*/
|
||||||
|
#[Route(path: '/security/recovery/reset/{token}', name: 'security_recovery_reset')]
|
||||||
|
public function reset(Request $request, UserPasswordHasherInterface $passwordHasher, TranslatorInterface $translator, string $token = null): Response
|
||||||
|
{
|
||||||
|
if ($token) {
|
||||||
|
// We store the token in session and remove it from the URL, to avoid the URL being
|
||||||
|
// loaded in a browser and potentially leaking the token to 3rd party JavaScript.
|
||||||
|
$this->storeTokenInSession(token: $token);
|
||||||
|
|
||||||
|
return $this->redirectToRoute(route: 'security_recovery_reset');
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->getTokenFromSession();
|
||||||
|
if (null === $token) {
|
||||||
|
throw $this->createNotFoundException(message: 'No reset password token found in the URL or in the session.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$user = $this->resetPasswordHelper->validateTokenAndFetchUser(fullToken: $token);
|
||||||
|
} catch (ResetPasswordExceptionInterface $e) {
|
||||||
|
$this->addFlash(type: 'reset_password_error', message: sprintf(
|
||||||
|
'%s - %s',
|
||||||
|
$translator->trans(id: ResetPasswordExceptionInterface::MESSAGE_PROBLEM_VALIDATE, parameters: [], domain: 'ResetPasswordBundle'),
|
||||||
|
$translator->trans(id: $e->getReason(), parameters: [], domain: 'ResetPasswordBundle')
|
||||||
|
));
|
||||||
|
|
||||||
|
return $this->redirectToRoute(route: 'app_forgot_password_request');
|
||||||
|
}
|
||||||
|
|
||||||
|
// The token is valid; allow the user to change their password.
|
||||||
|
$form = $this->createForm(type: ChangePasswordFormType::class);
|
||||||
|
$form->handleRequest(request: $request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
// A password reset token should be used only once, remove it.
|
||||||
|
$this->resetPasswordHelper->removeResetRequest(fullToken: $token);
|
||||||
|
|
||||||
|
// Encode(hash) the plain password, and set it.
|
||||||
|
$encodedPassword = $passwordHasher->hashPassword(
|
||||||
|
user: $user,
|
||||||
|
plainPassword: $form->get(name: 'plainPassword')->getData()
|
||||||
|
);
|
||||||
|
|
||||||
|
$user->setPassword($encodedPassword);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
// The session is cleaned up after the password has been changed.
|
||||||
|
$this->cleanSessionAfterReset();
|
||||||
|
|
||||||
|
$this->addFlash(type: 'success', message: 'Your password has been changed.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute(route: 'app_main');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render(view: '@default/security/reset_password.html.twig', parameters: [
|
||||||
|
'resetForm' => $form->createView(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function processSendingPasswordResetEmail(string $formData, MailerInterface $mailer): RedirectResponse
|
||||||
|
{
|
||||||
|
$user = $this->entityManager->getRepository(entityName: User::class)->findOneBy(criteria: [
|
||||||
|
'email' => $formData,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
$user = $this->entityManager->getRepository(entityName: User::class)->findOneBy(criteria: [
|
||||||
|
'username' => $formData,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not reveal whether a user account was found or not.
|
||||||
|
// if (!$user) {
|
||||||
|
// return $this->redirectToRoute(route: 'app_check_email');
|
||||||
|
// }
|
||||||
|
|
||||||
|
try {
|
||||||
|
$resetToken = $this->resetPasswordHelper->generateResetToken(user: $user);
|
||||||
|
} catch (ResetPasswordExceptionInterface $e) {
|
||||||
|
$this->addFlash(type: 'reset_password_error', message: sprintf(
|
||||||
|
'%s - %s',
|
||||||
|
ResetPasswordExceptionInterface::MESSAGE_PROBLEM_HANDLE,
|
||||||
|
$e->getReason()
|
||||||
|
));
|
||||||
|
|
||||||
|
return $this->redirectToRoute(route: 'security_forgot_password');
|
||||||
|
}
|
||||||
|
|
||||||
|
$email = (new TemplatedEmail())
|
||||||
|
->from(new Address(address: 'tracer@24unix.net', name: '24unix.net'))
|
||||||
|
->to($user->getEmail())
|
||||||
|
->subject(subject: 'Your password reset request')
|
||||||
|
->htmlTemplate(template: '@default/security/mail/recovery.html.twig')
|
||||||
|
->context(context: [
|
||||||
|
'resetToken' => $resetToken,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$mailer->send(message: $email);
|
||||||
|
|
||||||
|
// Store the token object in session for retrieval in check-email route.
|
||||||
|
$this->setTokenObjectInSession(token: $resetToken);
|
||||||
|
|
||||||
|
return $this->redirectToRoute(route: 'security_recovery_mail_sent');
|
||||||
|
}
|
||||||
|
}
|
@ -2,46 +2,117 @@
|
|||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use ApiPlatform\Core\Api\IriConverterInterface;
|
use App\Form\LoginFormType;
|
||||||
use App\Entity\User;
|
use App\Form\RegistrationFormType;
|
||||||
|
use App\Repository\UserRepository;
|
||||||
|
use App\Security\EmailVerifier;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\Config\Definition\Exception\Exception;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Mime\Address;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class SecurityController extends AbstractController
|
class SecurityController extends AbstractController
|
||||||
{
|
{
|
||||||
#[Route(path: '/login', name: 'app_login')] // *** method post
|
|
||||||
public function login(AuthenticationUtils $authenticationUtils, IriConverterInterface $iriConverter): Response
|
public function __construct(private readonly EmailVerifier $emailVerifier)
|
||||||
{
|
{
|
||||||
|
// empty body
|
||||||
/** @var User $user */
|
|
||||||
$user = $this->getUser() ?? null;
|
|
||||||
|
|
||||||
return new Response(content: null, status: 204, headers: [
|
|
||||||
'Location' => $iriConverter->getIriFromItem(item: $user)
|
|
||||||
]);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
#[Route(path: '/login', name: 'app_login')]
|
||||||
return $this->render(view: 'security/login.html.twig', parameters: [
|
public function index(AuthenticationUtils $authenticationUtils): Response
|
||||||
'error' => $authenticationUtils->getLastAuthenticationError(),
|
|
||||||
'last_username' => $authenticationUtils->getLastUsername(),
|
|
||||||
]);
|
|
||||||
*
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
#[Route(path: '/logout', name: 'app_logout')]
|
|
||||||
public function logout(): never
|
|
||||||
{
|
{
|
||||||
throw new Exception(message: 'Logout should never be reached.');
|
// get the login error if there is one
|
||||||
|
if ($error = $authenticationUtils->getLastAuthenticationError()) {
|
||||||
|
$this->addFlash(type: 'error', message: $error->getMessageKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// last username entered by the user
|
||||||
|
$lastUsername = $authenticationUtils->getLastUsername();
|
||||||
|
|
||||||
|
return $this->render(view: '@default/security/login.html.twig', parameters: [
|
||||||
|
'last_username' => $lastUsername,
|
||||||
|
'error' => '',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[Route(path: '/register', name: 'app_register')]
|
||||||
|
public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher, EntityManagerInterface $entityManager): Response
|
||||||
|
{
|
||||||
|
$form = $this->createForm(type: RegistrationFormType::class);
|
||||||
|
$form->handleRequest(request: $request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$user = $form->getData();
|
||||||
|
// hash the plain password
|
||||||
|
$user->setPassword(
|
||||||
|
password: $userPasswordHasher->hashPassword(
|
||||||
|
user: $user,
|
||||||
|
plainPassword: $form->get(name: 'password')->getData()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($form->get(name: 'agreeTerms')->getData()) {
|
||||||
|
$user->agreeTerms();
|
||||||
|
} // no else, we already confirmed in the form itself
|
||||||
|
$entityManager->persist(entity: $user);
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
|
// generate a signed url and email it to the user
|
||||||
|
$this->emailVerifier->sendEmailConfirmation(verifyEmailRouteName: 'app_verify_email', user: $user,
|
||||||
|
email: (new TemplatedEmail())
|
||||||
|
->from(new Address(address: 'info@24unix.net', name: '24unix.net'))
|
||||||
|
->to($user->getEmail())
|
||||||
|
->subject(subject: 'Please Confirm your Email')
|
||||||
|
->htmlTemplate(template: '@default/security/mail/registration.html.twig')
|
||||||
|
->context(context: [
|
||||||
|
'username' => $user->getUsername()
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->render(view: '@default/security/registration_finished.html.twig');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render(view: '@default/security/register.html.twig', parameters: [
|
||||||
|
'registrationForm' => $form->createView(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route(path: '/verify/email', name: 'app_verify_email')]
|
||||||
|
public function verifyUserEmail(Request $request, TranslatorInterface $translator, UserRepository $userRepository): Response
|
||||||
|
{
|
||||||
|
$id = $request->get(key: 'id');
|
||||||
|
|
||||||
|
if ($id === null) {
|
||||||
|
return $this->redirectToRoute(route: 'app_register');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $userRepository->find($id);
|
||||||
|
|
||||||
|
if ($user === null) {
|
||||||
|
return $this->redirectToRoute(route: 'app_register');
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate email confirmation link, sets User::isVerified=true and persists
|
||||||
|
try {
|
||||||
|
$this->emailVerifier->handleEmailConfirmation(request: $request, user: $user);
|
||||||
|
} catch (VerifyEmailExceptionInterface $exception) {
|
||||||
|
$this->addFlash(type: 'verify_email_error', message: $translator->trans(id: $exception->getReason(), parameters: [], domain: 'VerifyEmailBundle'));
|
||||||
|
|
||||||
|
return $this->redirectToRoute(route: 'app_main');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addFlash(type: 'success', message: 'Your email address has been verified.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute(route: 'app_main');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,13 @@
|
|||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
|
use App\Form\EditProfileFormType;
|
||||||
use App\Repository\UserRepository;
|
use App\Repository\UserRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||||
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
|
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
|
||||||
@ -13,31 +17,56 @@ use Symfony\Component\Security\Core\Exception\UserNotFoundException;
|
|||||||
/**
|
/**
|
||||||
* Class UserController.
|
* Class UserController.
|
||||||
*/
|
*/
|
||||||
class UserController extends AbstractController
|
class UserController extends BaseController
|
||||||
{
|
{
|
||||||
|
|
||||||
#[Route(path: '/profile/edit/{username}', name: 'app_profile_edit')]
|
#[Route(path: '/profile/edit/{username}', name: 'app_profile_edit')]
|
||||||
public function editProfile(UserRepository $userRepository, string $username = ''): Response
|
public function editProfile(Request $request, UserRepository $userRepository, UserPasswordHasherInterface $userPasswordHasher, EntityManagerInterface $entityManager, string $username = ''): Response
|
||||||
{
|
{
|
||||||
/* var User $user */
|
if ($username !== '') {
|
||||||
if ($username === '') {
|
|
||||||
if ($this->isGranted(attribute: 'ROLE_USER')) {
|
|
||||||
$user = $this->getUser();
|
|
||||||
} else {
|
|
||||||
throw new AccessDeniedException(message: 'You need to be logged in.');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ($this->isGranted(attribute: 'ROLE_ADMIN')) {
|
if ($this->isGranted(attribute: 'ROLE_ADMIN')) {
|
||||||
$user = $userRepository->findOneBy([
|
$user = $userRepository->findOneBy([
|
||||||
'username' => $username,
|
'username' => $username,
|
||||||
]);
|
]);
|
||||||
|
} else {
|
||||||
|
throw new AccessDeniedException(message: 'Only admins are allowed to edit Profiles.');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$user = $this->getUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($user)) {
|
$form = $this->createForm(type: EditProfileFormType::class, data: $user);
|
||||||
return $this->render(view: 'user/edit_profile.html.twig', parameters: [
|
$form->handleRequest(request: $request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$user = $form->getData();
|
||||||
|
// if there's a new password, use it
|
||||||
|
|
||||||
|
if ($form->get(name: 'newPassword')->getData())
|
||||||
|
$user->setPassword(
|
||||||
|
password: $userPasswordHasher->hashPassword(
|
||||||
|
user: $user,
|
||||||
|
plainPassword: $form->get(name: 'newPassword')->getData()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$entityManager->persist(entity: $user);
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
|
return $this->redirectToRoute(route: 'app_main');
|
||||||
|
};
|
||||||
|
|
||||||
|
$user = $form->getData();
|
||||||
|
// hash the plain password
|
||||||
|
|
||||||
|
|
||||||
|
return $this->renderForm(view: '@default/user/edit_profile.html.twig', parameters: [
|
||||||
'user' => $user,
|
'user' => $user,
|
||||||
|
'userForm' => $form
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
if (isset($user)) {
|
||||||
} else {
|
} else {
|
||||||
throw new UserNotFoundException();
|
throw new UserNotFoundException();
|
||||||
}
|
}
|
||||||
|
39
src/Entity/ResetPasswordRequest.php
Normal file
39
src/Entity/ResetPasswordRequest.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\ResetPasswordRequestRepository;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestInterface;
|
||||||
|
use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestTrait;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: ResetPasswordRequestRepository::class)]
|
||||||
|
class ResetPasswordRequest implements ResetPasswordRequestInterface
|
||||||
|
{
|
||||||
|
use ResetPasswordRequestTrait;
|
||||||
|
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
private ?User $user = null;
|
||||||
|
|
||||||
|
public function __construct(User $user, \DateTimeInterface $expiresAt, string $selector, string $hashedToken)
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
$this->initialize(expiresAt: $expiresAt, selector: $selector, hashedToken: $hashedToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): object
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
}
|
@ -2,48 +2,40 @@
|
|||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use ApiPlatform\Core\Annotation\ApiFilter;
|
|
||||||
use ApiPlatform\Core\Annotation\ApiResource;
|
|
||||||
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
|
|
||||||
use App\Repository\UserRepository;
|
use App\Repository\UserRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Stringable;
|
||||||
|
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||||
use Symfony\Component\Security\Core\User\UserInterface;
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
/**
|
|
||||||
* => ["security" => "is_granted('ROLE_ADMIN') or object.owner == user"]
|
|
||||||
attributes : ['security' => 'is_granted("ROLE_USER")']
|
|
||||||
|
|
||||||
*/
|
|
||||||
#[ORM\Entity(repositoryClass: UserRepository::class), ORM\HasLifecycleCallbacks]
|
#[ORM\Entity(repositoryClass: UserRepository::class), ORM\HasLifecycleCallbacks]
|
||||||
#[ApiResource(
|
#[UniqueEntity(fields: ['username', 'email'], message: 'There is already an account with this username or email.')]
|
||||||
collectionOperations: ['get', 'post'],
|
|
||||||
itemOperations : ['get'],
|
|
||||||
)]
|
|
||||||
#[ApiFilter(filterClass: SearchFilter::class, properties: ['username' => 'exact'])]
|
|
||||||
|
|
||||||
class User implements UserInterface, PasswordAuthenticatedUserInterface, \Stringable
|
class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
#[ORM\Column(type: 'integer')]
|
#[ORM\Column(type: 'integer')]
|
||||||
private int $id;
|
private int $id;
|
||||||
|
|
||||||
#[ORM\Column(type: 'string', length: 180, unique: true)]
|
#[ORM\Column(type: 'string', length: 180, unique: true, nullable: false)]
|
||||||
private string $username;
|
private string $username;
|
||||||
|
|
||||||
#[ORM\Column(type: 'json')]
|
#[ORM\Column(type: 'json')]
|
||||||
private array $roles = [];
|
private array $roles = [];
|
||||||
|
|
||||||
#[ORM\Column(type: 'string')]
|
#[ORM\Column(type: 'string', nullable: false)]
|
||||||
private string $password;
|
private string $password;
|
||||||
|
|
||||||
private string $plainPassword;
|
#[ORM\Column(type: 'string', length: 255, nullable: false)]
|
||||||
|
#[Assert\Email(
|
||||||
#[ORM\Column(type: 'string', length: 255)]
|
message: 'The eMail {{ value }} is not a valid eMail.'
|
||||||
|
)]
|
||||||
private string $email;
|
private string $email;
|
||||||
|
|
||||||
#[ORM\Column(type: 'string', length: 255, nullable: true)]
|
#[ORM\Column(type: 'string', length: 255, nullable: true)]
|
||||||
@ -67,15 +59,18 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, \String
|
|||||||
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
||||||
private $modifiedAt;
|
private $modifiedAt;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'boolean')]
|
||||||
|
private $isVerified = false;
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?DateTimeImmutable $agreedTermsAt = null;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->projects = new ArrayCollection();
|
$this->projects = new ArrayCollection();
|
||||||
$this->pages = new ArrayCollection();
|
$this->pages = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public function __toString(): string
|
public function __toString(): string
|
||||||
{
|
{
|
||||||
return $this->username;
|
return $this->username;
|
||||||
@ -86,11 +81,6 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, \String
|
|||||||
return $this->id;
|
return $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPlainPassword(): string
|
|
||||||
{
|
|
||||||
return $this->plainPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUsername(): ?string
|
public function getUsername(): ?string
|
||||||
{
|
{
|
||||||
return $this->username;
|
return $this->username;
|
||||||
@ -308,4 +298,28 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, \String
|
|||||||
{
|
{
|
||||||
$this->modifiedAt = new DateTimeImmutable(datetime: 'now');
|
$this->modifiedAt = new DateTimeImmutable(datetime: 'now');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isVerified(): bool
|
||||||
|
{
|
||||||
|
return $this->isVerified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setIsVerified(bool $isVerified): self
|
||||||
|
{
|
||||||
|
$this->isVerified = $isVerified;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAgreedTermsAt(): ?DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->agreedTermsAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function agreeTerms(): self
|
||||||
|
{
|
||||||
|
$this->agreedTermsAt = new DateTimeImmutable();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
54
src/Form/ChangePasswordFormType.php
Normal file
54
src/Form/ChangePasswordFormType.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
use Symfony\Component\Validator\Constraints\Length;
|
||||||
|
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||||
|
|
||||||
|
class ChangePasswordFormType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add(child: 'plainPassword', type: RepeatedType::class, options: [
|
||||||
|
'type' => PasswordType::class,
|
||||||
|
'options' => [
|
||||||
|
'attr' => [
|
||||||
|
'autocomplete' => 'new-password',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'first_options' => [
|
||||||
|
'constraints' => [
|
||||||
|
new NotBlank(options: [
|
||||||
|
'message' => 'Please enter a password',
|
||||||
|
]),
|
||||||
|
new Length(exactly: [
|
||||||
|
'min' => 6,
|
||||||
|
'minMessage' => 'Your password should be at least {{ limit }} characters',
|
||||||
|
// max length allowed by Symfony for security reasons
|
||||||
|
'max' => 4096,
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
'label' => 'New password',
|
||||||
|
],
|
||||||
|
'second_options' => [
|
||||||
|
'label' => 'Repeat Password',
|
||||||
|
],
|
||||||
|
'invalid_message' => 'The password fields must match.',
|
||||||
|
// Instead of being set onto the object directly,
|
||||||
|
// this is read and encoded in the controller
|
||||||
|
'mapped' => false,
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults(defaults: []);
|
||||||
|
}
|
||||||
|
}
|
45
src/Form/EditProfileFormType.php
Normal file
45
src/Form/EditProfileFormType.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
use Symfony\Component\Validator\Constraints\Length;
|
||||||
|
|
||||||
|
class EditProfileFormType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add(child: 'username')
|
||||||
|
->add(child: 'firstName')
|
||||||
|
->add(child: 'lastName')
|
||||||
|
->add(child: 'email', type: EmailType::class)
|
||||||
|
->add(child: 'newPassword', type: RepeatedType::class, options: [
|
||||||
|
'type' => PasswordType::class,
|
||||||
|
'mapped' => false,
|
||||||
|
'invalid_message' => 'The password fields must match.',
|
||||||
|
'options' => ['attr' => ['class' => 'password-field', 'autocomplete' => 'off']],
|
||||||
|
'required' => false,
|
||||||
|
'first_options' => ['label' => 'Password'],
|
||||||
|
'second_options' => ['label' => 'Repeat Password (only needed if you want to update the password'],
|
||||||
|
'constraints' => [new Length(exactly: ['min' => 6])]
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults(defaults: [
|
||||||
|
'data_class' => User::class,
|
||||||
|
'csrf_protection' => true,
|
||||||
|
'csrf_field_name' => '_csrf_token',
|
||||||
|
'csrf_token_id' => 'authenticate'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
49
src/Form/RegistrationFormType.php
Normal file
49
src/Form/RegistrationFormType.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
use Symfony\Component\Validator\Constraints\IsTrue;
|
||||||
|
use Symfony\Component\Validator\Constraints\Length;
|
||||||
|
|
||||||
|
class RegistrationFormType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add(child: 'username')
|
||||||
|
->add(child: 'email', type: EmailType::class)
|
||||||
|
->add(child: 'password', type: RepeatedType::class, options: [
|
||||||
|
'type' => PasswordType::class,
|
||||||
|
'invalid_message' => 'The password fields must match.',
|
||||||
|
'options' => ['attr' => ['class' => 'password-field']],
|
||||||
|
'required' => true,
|
||||||
|
'first_options' => ['label' => 'Password'],
|
||||||
|
'second_options' => ['label' => 'Repeat Password'],
|
||||||
|
'constraints' => [new Length(exactly: ['min' => 6])]
|
||||||
|
])
|
||||||
|
->add(child: 'agreeTerms', type: CheckboxType::class, options: [
|
||||||
|
'mapped' => false,
|
||||||
|
'constraints' => [
|
||||||
|
new IsTrue(options: [
|
||||||
|
'message' => 'You should agree to our terms.',
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults(defaults: [
|
||||||
|
'data_class' => User::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
31
src/Form/ResetPasswordRequestFormType.php
Normal file
31
src/Form/ResetPasswordRequestFormType.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||||
|
|
||||||
|
class ResetPasswordRequestFormType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add(child: 'account', type: null, options: [
|
||||||
|
'mapped' => false,
|
||||||
|
'constraints' => [
|
||||||
|
new NotBlank(options: [
|
||||||
|
'message' => 'Please enter your email or username',
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults(defaults: []);
|
||||||
|
}
|
||||||
|
}
|
51
src/Repository/ResetPasswordRequestRepository.php
Normal file
51
src/Repository/ResetPasswordRequestRepository.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\ResetPasswordRequest;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestInterface;
|
||||||
|
use SymfonyCasts\Bundle\ResetPassword\Persistence\Repository\ResetPasswordRequestRepositoryTrait;
|
||||||
|
use SymfonyCasts\Bundle\ResetPassword\Persistence\ResetPasswordRequestRepositoryInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<ResetPasswordRequest>
|
||||||
|
*
|
||||||
|
* @method ResetPasswordRequest|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
|
* @method ResetPasswordRequest|null findOneBy(array $criteria, array $orderBy = null)
|
||||||
|
* @method ResetPasswordRequest[] findAll()
|
||||||
|
* @method ResetPasswordRequest[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
*/
|
||||||
|
class ResetPasswordRequestRepository extends ServiceEntityRepository implements ResetPasswordRequestRepositoryInterface
|
||||||
|
{
|
||||||
|
use ResetPasswordRequestRepositoryTrait;
|
||||||
|
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, ResetPasswordRequest::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(ResetPasswordRequest $entity, bool $flush = false): void
|
||||||
|
{
|
||||||
|
$this->getEntityManager()->persist($entity);
|
||||||
|
|
||||||
|
if ($flush) {
|
||||||
|
$this->getEntityManager()->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remove(ResetPasswordRequest $entity, bool $flush = false): void
|
||||||
|
{
|
||||||
|
$this->getEntityManager()->remove($entity);
|
||||||
|
|
||||||
|
if ($flush) {
|
||||||
|
$this->getEntityManager()->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createResetPasswordRequest(object $user, \DateTimeInterface $expiresAt, string $selector, string $hashedToken): ResetPasswordRequestInterface
|
||||||
|
{
|
||||||
|
return new ResetPasswordRequest($user, $expiresAt, $selector, $hashedToken);
|
||||||
|
}
|
||||||
|
}
|
54
src/Security/EmailVerifier.php
Normal file
54
src/Security/EmailVerifier.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Security;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Mailer\MailerInterface;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
|
||||||
|
use SymfonyCasts\Bundle\VerifyEmail\VerifyEmailHelperInterface;
|
||||||
|
|
||||||
|
class EmailVerifier
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly VerifyEmailHelperInterface $verifyEmailHelper,
|
||||||
|
private readonly MailerInterface $mailer,
|
||||||
|
private readonly EntityManagerInterface $entityManager
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendEmailConfirmation(string $verifyEmailRouteName, User /* UserInterface */ $user, TemplatedEmail $email): void
|
||||||
|
{
|
||||||
|
$signatureComponents = $this->verifyEmailHelper->generateSignature(
|
||||||
|
routeName: $verifyEmailRouteName,
|
||||||
|
userId: $user->getId(),
|
||||||
|
userEmail: $user->getEmail(),
|
||||||
|
extraParams: ['id' => $user->getId()]
|
||||||
|
);
|
||||||
|
|
||||||
|
$context = $email->getContext();
|
||||||
|
$context['signedUrl'] = $signatureComponents->getSignedUrl();
|
||||||
|
$context['expiresAtMessageKey'] = $signatureComponents->getExpirationMessageKey();
|
||||||
|
$context['expiresAtMessageData'] = $signatureComponents->getExpirationMessageData();
|
||||||
|
|
||||||
|
$email->context(context: $context);
|
||||||
|
|
||||||
|
$this->mailer->send(message: $email);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws VerifyEmailExceptionInterface
|
||||||
|
*/
|
||||||
|
public function handleEmailConfirmation(Request $request, User /*UserInterface*/ $user): void
|
||||||
|
{
|
||||||
|
$this->verifyEmailHelper->validateEmailConfirmation(signedUrl: $request->getUri(), userId: $user->getId(), userEmail: $user->getEmail());
|
||||||
|
|
||||||
|
$user->setIsVerified(isVerified: true);
|
||||||
|
|
||||||
|
$this->entityManager->persist(entity: $user);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
}
|
@ -3,12 +3,17 @@
|
|||||||
namespace App\Security;
|
namespace App\Security;
|
||||||
|
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
|
use App\Form\LoginFormType;
|
||||||
use App\Repository\UserRepository;
|
use App\Repository\UserRepository;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\Form\FormBuilder;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\RouterInterface;
|
use Symfony\Component\Routing\RouterInterface;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||||
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
|
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
|
||||||
use Symfony\Component\Security\Core\Security;
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
|
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
|
||||||
@ -16,7 +21,9 @@ use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
|
|||||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
|
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
|
||||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
||||||
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials;
|
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials;
|
||||||
|
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
|
||||||
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
||||||
|
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
|
||||||
use Symfony\Component\Security\Http\Util\TargetPathTrait;
|
use Symfony\Component\Security\Http\Util\TargetPathTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,14 +46,12 @@ class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
|
|||||||
|
|
||||||
$request->getSession()->set(name: Security::LAST_USERNAME, value: $username);
|
$request->getSession()->set(name: Security::LAST_USERNAME, value: $username);
|
||||||
|
|
||||||
dd("here");
|
|
||||||
|
|
||||||
return new Passport(
|
return new Passport(
|
||||||
userBadge: new UserBadge(userIdentifier: $username, userLoader: function ($userIdentifier) {
|
userBadge: new UserBadge(userIdentifier: $username, userLoader: function ($username) {
|
||||||
$user = $this->userRepository->findOneBy(['username' => $userIdentifier]);
|
$user = $this->userRepository->findOneBy(['username' => $username]);
|
||||||
|
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
$user = $this->userRepository->findOneBy(['email' => $userIdentifier]);
|
$user = $this->userRepository->findOneBy(['email' => $username]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
@ -56,10 +61,7 @@ class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
|
|||||||
return $user;
|
return $user;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// remove me later for PasswordCredentials()
|
credentials: new PasswordCredentials(password: $password),
|
||||||
credentials: new CustomCredentials(customCredentialsChecker: fn($credentials, User $user) => $credentials === 'test', credentials : $password),
|
|
||||||
|
|
||||||
// new PasswordCredentials($password),
|
|
||||||
badges: [
|
badges: [
|
||||||
new CsrfTokenBadge(csrfTokenId: 'authenticate', csrfToken: $csrfToken),
|
new CsrfTokenBadge(csrfTokenId: 'authenticate', csrfToken: $csrfToken),
|
||||||
new RememberMeBadge(),
|
new RememberMeBadge(),
|
||||||
@ -82,4 +84,5 @@ class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
|
|||||||
{
|
{
|
||||||
return $this->router->generate(name: 'app_login');
|
return $this->router->generate(name: 'app_login');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
32
symfony.lock
32
symfony.lock
@ -1,18 +1,4 @@
|
|||||||
{
|
{
|
||||||
"api-platform/core": {
|
|
||||||
"version": "2.7",
|
|
||||||
"recipe": {
|
|
||||||
"repo": "github.com/symfony/recipes",
|
|
||||||
"branch": "main",
|
|
||||||
"version": "2.5",
|
|
||||||
"ref": "05b57782a78c21a664a42055dc11cf1954ca36bb"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"config/packages/api_platform.yaml",
|
|
||||||
"config/routes/api_platform.yaml",
|
|
||||||
"src/Entity/.gitignore"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"doctrine/annotations": {
|
"doctrine/annotations": {
|
||||||
"version": "1.13",
|
"version": "1.13",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
@ -303,5 +289,23 @@
|
|||||||
"package.json",
|
"package.json",
|
||||||
"webpack.config.js"
|
"webpack.config.js"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"symfonycasts/reset-password-bundle": {
|
||||||
|
"version": "1.16",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "1.0",
|
||||||
|
"ref": "97c1627c0384534997ae1047b93be517ca16de43"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/reset_password.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfonycasts/verify-email-bundle": {
|
||||||
|
"version": "v1.12.0"
|
||||||
|
},
|
||||||
|
"twig/extra-bundle": {
|
||||||
|
"version": "v3.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>{% block title %}Welcome!{% endblock %}</title>
|
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text></svg>">
|
|
||||||
{# Run `composer require symfony/webpack-encore-bundle` to start using Symfony UX #}
|
|
||||||
{% block stylesheets %}
|
|
||||||
{{ encore_entry_link_tags('app') }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block javascripts %}
|
|
||||||
{{ encore_entry_script_tags('app') }}
|
|
||||||
{% endblock %}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{% block body %}{% endblock %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -21,7 +21,7 @@
|
|||||||
{% if is_granted('ROLE_USER') %}
|
{% if is_granted('ROLE_USER') %}
|
||||||
<li class="nav-item dropdown me-auto">
|
<li class="nav-item dropdown me-auto">
|
||||||
<button type="button" id="navbar-dropdown" data-bs-target="#dropdown-menu" data-bs-toggle="dropdown"
|
<button type="button" id="navbar-dropdown" data-bs-target="#dropdown-menu" data-bs-toggle="dropdown"
|
||||||
class="btn btn-primary dropdown-toggle ml-auto mb-2 button-login">
|
class="btn btn-primary dropdown-toggle button-login">
|
||||||
{{ app.user.username }}
|
{{ app.user.username }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<div class="col">
|
<div class="col m-3" id="main_content">
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="q-title-show">Quote of the Moment</h1>
|
<h1 class="q-title-show">Quote of the Moment</h1>
|
||||||
<div class="q-display p-3">
|
<div class="q-display p-3">
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
{% extends template_dir ~ 'base.html.twig' %}
|
{% extends '@default/base.html.twig' %}
|
||||||
|
|
||||||
{% block title %}Imprint{% endblock %}
|
{% block title %}Imprint{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="imprint-wrapper">
|
<div class="imprint-wrapper">
|
||||||
<p>This page is only available in German, as the server is located in Germany.</p>
|
<h2>This page is only available in German, as the server is located in Germany.</h2>
|
||||||
|
|
||||||
|
|
||||||
<p>Inhaltlich Verantwortlicher gemäß § 55 Abs. 2RStV: Michael Espey, Große Kirchreihe 27, 25377 Kollmar</p>
|
<p>Inhaltlich Verantwortlicher gemäß § 55 Abs. 2RStV:<br><br>
|
||||||
|
Michael Espey<br>
|
||||||
|
Große Kirchreihe 27<br>
|
||||||
|
25377 Kollmar</p>
|
||||||
|
|
||||||
<p>eMail: webmaster (AT) 24unix.net</p>
|
<p>eMail: webmaster (AT) 24unix.net</p>
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{% extends template_dir ~'base.html.twig' %}
|
{% extends '@default/base.html.twig' %}
|
||||||
|
|
||||||
{% block title %}Privacy{% endblock %}
|
{% block title %}Privacy{% endblock %}
|
||||||
|
|
||||||
|
70
templates/themes/default/projects/index.html.twig
Normal file
70
templates/themes/default/projects/index.html.twig
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
{# templates/projects/index.html.twig #}
|
||||||
|
{% extends '@default/base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %} Projects {% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="container-fluid box">
|
||||||
|
<div class="row">
|
||||||
|
<h2>This is an overview of my current public (and open source) projects.</h2>
|
||||||
|
|
||||||
|
<!-- projects List -->
|
||||||
|
<div class="col-sm-12">
|
||||||
|
{% for project in projects %}
|
||||||
|
<div class="project-container bg-dark my-4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<a href="{{ path('app_projects', { name: project.name }) }}">
|
||||||
|
{% if project.teaserImage %}
|
||||||
|
<img
|
||||||
|
class="blog-img"
|
||||||
|
src="/uploads/projects/{{ project.teaserImage }}"
|
||||||
|
alt="Teaser">
|
||||||
|
{% else %}
|
||||||
|
<img
|
||||||
|
class="blog-img"
|
||||||
|
src="{{ asset('build/images/24unix/24_logo_bg_96x96.png') }}"
|
||||||
|
alt="Teaser">
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
<br>
|
||||||
|
<div>
|
||||||
|
{% for developer in project.developer %}
|
||||||
|
<a class="align-left blog-details"
|
||||||
|
href="{{ path('app_profile', { 'username':developer.username }) }}">
|
||||||
|
<img class="article-author-img rounded-circle"
|
||||||
|
src="{{ asset('build/images/tracer_schmolle.png') }}"
|
||||||
|
alt="profile"></a>
|
||||||
|
<a href="{{ path('app_profile', { 'username':developer.username }) }}">{{ developer.username }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-8 mt-2">
|
||||||
|
<a href="{{ path('app_projects', { name: project.name }) }}">
|
||||||
|
<div class="article-title d-inline-block pl-3 align-middle">
|
||||||
|
<h2>{{ project.name }}</h2>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<br>
|
||||||
|
<div class="blog-teaser mb-2 pb-2 text-xl-start">
|
||||||
|
{{ project.description }}
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
started: {{ project.createdAt | ago }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if is_granted('ROLE_ADMIN') %}
|
||||||
|
<div class="text-xl-start">
|
||||||
|
<a href="{{ path('app_main') }}"><i class="fa fa-plus-circle"></i></a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
35
templates/themes/default/projects/show.html.twig
Normal file
35
templates/themes/default/projects/show.html.twig
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{# templates/blog/blog_show.html.twig #}
|
||||||
|
{% extends '@default/base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Projects{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
{% if is_granted('ROLE_ADMIN') %}
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<a href="{{ path('app_main', { id : project.id }) }}"><i class="fa fa-3x fa-fw fa-edit"></i></a>
|
||||||
|
<a href="{{ path('app_main', { id : project.id }) }}"><i class="fa fa-3x fa-fw fa-trash"></i></a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="show-article-container p-3 mt-4">
|
||||||
|
<div class="show-article-title-container d-inline-block pl-3 align-middle">
|
||||||
|
<h2>{{ project.name }}</h2>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Source: <a href="{{ project.url }}" target="_blank">{{ project.url }}</a> <i
|
||||||
|
class="fa fa-external-link" aria-hidden="true"></i>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="article-text">
|
||||||
|
{{ readme | markdown_to_html }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
22
templates/themes/default/security/forgot_password.html.twig
Normal file
22
templates/themes/default/security/forgot_password.html.twig
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{% extends '@default/base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Reset your password{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
{% for flash_error in app.flashes('reset_password_error') %}
|
||||||
|
<div class="alert alert-danger" role="alert">{{ flash_error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
<h1>Reset your password</h1>
|
||||||
|
|
||||||
|
{{ form_start(requestForm) }}
|
||||||
|
{{ form_row(requestForm.account) }}
|
||||||
|
<div>
|
||||||
|
<small>
|
||||||
|
Enter your email address or your username and we will send you a
|
||||||
|
link to reset your password.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-primary float-end">Send password reset email</button>
|
||||||
|
{{ form_end(requestForm) }}
|
||||||
|
{% endblock %}
|
@ -1,9 +1,9 @@
|
|||||||
{% extends '../themes/default/base.html.twig' %}
|
{% extends '@default/base.html.twig' %}
|
||||||
|
|
||||||
{% block title %}Log In!{% endblock %}
|
{% block title %}Log In!{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container box">
|
<div class="container rounded">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="login-form bg-dark mt-4 p-4">
|
<div class="login-form bg-dark mt-4 p-4">
|
||||||
<form method="post" class="row g-3">
|
<form method="post" class="row g-3">
|
||||||
@ -15,9 +15,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<label for="inputEmail">Username or eMail</label>
|
<label for="inputUsername">Username or eMail</label>
|
||||||
<input type="text" name="username" id="inputUsername" class="form-control" value="{{ last_username }}" required autofocus>
|
<input type="text" name="username" id="inputUsername"
|
||||||
|
class="form-control" {# value="{{ last_username }}" #} required autofocus>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<label for="inputPassword">Password</label>
|
<label for="inputPassword">Password</label>
|
||||||
@ -28,13 +32,9 @@
|
|||||||
<input type="checkbox" name="_remember_me" class="form-check-input">Remember me
|
<input type="checkbox" name="_remember_me" class="form-check-input">Remember me
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
|
|
||||||
|
|
||||||
<div class="col-12">
|
|
||||||
<button class="btn btn-lg btn-primary float-end" type="submit">
|
<button class="btn btn-lg btn-primary float-end" type="submit">
|
||||||
Sign in
|
Sign in
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -0,0 +1,9 @@
|
|||||||
|
<h1>Hi!</h1>
|
||||||
|
|
||||||
|
<p>To reset your password, please visit the following link</p>
|
||||||
|
|
||||||
|
<a href="{{ url('security_recovery_reset', {token: resetToken.token}) }}">Reset password</a>
|
||||||
|
|
||||||
|
<p>This link will expire in {{ resetToken.expirationMessageKey|trans(resetToken.expirationMessageData, 'ResetPasswordBundle') }}.</p>
|
||||||
|
|
||||||
|
<p>Cheers!</p>
|
@ -0,0 +1,39 @@
|
|||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html lang="en" style="margin:0;padding:0;">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||||
|
<meta name="format-detection" content="telephone=no"/>
|
||||||
|
<title></title>
|
||||||
|
<style type="text/css">
|
||||||
|
@media screen and (max-width: 599px) {
|
||||||
|
div {
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: #1e1f1e;
|
||||||
|
margin:25px;
|
||||||
|
padding:0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="border-collapse:collapse;border-spacing:0;">
|
||||||
|
<h1>Hi {{ username }}, please confirm your email!</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Please confirm your email address by clicking the following link: <br><br>
|
||||||
|
<a href="{{ signedUrl|raw }}">Confirm my Email</a>.
|
||||||
|
<br>
|
||||||
|
This link will expire in {{ expiresAtMessageKey|trans(expiresAtMessageData, 'VerifyEmailBundle') }}.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Kind regards,
|
||||||
|
24unix.net
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,11 @@
|
|||||||
|
{% extends '@default/base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Password Reset Email Sent{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<p>
|
||||||
|
If an account matching your email exists, then an email was just sent that contains a link that you can use to reset your password.
|
||||||
|
This link will expire in {{ resetToken.expirationMessageKey|trans(resetToken.expirationMessageData, 'ResetPasswordBundle') }}.
|
||||||
|
</p>
|
||||||
|
<p>If you don't receive an email please check your spam folder or <a href="{{ path('security_forgot_password') }}">try again</a>.</p>
|
||||||
|
{% endblock %}
|
60
templates/themes/default/security/register.html.twig
Normal file
60
templates/themes/default/security/register.html.twig
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
{% extends '@default/base.html.twig' %}
|
||||||
|
{% form_theme registrationForm _self %}
|
||||||
|
|
||||||
|
{% block title %}Register{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block form_row %}
|
||||||
|
{%- set widget_attr = {} -%}
|
||||||
|
{%- if help is not empty -%}
|
||||||
|
{%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%}
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
|
{{- form_label(form, null, {
|
||||||
|
label_attr: { class: 'sr-only' }
|
||||||
|
}) -}}
|
||||||
|
{{- form_errors(form) -}}
|
||||||
|
{{- form_widget(form, widget_attr) -}}
|
||||||
|
{{- form_help(form) -}}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
{% for flash_error in app.flashes('verify_email_error') %}
|
||||||
|
<div class="alert alert-danger" role="alert">{{ flash_error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="container rounded">
|
||||||
|
<div class="row">
|
||||||
|
<div class="login-form bg-dark mt-4 p-4">
|
||||||
|
<h1 class="h3 mb-3 font-weight-normal">Register</h1>
|
||||||
|
|
||||||
|
{{ form_start(registrationForm, {
|
||||||
|
attr: { class: 'form-signin' }
|
||||||
|
}) }}
|
||||||
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
|
||||||
|
{{ form_row(registrationForm.username, {
|
||||||
|
attr: { placeholder: 'User name' }
|
||||||
|
}) }}
|
||||||
|
{{ form_row(registrationForm.email, {
|
||||||
|
attr: { placeholder: 'eMail' }
|
||||||
|
}) }}
|
||||||
|
{{ form_row(registrationForm.password.first, {
|
||||||
|
attr: { placeholder: 'Password'}
|
||||||
|
}) }}
|
||||||
|
{{ form_row(registrationForm.password.second, {
|
||||||
|
attr: { placeholder: 'Confirm password'}
|
||||||
|
}) }}
|
||||||
|
|
||||||
|
{{ form_row(registrationForm.agreeTerms) }}
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary float-end">Register</button>
|
||||||
|
{{ form_end(registrationForm) }}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
|||||||
|
{% extends '@default/base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Registration finished{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<div class="container rounded">
|
||||||
|
<div class="row">
|
||||||
|
<div class="login-form bg-dark mt-4 p-4">
|
||||||
|
<h1 class="h3 mb-3 font-weight-normal">Registration finished</h1>
|
||||||
|
<p>You'll receive an email soon to confirm your identity.</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
12
templates/themes/default/security/reset_password.html.twig
Normal file
12
templates/themes/default/security/reset_password.html.twig
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{% extends '@default/base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Reset your password{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h1>Reset your password</h1>
|
||||||
|
|
||||||
|
{{ form_start(resetForm) }}
|
||||||
|
{{ form_row(resetForm.plainPassword) }}
|
||||||
|
<button class="btn btn-primary">Reset password</button>
|
||||||
|
{{ form_end(resetForm) }}
|
||||||
|
{% endblock %}
|
@ -1,4 +1,4 @@
|
|||||||
{% extends '../themes/default/base.html.twig' %}
|
{% extends '@default/base.html.twig' %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Profile of {{ user.username }}
|
Profile of {{ user.username }}
|
||||||
@ -13,38 +13,30 @@
|
|||||||
<img class="rounded-circle mt-5"
|
<img class="rounded-circle mt-5"
|
||||||
width="150px"
|
width="150px"
|
||||||
src=" {{ asset('build/images/tracer_schmolle150x150.png') }}" alt="profile image">
|
src=" {{ asset('build/images/tracer_schmolle150x150.png') }}" alt="profile image">
|
||||||
|
|
||||||
<span class="font-weight-bold">{{ user.username }}</span>
|
<span class="font-weight-bold">{{ user.username }}</span>
|
||||||
<span class="text-white-50"><i class="fa fa-lg fa-envelope me-1"></i>{{ user.email }}</span>
|
<span class="text-white-50"><i class="fa fa-lg fa-envelope me-1"></i>{{ user.email }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-5 border-right">
|
<div class="col-md-5 border-right">
|
||||||
|
{{ form_start(userForm) }}
|
||||||
<div class="p-3 py-5">
|
<div class="p-3 py-5">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
<h4 class="text-right">User Profile</h4>
|
<h4 class="text-right">User Profile</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-2">
|
<div class="row mt-2">
|
||||||
<div class="col-md-6">
|
{{ form_row(userForm.username) }}
|
||||||
<label class="labels" for="first-name">First Name</label>
|
{{ form_row(userForm.firstName) }}
|
||||||
<input type="text" id="first-name" class="form-control" placeholder="First Name" value="{{ user.firstName }}">
|
{{ form_row(userForm.lastName) }}
|
||||||
|
{{ form_row(userForm.email) }}
|
||||||
|
{{ form_row(userForm.newPassword.first) }}
|
||||||
|
{{ form_row(userForm.newPassword.second) }}
|
||||||
|
{{ form_rest(userForm) }}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-5 text-center float-end">
|
||||||
<div class="col-md-6">
|
<button class="btn btn-primary profile-button" type="submit">Save Profile</button>
|
||||||
<label class="labels" for="last-name">Last Name</label>
|
|
||||||
<input type="text" id="last-name" class="form-control" value="{{ user.lastName }}" placeholder="Last Name">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="labels" for="username">Username</label>
|
|
||||||
<input type="text" class="form-control" id="username" placeholder="eMail address" value="{{ user.email }}">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
<div class="mt-5 text-center">
|
|
||||||
<button class="btn btn-primary profile-button" type="button">Save Profile</button>
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -1,4 +1,4 @@
|
|||||||
{% extends '../themes/default/base.html.twig' %}
|
{% extends '@default/base.html.twig' %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Profile of {{ user.username }}
|
Profile of {{ user.username }}
|
Loading…
Reference in New Issue
Block a user