diff --git a/.env b/.env
index 5c5c85f..e0f36bf 100644
--- a/.env
+++ b/.env
@@ -42,3 +42,9 @@ CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
###< symfony/messenger ###
+
+###> symfony/lock ###
+# Choose one of the stores below
+# postgresql+advisory://db_user:db_password@localhost/db_name
+LOCK_DSN=flock
+###< symfony/lock ###
diff --git a/TODO b/TODO
index 7d3b7fd..dbf4f6e 100644
--- a/TODO
+++ b/TODO
@@ -2,6 +2,7 @@ accent color #d43934
add dates and author to pages
+blog
use turbo
make unit tests
diff --git a/assets/app.js b/assets/app.js
index 48617e4..0bb46da 100644
--- a/assets/app.js
+++ b/assets/app.js
@@ -10,22 +10,58 @@ import 'fork-awesome/scss/fork-awesome.scss'
import './styles/app.scss'
import $ from 'jquery'
import 'bootstrap'
+
+// Dropzone stuff move to component
+import {Dropzone} from 'dropzone'
+
+// TODO handle error (Chapter 26)
+const formElement = $('#dropzoneForm')
+if (formElement) {
+ const previewContent = $('#preview-content').html()
+ console.log(previewContent)
+ const dropzone = new Dropzone('#dropzoneForm', {
+ acceptedFiles: '.jpg, .jpeg, .png',
+ maxFiles: 1,
+ init: function () {
+ this.hiddenFileInput.removeAttribute('multiple')
+ this.on('maxfilesexceeded', (file) => {
+ this.removeAllFiles()
+ this.addFile(file)
+ })
+ this.on('error', (file, data) => {
+ console.log('error');
+ if (data.detail) {
+ this.emit('error', file, data.detail)
+ }
+
+ })
+ }
+ })
+ console.log('filename', previewContent)
+ const mockFile = { name: previewContent }
+ console.log('file', mockFile)
+ dropzone.displayExistingFile(mockFile)
+}
+
+// End Dropzone stuff move to component
+
//import './js/index'
// needed for legacy code
//global.$ = $
if (window.matchMedia('(prefers-color-scheme)').media !== 'not all') {
- console.log('🎉 Dark mode is supported')
+ console.log('🎉 Dark mode is supported')
}
$(document).ready(() => {
- console.log('ready')
- $('#toggleSidebar').on('click', () => {
- const toggleIcon = $('#toggleIcon')
- 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')
- })
+ console.log('ready')
+ $('#toggleSidebar').on('click', () => {
+ const toggleIcon = $('#toggleIcon')
+ 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')
+ })
})
+
// start the Stimulus application
//import './bootstrap'
diff --git a/assets/js/components/_avatarDropzone.js b/assets/js/components/_avatarDropzone.js
new file mode 100644
index 0000000..597a6db
--- /dev/null
+++ b/assets/js/components/_avatarDropzone.js
@@ -0,0 +1 @@
+i
\ No newline at end of file
diff --git a/assets/styles/app.css b/assets/styles/app.css
deleted file mode 100644
index cb33b13..0000000
--- a/assets/styles/app.css
+++ /dev/null
@@ -1,3 +0,0 @@
-body {
- background-color: lightgray;
-}
diff --git a/assets/styles/app.scss b/assets/styles/app.scss
index ad39446..fbf9f2f 100644
--- a/assets/styles/app.scss
+++ b/assets/styles/app.scss
@@ -18,13 +18,17 @@
$primary: #FF8040;
$jet-black: #0e0e10;
-$body-color: #3f3f3f;
-$list-group-bg: $body-color;
-$list-group-hover-bg: #232323;
-$list-group-active-bg: #232323;
-$list-group-action-active-bg: #232323;
-@import '~bootstrap';
+
+$body-color: #9f9f9f;
+$list-group: #232323;
+$list-group-bg: $list-group;
+$list-group-hover-bg: darken($list-group, 10%);
+$list-group-active-bg: $list-group;
+$list-group-action-active-bg: $list-group;
+
+@import 'bootstrap/scss/bootstrap';
+@import 'dropzone/dist/dropzone';
@import './components/sidebar';
@@ -36,7 +40,16 @@ html, body {
background: #0e0e10;
}
+// Dropzone
+.dropzone .dz-preview.dz-image-preview {
+ background: transparent !important;
+}
+
+.article-author-img {
+ width: 80px;
+ margin: 15px;
+}
///
diff --git a/composer.json b/composer.json
index 1195f29..b632310 100644
--- a/composer.json
+++ b/composer.json
@@ -14,6 +14,7 @@
"easycorp/easyadmin-bundle": "^4.0",
"knplabs/knp-time-bundle": "^1.18",
"league/commonmark": "^2.3",
+ "liip/imagine-bundle": "^2.9",
"nelmio/cors-bundle": "^2.2",
"phpdocumentor/reflection-docblock": "^5.3",
"phpstan/phpdoc-parser": "^1.4",
@@ -39,6 +40,7 @@
"symfony/property-access": "6.1.*",
"symfony/property-info": "6.1.*",
"symfony/proxy-manager-bridge": "6.1.*",
+ "symfony/rate-limiter": "6.1.*",
"symfony/runtime": "6.1.*",
"symfony/security-bundle": "6.1.*",
"symfony/security-csrf": "6.1.*",
diff --git a/composer.lock b/composer.lock
index 1bae413..ccaa754 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "a389aa46db42a7da38ebb6610fb97a52",
+ "content-hash": "1c940d128112ec366d0f81bc6eccee45",
"packages": [
{
"name": "dflydev/dot-access-data",
@@ -1689,6 +1689,68 @@
],
"time": "2022-10-17T19:48:16+00:00"
},
+ {
+ "name": "imagine/imagine",
+ "version": "1.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-imagine/Imagine.git",
+ "reference": "ae864f26afbf8859ebd2e2b9df92d77ee175dc13"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-imagine/Imagine/zipball/ae864f26afbf8859ebd2e2b9df92d77ee175dc13",
+ "reference": "ae864f26afbf8859ebd2e2b9df92d77ee175dc13",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4 || ^9.3"
+ },
+ "suggest": {
+ "ext-exif": "to read EXIF metadata",
+ "ext-gd": "to use the GD implementation",
+ "ext-gmagick": "to use the Gmagick implementation",
+ "ext-imagick": "to use the Imagick implementation"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-develop": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Imagine\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bulat Shakirzyanov",
+ "email": "mallluhuct@gmail.com",
+ "homepage": "http://avalanche123.com"
+ }
+ ],
+ "description": "Image processing for PHP 5.3",
+ "homepage": "http://imagine.readthedocs.org/",
+ "keywords": [
+ "drawing",
+ "graphics",
+ "image manipulation",
+ "image processing"
+ ],
+ "support": {
+ "issues": "https://github.com/php-imagine/Imagine/issues",
+ "source": "https://github.com/php-imagine/Imagine/tree/1.3.2"
+ },
+ "time": "2022-04-01T11:58:30+00:00"
+ },
{
"name": "knplabs/knp-time-bundle",
"version": "v1.20.0",
@@ -2016,6 +2078,108 @@
],
"time": "2021-08-14T12:15:32+00:00"
},
+ {
+ "name": "liip/imagine-bundle",
+ "version": "2.9.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/liip/LiipImagineBundle.git",
+ "reference": "ba164fef7be638f28d298f9c89b5a8364c3e0a4d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/ba164fef7be638f28d298f9c89b5a8364c3e0a4d",
+ "reference": "ba164fef7be638f28d298f9c89b5a8364c3e0a4d",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "imagine/imagine": "^1.2.4",
+ "php": "^7.1|^8.0",
+ "symfony/filesystem": "^3.4|^4.4|^5.3|^6.0",
+ "symfony/finder": "^3.4|^4.4|^5.3|^6.0",
+ "symfony/framework-bundle": "^3.4.23|^4.4|^5.3|^6.0",
+ "symfony/mime": "^4.4|^5.3|^6.0",
+ "symfony/options-resolver": "^3.4|^4.4|^5.3|^6.0",
+ "symfony/process": "^3.4|^4.4|^5.3|^6.0",
+ "twig/twig": "^1.44|^2.9|^3.0"
+ },
+ "require-dev": {
+ "amazonwebservices/aws-sdk-for-php": "^1.0",
+ "aws/aws-sdk-php": "^2.4",
+ "doctrine/cache": "^1.11|^2.0",
+ "doctrine/persistence": "^1.3|^2.0",
+ "enqueue/enqueue-bundle": "^0.9|^0.10",
+ "ext-gd": "*",
+ "league/flysystem": "^1.0|^2.0|^3.0",
+ "phpstan/phpstan": "^0.12.64",
+ "psr/cache": "^1.0|^2.0|^3.0",
+ "psr/log": "^1.0",
+ "symfony/browser-kit": "^3.4|^4.4|^5.3|^6.0",
+ "symfony/cache": "^3.4|^4.4|^5.3|^6.0",
+ "symfony/console": "^3.4|^4.4|^5.3|^6.0",
+ "symfony/dependency-injection": "^3.4|^4.4|^5.3|^6.0",
+ "symfony/form": "^3.4|^4.4|^5.3|^6.0",
+ "symfony/messenger": "^4.4|^5.3|^6.0",
+ "symfony/phpunit-bridge": "^5.3",
+ "symfony/templating": "^3.4|^4.4|^5.3|^6.0",
+ "symfony/validator": "^3.4|^4.4|^5.3|^6.0",
+ "symfony/yaml": "^3.4|^4.4|^5.3|^6.0"
+ },
+ "suggest": {
+ "alcaeus/mongo-php-adapter": "required for mongodb components",
+ "amazonwebservices/aws-sdk-for-php": "required to use AWS version 1 cache resolver",
+ "aws/aws-sdk-php": "required to use AWS version 2/3 cache resolver",
+ "doctrine/mongodb-odm": "required to use mongodb-backed doctrine components",
+ "enqueue/enqueue-bundle": "^0.9 add if you like to process images in background",
+ "ext-exif": "required to read EXIF metadata from images",
+ "ext-gd": "required to use gd driver",
+ "ext-gmagick": "required to use gmagick driver",
+ "ext-imagick": "required to use imagick driver",
+ "ext-mongodb": "required for mongodb components",
+ "league/flysystem": "required to use FlySystem data loader or cache resolver",
+ "monolog/monolog": "A psr/log compatible logger is required to enable logging",
+ "symfony/messenger": "If you like to process images in background",
+ "symfony/templating": "required to use deprecated Templating component instead of Twig"
+ },
+ "type": "symfony-bundle",
+ "autoload": {
+ "psr-4": {
+ "Liip\\ImagineBundle\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Liip and other contributors",
+ "homepage": "https://github.com/liip/LiipImagineBundle/contributors"
+ }
+ ],
+ "description": "This bundle provides an image manipulation abstraction toolkit for Symfony-based projects.",
+ "homepage": "http://liip.ch",
+ "keywords": [
+ "bundle",
+ "image",
+ "imagine",
+ "liip",
+ "manipulation",
+ "photos",
+ "pictures",
+ "symfony",
+ "transformation"
+ ],
+ "support": {
+ "issues": "https://github.com/liip/LiipImagineBundle/issues",
+ "source": "https://github.com/liip/LiipImagineBundle/tree/2.9.0"
+ },
+ "time": "2022-10-06T06:33:35+00:00"
+ },
{
"name": "monolog/monolog",
"version": "3.2.0",
@@ -5076,6 +5240,83 @@
],
"time": "2022-10-23T10:33:34+00:00"
},
+ {
+ "name": "symfony/lock",
+ "version": "v6.1.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/lock.git",
+ "reference": "98d6c4b6608d15e403a228c15afb4f3e71b22a57"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/lock/zipball/98d6c4b6608d15e403a228c15afb4f3e71b22a57",
+ "reference": "98d6c4b6608d15e403a228c15afb4f3e71b22a57",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/log": "^1|^2|^3"
+ },
+ "conflict": {
+ "doctrine/dbal": "<2.13"
+ },
+ "require-dev": {
+ "doctrine/dbal": "^2.13|^3.0",
+ "predis/predis": "~1.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Lock\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jérémy Derussé",
+ "email": "jeremy@derusse.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Creates and manages locks, a mechanism to provide exclusive access to a shared resource",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "cas",
+ "flock",
+ "locking",
+ "mutex",
+ "redlock",
+ "semaphore"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/lock/tree/v6.1.7"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-10-28T16:23:08+00:00"
+ },
{
"name": "symfony/mailer",
"version": "v6.1.7",
@@ -6495,6 +6736,76 @@
],
"time": "2022-03-02T13:21:45+00:00"
},
+ {
+ "name": "symfony/rate-limiter",
+ "version": "v6.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/rate-limiter.git",
+ "reference": "9e75706446f7c3686773c408f422ffb5ec4ba32b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/9e75706446f7c3686773c408f422ffb5ec4ba32b",
+ "reference": "9e75706446f7c3686773c408f422ffb5ec4ba32b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "symfony/lock": "^5.4|^6.0",
+ "symfony/options-resolver": "^5.4|^6.0"
+ },
+ "require-dev": {
+ "psr/cache": "^1.0|^2.0|^3.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\RateLimiter\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Wouter de Jong",
+ "email": "wouter@wouterj.nl"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides a Token Bucket implementation to rate limit input and output in your application",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "limiter",
+ "rate-limiter"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/rate-limiter/tree/v6.1.3"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-07-20T13:46:29+00:00"
+ },
{
"name": "symfony/routing",
"version": "v6.1.7",
diff --git a/config/bundles.php b/config/bundles.php
index dd71193..08dbdfd 100644
--- a/config/bundles.php
+++ b/config/bundles.php
@@ -18,4 +18,5 @@ return [
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle::class => ['all' => true],
SymfonyCasts\Bundle\ResetPassword\SymfonyCastsResetPasswordBundle::class => ['all' => true],
+ Liip\ImagineBundle\LiipImagineBundle::class => ['all' => true],
];
diff --git a/config/packages/liip_imagine.yaml b/config/packages/liip_imagine.yaml
new file mode 100644
index 0000000..79bd742
--- /dev/null
+++ b/config/packages/liip_imagine.yaml
@@ -0,0 +1,24 @@
+# Documentation on how to configure the bundle can be found at: https://symfony.com/doc/current/bundles/LiipImagineBundle/basic-usage.html
+liip_imagine:
+ # valid drivers options include "gd" or "gmagick" or "imagick"
+ driver: "gd"
+
+ filter_sets:
+ squared_thumbnail_small:
+ filters:
+ thumbnail:
+ size: [100, 100]
+ mode: outbound
+ allow_upscale: true
+ squared_thumbnail_medium:
+ filters:
+ thumbnail:
+ size: [200, 200]
+ mode: outbound
+ allow_upscale: true
+ squared_thumbnail_large:
+ filters:
+ thumbnail:
+ size: [400, 400]
+ mode: outbound
+ allow_upscale: true
\ No newline at end of file
diff --git a/config/packages/lock.yaml b/config/packages/lock.yaml
new file mode 100644
index 0000000..574879f
--- /dev/null
+++ b/config/packages/lock.yaml
@@ -0,0 +1,2 @@
+framework:
+ lock: '%env(LOCK_DSN)%'
diff --git a/config/packages/security.yaml b/config/packages/security.yaml
index b7ad3e4..da3b26e 100644
--- a/config/packages/security.yaml
+++ b/config/packages/security.yaml
@@ -28,6 +28,8 @@ security:
path: security_logout
switch_user: true
+ login_throttling: true
+
remember_me:
secret: '%kernel.secret%'
signature_properties: [password]
diff --git a/config/routes/liip_imagine.yaml b/config/routes/liip_imagine.yaml
new file mode 100644
index 0000000..201cbd5
--- /dev/null
+++ b/config/routes/liip_imagine.yaml
@@ -0,0 +1,2 @@
+_liip_imagine:
+ resource: "@LiipImagineBundle/Resources/config/routing.yaml"
diff --git a/config/services.yaml b/config/services.yaml
index 73b35dd..112efda 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -11,6 +11,9 @@ services:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
+ # bind
+ # $var: 'content'
+
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
diff --git a/package.json b/package.json
index 0e0ac3e..74f8f8f 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@typescript-eslint/eslint-plugin-tslint": "^5.26.0",
"axios": "^0.27.1",
"bootstrap": "^5.2.2",
- "bootswatch": "^5.2.2",
+ "dropzone": "^6.0.0-beta.2",
"eslint": "^8.15.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.26.0",
diff --git a/public/uploads/avatars/24_logo_bg_90x90.png b/public/uploads/avatars/24_logo_bg_90x90.png
deleted file mode 100644
index b00fda4..0000000
Binary files a/public/uploads/avatars/24_logo_bg_90x90.png and /dev/null differ
diff --git a/public/uploads/avatars/tracer_schmolle.png b/public/uploads/avatars/tracer_schmolle.png
deleted file mode 100644
index a3147c8..0000000
Binary files a/public/uploads/avatars/tracer_schmolle.png and /dev/null differ
diff --git a/src/Controller/Admin/ProjectsCrudController.php b/src/Controller/Admin/ProjectsCrudController.php
index 6173a60..dfbae28 100644
--- a/src/Controller/Admin/ProjectsCrudController.php
+++ b/src/Controller/Admin/ProjectsCrudController.php
@@ -4,6 +4,8 @@ namespace App\Controller\Admin;
use App\Entity\Projects;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
+use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
+use EasyCorp\Bundle\EasyAdminBundle\Field\Field;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
@@ -20,11 +22,13 @@ class ProjectsCrudController extends AbstractCrudController
yield IdField::new(propertyName: 'id')
->onlyOnIndex();
yield TextField::new(propertyName: 'name');
- yield TextField::new(propertyName: 'description');
+ yield AssociationField::new('developer');
yield TextField::new(propertyName: 'description');
yield ImageField::new(propertyName: 'teaserImage')
->setBasePath(path: 'uploads/projects')
->setUploadDir(uploadDirPath: 'public/uploads/projects')
->setUploadedFileNamePattern(patternOrCallable: '[timestamp]-[slug].[extension]');
+ yield Field::new('createdAt')
+ ->hideOnForm();
}
}
diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php
index 448d76b..d9e0c0d 100644
--- a/src/Controller/SecurityController.php
+++ b/src/Controller/SecurityController.php
@@ -87,18 +87,7 @@ class SecurityController extends AbstractController
} // 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->sendEmailConfirmation(verifyEmailRouteName: 'security_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()
- ])
- );
+ $this->generateSignedUrlAndEmailToTheUser($user);
return $this->render(view: '@default/security/registration_finished.html.twig');
}
@@ -127,7 +116,7 @@ class SecurityController extends AbstractController
try {
$this->handleEmailConfirmation(request: $request, user: $user);
} catch (VerifyEmailExceptionInterface $exception) {
- $this->addFlash(type: 'verify_email_error', message: $translator->trans(id: $exception->getReason(), parameters: [], domain: 'VerifyEmailBundle'));
+ $this->addFlash(type: 'error', message: $translator->trans(id: $exception->getReason(), parameters: [], domain: 'VerifyEmailBundle'));
return $this->redirectToRoute(route: 'app_main');
}
@@ -315,11 +304,48 @@ class SecurityController extends AbstractController
*/
public function handleEmailConfirmation(Request $request, User /*UserInterface*/ $user): void
{
- $this->verifyEmailHelper->validateEmailConfirmation(signedUrl: $request->getUri(), userId: $user->getId(), userEmail: $user->getEmail());
+ $this->verifyEmailHelper->validateEmailConfirmation(signedUrl: $request->getUri(), userId: $user->getId(), userEmail: $user->getEmail());
+ $user->setIsVerified(isVerified: true);
+ $this->entityManager->persist(entity: $user);
+ $this->entityManager->flush();
+ }
- $user->setIsVerified(isVerified: true);
+ /**
+ * @param mixed $user
+ * @return void
+ */
+ public function generateSignedUrlAndEmailToTheUser(mixed $user): void
+ {
+ $this->sendEmailConfirmation(verifyEmailRouteName: 'security_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()
+ ])
+ );
+ }
- $this->entityManager->persist(entity: $user);
- $this->entityManager->flush();
+
+ #[Route('/security/resend/verify_email', name: 'security_resend_verify_email')]
+ public function resendVerifyEmail(Request $request, UserRepository $userRepository)
+ {
+
+ if ($request->isMethod('POST')) {
+
+ $email = $request->getSession()->get('non_verified_email');
+ $user = $userRepository->findOneBy(['email' => $email]);
+ if (!$user) {
+ throw $this->createNotFoundException('user not found for email');
+ }
+
+ $this->generateSignedUrlAndEmailToTheUser(user: $user);
+ $this->addFlash('success', 'eMail has been sent.');
+
+ return $this->redirectToRoute('app_main');
+ }
+ return $this->render('@default/security/resend_activation.html.twig');
}
}
diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php
index afad2ef..9f20143 100644
--- a/src/Controller/UserController.php
+++ b/src/Controller/UserController.php
@@ -6,13 +6,18 @@ use App\Entity\User;
use App\Form\EditProfileFormType;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
+use Sunrise\Slugger\Slugger;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
+use Symfony\Component\Validator\Constraints\File;
+use Symfony\Component\Validator\Constraints\NotBlank;
+use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* Class UserController.
@@ -56,10 +61,6 @@ class UserController extends BaseController
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,
'userForm' => $form
@@ -93,4 +94,48 @@ class UserController extends BaseController
'users' => $users,
]);
}
+
+ // TODO move to a helper class
+ function humanFilesize($bytes, $decimals = 2)
+ {
+ $sz = 'BKMGTP';
+ $factor = floor((strlen($bytes) - 1) / 3);
+ return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
+ }
+
+
+ #[Route(path: '/user/upload/avatar/{id}', name: 'user_upload_avatar')]
+ public function uploadAvatar(
+ Request $request,
+ UserRepository $userRepository,
+ EntityManagerInterface $entityManager,
+ ValidatorInterface $validator,
+ int $id)
+ {
+ $user = $userRepository->find($id);
+
+ if (!$user) {
+ return $this->json('User not found.', 404);
+ }
+
+ $postMaxSize = UploadedFile::getMaxFilesize();
+ $contentLength = $request->headers->get('Content-length');
+
+ if ($contentLength > $postMaxSize) {
+ return $this->json('File is bigger than the allowed ' . $this->humanFilesize($postMaxSize) . ' Bytes.', 400);
+ }
+
+ $uploadedAvatar = $request->files->get('file');
+ $destination = $this->getParameter(name: 'kernel.project_dir') . '/public/uploads/avatars';
+ $originalFilename = pathinfo($uploadedAvatar->getClientOriginalName(), PATHINFO_FILENAME);
+ $slugger = new Slugger();
+ $cleanFilename = $slugger->slugify($originalFilename);
+ $newFilename = $cleanFilename . '-' . uniqid() . '.' . $uploadedAvatar->guessExtension();
+ $uploadedAvatar->move($destination, $newFilename);
+ $user->setAvatar($newFilename);
+ $entityManager->persist(entity: $user);
+ $entityManager->flush();
+
+ return $this->json(data: 'OK', status: 201);
+ }
}
diff --git a/src/Entity/Projects.php b/src/Entity/Projects.php
index b29bc24..7012762 100644
--- a/src/Entity/Projects.php
+++ b/src/Entity/Projects.php
@@ -2,15 +2,14 @@
namespace App\Entity;
-use ApiPlatform\Core\Annotation\ApiResource;
use App\Repository\ProjectsRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
+use Stringable;
#[ORM\Entity(repositoryClass: ProjectsRepository::class)]
-#[ApiResource]
-class Projects implements \Stringable
+class Projects implements Stringable
{
#[ORM\Id]
#[ORM\GeneratedValue]
@@ -33,7 +32,7 @@ class Projects implements \Stringable
private ?string $teaserImage = null;
#[ORM\ManyToMany(targetEntity: User::class, inversedBy: 'projects')]
- private $developer;
+ private Collection $developer;
public function __construct()
{
diff --git a/src/EventSubscriber/CheckVerifiedUserSubscriber.php b/src/EventSubscriber/CheckVerifiedUserSubscriber.php
new file mode 100644
index 0000000..ec3cd38
--- /dev/null
+++ b/src/EventSubscriber/CheckVerifiedUserSubscriber.php
@@ -0,0 +1,61 @@
+getPassport();
+ /*
+ * var User $user
+ */
+ $user = $passport->getUser();
+
+ if (!$user->isVerified()) {
+ throw new UserNotVerifiedException();
+ }
+ }
+
+
+ public function onValidationFailure(LoginFailureEvent $failureEvent)
+ {
+ if (!$failureEvent->getException() instanceof UserNotVerifiedException) {
+ return;
+ }
+
+ $request = $failureEvent->getRequest();
+ $email = $failureEvent->getPassport()->getUser()->getEmail();
+ $request->getSession()->set('non_verified_email', $email);
+
+ $response = new RedirectResponse(
+ $this->router->generate('security_resend_verify_email')
+ );
+ $failureEvent->setResponse($response);
+ }
+
+
+ public static function getSubscribedEvents(): array
+ {
+ return [
+ CheckPassportEvent::class => ['onCheckPassport', -10],
+ LoginFailureEvent::class => 'onValidationFailure'
+ ];
+ }
+}
\ No newline at end of file
diff --git a/src/Exception/UserNotVerifiedException.php b/src/Exception/UserNotVerifiedException.php
index afc4460..bed0169 100644
--- a/src/Exception/UserNotVerifiedException.php
+++ b/src/Exception/UserNotVerifiedException.php
@@ -2,62 +2,10 @@
namespace App\Exception;
-use JetBrains\PhpStorm\ArrayShape;
+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
-use function is_array;
class UserNotVerifiedException extends AuthenticationException
{
- private ?string $identifier = null;
-
- /**
- * {@inheritdoc}
- */
- public function getMessageKey(): string
- {
- return 'User is not verified.';
- }
-
- /**
- * Get the user identifier (e.g. username or email address).
- */
- public function getUserIdentifier(): ?string
- {
- return $this->identifier;
- }
-
- /**
- * Set the user identifier (e.g. username or email address).
- */
- public function setUserIdentifier(string $identifier): void
- {
- $this->identifier = $identifier;
- }
-
- /**
- * {@inheritdoc}
- */
- #[ArrayShape(shape: ['{{ username }}' => "null|string", '{{ user_identifier }}' => "null|string"])]
- public function getMessageData(): array
- {
- return ['{{ username }}' => $this->identifier, '{{ user_identifier }}' => $this->identifier];
- }
-
- /**
- * {@inheritdoc}
- */
- public function __serialize(): array
- {
- return [$this->identifier, parent::__serialize()];
- }
-
- /**
- * {@inheritdoc}
- */
- public function __unserialize(array $data): void
- {
- [$this->identifier, $parentData] = $data;
- $parentData = is_array(value: $parentData) ? $parentData : unserialize(data: $parentData);
- parent::__unserialize(data: $parentData);
- }
-}
+ // empty body
+}
\ No newline at end of file
diff --git a/src/Form/EditProfileFormType.php b/src/Form/EditProfileFormType.php
index 65d98c4..a60f68b 100644
--- a/src/Form/EditProfileFormType.php
+++ b/src/Form/EditProfileFormType.php
@@ -5,10 +5,12 @@ 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\FileType;
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\Image;
use Symfony\Component\Validator\Constraints\Length;
class EditProfileFormType extends AbstractType
@@ -27,7 +29,7 @@ class EditProfileFormType extends AbstractType
'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'],
+ 'second_options' => ['label' => 'Repeat Password (only needed if you want to update the password)'],
'constraints' => [new Length(exactly: ['min' => 6])]
])
;
diff --git a/src/Security/LoginFormAuthenticator.php b/src/Security/LoginFormAuthenticator.php
index 6f72500..8a47316 100644
--- a/src/Security/LoginFormAuthenticator.php
+++ b/src/Security/LoginFormAuthenticator.php
@@ -9,6 +9,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
@@ -51,10 +52,6 @@ class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
throw new UserNotFoundException();
}
- if (!$user->isVerified()) {
- throw new UserNotVerifiedException();
- }
-
return $user;
}),
diff --git a/src/Twig/AppExtension.php b/src/Twig/AppExtension.php
new file mode 100644
index 0000000..4995c9e
--- /dev/null
+++ b/src/Twig/AppExtension.php
@@ -0,0 +1,27 @@
+