bumped symfony to 5.3
4
.env
@ -26,3 +26,7 @@ APP_SECRET=cd0ae68f915f2a06b82007f2906e54e8
|
||||
# DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7"
|
||||
DATABASE_URL="mysql://24unix:24.unix@127.0.0.1:3306/24unix"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
###> symfony/mailer ###
|
||||
MAILER_DSN=smtp://localhost
|
||||
###< symfony/mailer ###
|
||||
|
7
.gitignore
vendored
@ -8,3 +8,10 @@
|
||||
/var/
|
||||
/vendor/
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> symfony/webpack-encore-bundle ###
|
||||
/node_modules/
|
||||
/public/build/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
###< symfony/webpack-encore-bundle ###
|
||||
|
12
assets/app.js
Normal file
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Welcome to your app's main JavaScript file!
|
||||
*
|
||||
* We recommend including the built version of this JavaScript file
|
||||
* (and its CSS file) in your base layout (base.html.twig).
|
||||
*/
|
||||
|
||||
// any CSS you import will output into a single css file (app.css in this case)
|
||||
import './styles/app.css';
|
||||
|
||||
// start the Stimulus application
|
||||
import './bootstrap';
|
11
assets/bootstrap.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
import { startStimulusApp } from '@symfony/stimulus-bridge';
|
||||
|
||||
// Registers Stimulus controllers from controllers.json and in the controllers/ directory
|
||||
export const app = startStimulusApp(require.context(
|
||||
'@symfony/stimulus-bridge/lazy-controller-loader!./controllers',
|
||||
true,
|
||||
/\.(j|t)sx?$/
|
||||
));
|
||||
|
||||
// register any custom, 3rd party controllers here
|
||||
// app.register('some_controller_name', SomeImportedController);
|
4
assets/controllers.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"controllers": [],
|
||||
"entrypoints": []
|
||||
}
|
16
assets/controllers/hello_controller.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { Controller } from 'stimulus';
|
||||
|
||||
/*
|
||||
* This is an example Stimulus controller!
|
||||
*
|
||||
* Any element with a data-controller="hello" attribute will cause
|
||||
* this controller to be executed. The name "hello" comes from the filename:
|
||||
* hello_controller.js -> "hello"
|
||||
*
|
||||
* Delete this file or adapt it for your use!
|
||||
*/
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js';
|
||||
}
|
||||
}
|
15
assets/images/24unix/24_logo.svg
Normal file
After Width: | Height: | Size: 153 KiB |
17
assets/images/24unix/24_logo_bg.svg
Normal file
After Width: | Height: | Size: 154 KiB |
BIN
assets/images/24unix/24_logo_bg_64x64.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
assets/images/24unix/24_logo_bg_96x96.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
assets/images/Settings/root_screen.png
Normal file
After Width: | Height: | Size: 453 KiB |
BIN
assets/images/Settings/root_screen_original.png
Normal file
After Width: | Height: | Size: 1.6 MiB |
BIN
assets/images/Settings/user_screen.png
Normal file
After Width: | Height: | Size: 431 KiB |
BIN
assets/images/Settings/user_screen_original.png
Normal file
After Width: | Height: | Size: 1.7 MiB |
BIN
assets/images/Settings/vi_screen.png
Normal file
After Width: | Height: | Size: 309 KiB |
BIN
assets/images/Settings/vi_screen_original.png
Normal file
After Width: | Height: | Size: 5.9 MiB |
BIN
assets/images/Spookie/16x16.png
Normal file
After Width: | Height: | Size: 540 B |
BIN
assets/images/Spookie/32x32.png
Normal file
After Width: | Height: | Size: 960 B |
BIN
assets/images/Spookie/48x48.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
8
assets/images/Spookie/Spookie.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.0" width="1024.000000pt" height="1024.000000pt" viewBox="0 0 1024.000000 1024.000000" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" xmlns:bx="https://boxy-svg.com">
|
||||
<g transform="matrix(0.1, 0, 0, -0.1, -6.266793, 1030.994141)" fill="#000000" stroke="none">
|
||||
<path d="M 1568.308 10309.078 C 564.548 10309.078 62.668 9803.508 62.668 8792.368 C 62.668 8339.535 62.668 1727.029 62.668 1714.388 C 62.668 703.248 564.548 197.678 1568.308 197.678 C 1591.463 197.678 7590.868 197.678 8594.628 197.678 C 9598.388 197.678 10100.268 703.248 10100.268 1714.388 C 10100.268 2725.528 10100.268 6486.982 10100.268 8792.368 C 10100.268 9803.508 9598.388 10309.078 8594.628 10309.078 C 5583.348 10309.078 1568.308 10309.078 1568.308 10309.078 Z M 3672.54 7275.658 C 4244.339 8047.825 5331.794 8472.407 6085.228 8392.819 C 7558.724 8237.167 7711.517 7222.701 7669.179 6891 C 7601.342 6359.52 7261.927 6120.025 7215.766 6046.83 C 7054.424 5790.996 7112.956 5590 7129.487 5573 C 7140.184 5562 7284.674 5550.452 7381.343 5616 C 7983.187 6016.375 8092.748 6264.518 8434.473 6410 C 9054.145 6673.811 9255.358 5797.308 9262.001 5720 C 9313.839 5116.734 9046.297 5272.999 8804.116 5495.788 C 8923.874 4704.112 8630.922 4160.925 8514.503 4156.928 C 8067.033 4141.565 7890.303 4557.157 7878.388 4495.295 C 7651.349 3316.524 6684.687 4203.602 6680.894 4195 C 6408.836 3577.95 6794.683 3431.998 6849.431 3394 C 7072.287 3239.325 7829.534 3522.262 7901.588 3527 C 8353.345 3556.705 8404.386 3093.747 8337.202 2842.622 C 8300.167 2704.19 8116.008 3110.391 8098.694 3056.379 C 7905.831 2454.74 7286.79 1799.512 5252.72 1970 C 2634.673 2189.435 3169.554 3982.907 3145.643 3965.989 C 2508.377 3515.099 2335 4324.288 2233.361 4750 C 2228.393 4770.808 2005.604 4213.617 1673.29 4210.931 C 1340.976 4208.245 1180.756 5173.068 1418.474 5904 C 1278.78 5813.12 1110.84 5657.594 908.927 5784 C 768.062 5872.188 787.568 6952.627 1704.288 7206 C 1828.193 7240.246 2045.681 7168.896 2123.478 7041 C 2259.345 6817.638 2477.148 6490.068 2526.059 6431 C 2665.475 6262.634 3023.704 6040.791 3220.366 6417 C 3444.851 6846.434 3309.293 6770.088 3672.54 7275.658 Z" style="stroke: rgb(218, 85, 85); fill: #ff8040;"/>
|
||||
<path d="M 4218 7190 C 3843.862 7161.67 3684.337 6798.411 3725.003 6362.026 C 3760.674 5979.24 4109.523 6050.046 4179.282 6064.14 C 4462.744 6121.409 4624.226 6383.444 4672.836 6857.022 C 4686.564 6990.762 4563.698 7216.176 4218 7190 Z" style="stroke: rgb(218, 85, 85); fill: #ff8040;"/>
|
||||
<path d="M 5340 6904 C 5231.393 6881.963 5107.694 6745.696 4939 6400 C 4867.75 6253.991 4906.668 6046.567 4987 5945 C 5126.502 5768.621 5353.992 5737.6 5564.869 5909.852 C 5636.594 5968.439 5788.19 6124.729 5832.272 6408.204 C 5894.908 6810.991 5662.601 6969.457 5340 6904 Z" style="stroke: rgb(218, 85, 85); fill: #ff8040;"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
BIN
assets/images/Spookie/spookie_64x64.png
Normal file
After Width: | Height: | Size: 1012 B |
BIN
assets/images/Spookie/spooky.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
assets/images/Spookie/spooky_16x16.png
Normal file
After Width: | Height: | Size: 528 B |
BIN
assets/images/Spookie/spooky_32x32.png
Normal file
After Width: | Height: | Size: 987 B |
BIN
assets/images/alien-profile.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
assets/images/asteroid.jpeg
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
assets/images/bg.jpeg
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/images/meteor-shower.jpg
Normal file
After Width: | Height: | Size: 24 KiB |
12
assets/js/app.js
Normal file
@ -0,0 +1,12 @@
|
||||
// assets/js/app.js
|
||||
|
||||
|
||||
import '../styles/app.scss';
|
||||
|
||||
// import $ from 'jquery';
|
||||
// global.$ = $;
|
||||
|
||||
import 'bootstrap';
|
||||
|
||||
require('@fortawesome/fontawesome-free/css/all.min.css');
|
||||
require('@fortawesome/fontawesome-free/js/all.js');
|
3
assets/styles/app.css
Normal file
@ -0,0 +1,3 @@
|
||||
body {
|
||||
background-color: lightgray;
|
||||
}
|
185
assets/styles/app.scss
Normal file
@ -0,0 +1,185 @@
|
||||
/*
|
||||
jet black (tiefschwarz) - RAL 9005: #0e0e10, rgba(14, 14, 16, 1.0);
|
||||
mango: #ff8040, rgba(255, 130, 67, 1.0);
|
||||
gray: #a1a1a1, rgba(161, 161, 161, 1.0)
|
||||
*/
|
||||
|
||||
/*
|
||||
@media (prefers-color-scheme: light) {}
|
||||
@media (prefers-color-scheme: dark) {}
|
||||
*/
|
||||
|
||||
/* debug */
|
||||
* {
|
||||
border: 0 solid gray;
|
||||
}
|
||||
|
||||
// customize some Bootstrap variables
|
||||
|
||||
|
||||
$primary: #FF8040;
|
||||
$body-bg: #0E0E10;
|
||||
$body-color: darken(white, 20);
|
||||
|
||||
// the ~ allows you to reference things in node_modules
|
||||
@import "~bootstrap/scss/bootstrap";
|
||||
|
||||
body {
|
||||
padding-top: 95px;
|
||||
padding-bottom: 155px;
|
||||
}
|
||||
|
||||
.row.content {
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
.col-center {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.sidenav-left {
|
||||
padding-top: 20px;
|
||||
background-color: #f1f1f1;
|
||||
height: 100%;
|
||||
margin-left: 3em;
|
||||
}
|
||||
|
||||
.sidenav-right {
|
||||
padding-top: 20px;
|
||||
background-color: #f1f1f1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.sidenav {
|
||||
height: auto;
|
||||
padding: 15px;
|
||||
}
|
||||
.row.content {height:auto;}
|
||||
}
|
||||
|
||||
|
||||
.navbar-top {
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: #FF8040;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.navbar-bottom {
|
||||
border-top-width: 1px;
|
||||
border-top-color: #FF8040;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
height: 125px;
|
||||
opacity: 0.9;
|
||||
|
||||
}
|
||||
|
||||
.navbar-nav > li > .dropdown-menu {
|
||||
background-color: #A1A1A1;
|
||||
}
|
||||
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
.dropdown-toggle:after {
|
||||
content: none;
|
||||
}
|
||||
#dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
.navbar {
|
||||
padding-left: 100px;
|
||||
padding-right: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.button-login {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.box {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: #ff8040;
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
background-image: url('../images/bg.jpeg');
|
||||
background-position: center;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
/* BlogPosts */
|
||||
|
||||
.main-article {
|
||||
border: 2px solid #efefee;
|
||||
Background: #fff;
|
||||
border-top-left-radius: 6px;
|
||||
border-top-right-radius: 6px;
|
||||
}
|
||||
|
||||
.main-article img {
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
border-top-right-radius: 5px;
|
||||
border-top-left-radius: 5px;
|
||||
border-top: 5px solid lightblue;
|
||||
}
|
||||
|
||||
.blog-container {
|
||||
border: 1px solid #FF8040;
|
||||
border-radius: 5px;
|
||||
background: #0E0E10;
|
||||
margin-bottom: 25px;
|
||||
background-image: url('../images/bg.jpeg');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.main-article-link, .article-container a {
|
||||
text-decoration: none;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.main-article-link:hover {
|
||||
text-decoration: none;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.article-title {
|
||||
min-width: 300px;
|
||||
|
||||
}
|
||||
|
||||
@media (max-width: 440px) {
|
||||
.article-title {
|
||||
min-width: 100px;
|
||||
max-width: 245px;
|
||||
}
|
||||
}
|
||||
|
||||
.blog-img {
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
border-radius: 5px;
|
||||
margin: 7px;
|
||||
}
|
||||
|
||||
.article-author-img {
|
||||
height: 25px;
|
||||
border: 1px solid darkgray;
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
.blog-details {
|
||||
font-size: .8em;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.blog-teaser {
|
||||
margin-left: 15px;
|
||||
}
|
@ -13,22 +13,27 @@
|
||||
"doctrine/doctrine-migrations-bundle": "^3.1",
|
||||
"doctrine/orm": "^2.9",
|
||||
"easycorp/easyadmin-bundle": "^3",
|
||||
"symfony/console": "5.2.*",
|
||||
"symfony/dotenv": "5.2.*",
|
||||
"knplabs/knp-time-bundle": "^1.16",
|
||||
"symfony/console": "5.3.*",
|
||||
"symfony/dotenv": "5.3.*",
|
||||
"symfony/flex": "^1.3.1",
|
||||
"symfony/framework-bundle": "5.2.*",
|
||||
"symfony/proxy-manager-bridge": "5.2.*",
|
||||
"symfony/security-bundle": "5.2.*",
|
||||
"symfony/framework-bundle": "5.3.*",
|
||||
"symfony/mailer": "5.3.*",
|
||||
"symfony/proxy-manager-bridge": "5.3.*",
|
||||
"symfony/security-bundle": "5.3.*",
|
||||
"symfony/twig-bundle": "^5.2",
|
||||
"symfony/validator": "5.2.*",
|
||||
"symfony/yaml": "5.2.*",
|
||||
"symfony/validator": "5.3.*",
|
||||
"symfony/webpack-encore-bundle": "^1.11",
|
||||
"symfony/yaml": "5.3.*",
|
||||
"symfonycasts/verify-email-bundle": "^1.5",
|
||||
"twig/extra-bundle": "^2.12|^3.0",
|
||||
"twig/intl-extra": "^3.3",
|
||||
"twig/twig": "^2.12|^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/maker-bundle": "^1.31",
|
||||
"symfony/stopwatch": "^5.2",
|
||||
"symfony/web-profiler-bundle": "^5.2"
|
||||
"symfony/stopwatch": "^5.3",
|
||||
"symfony/web-profiler-bundle": "^5.3"
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
@ -70,7 +75,7 @@
|
||||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "5.2.*"
|
||||
"require": "5.3.*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1359
composer.lock
generated
@ -10,4 +10,7 @@ return [
|
||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||
EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle::class => ['all' => true],
|
||||
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
||||
SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle::class => ['all' => true],
|
||||
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
|
||||
Knp\Bundle\TimeBundle\KnpTimeBundle::class => ['all' => true],
|
||||
];
|
||||
|
3
config/packages/assets.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
framework:
|
||||
assets:
|
||||
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
|
3
config/packages/mailer.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
framework:
|
||||
mailer:
|
||||
dsn: '%env(MAILER_DSN)%'
|
4
config/packages/prod/webpack_encore.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
#webpack_encore:
|
||||
# Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
|
||||
# Available in version 1.2
|
||||
#cache: true
|
2
config/packages/test/webpack_encore.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
#webpack_encore:
|
||||
# strict_mode: false
|
30
config/packages/webpack_encore.yaml
Normal file
@ -0,0 +1,30 @@
|
||||
webpack_encore:
|
||||
# The path where Encore is building the assets - i.e. Encore.setOutputPath()
|
||||
output_path: '%kernel.project_dir%/public/build'
|
||||
# If multiple builds are defined (as shown below), you can disable the default build:
|
||||
# output_path: false
|
||||
|
||||
# Set attributes that will be rendered on all script and link tags
|
||||
script_attributes:
|
||||
defer: true
|
||||
# link_attributes:
|
||||
|
||||
# If using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials')
|
||||
# crossorigin: 'anonymous'
|
||||
|
||||
# Preload all rendered script and link tags automatically via the HTTP/2 Link header
|
||||
# preload: true
|
||||
|
||||
# Throw an exception if the entrypoints.json file is missing or an entry is missing from the data
|
||||
# strict_mode: false
|
||||
|
||||
# If you have multiple builds:
|
||||
# builds:
|
||||
# pass "frontend" as the 3rg arg to the Twig functions
|
||||
# {{ encore_entry_script_tags('entry1', null, 'frontend') }}
|
||||
|
||||
# frontend: '%kernel.project_dir%/public/frontend/build'
|
||||
|
||||
# Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
|
||||
# Put in config/packages/prod/webpack_encore.yaml
|
||||
# cache: true
|
31
migrations/Version20210530183330.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?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 Version20210530183330 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 is_verified TINYINT(1) NOT 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 is_verified');
|
||||
}
|
||||
}
|
31
migrations/Version20210601114523.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?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 Version20210601114523 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 blog ADD slug VARCHAR(255) NOT NULL, CHANGE title title VARCHAR(255) DEFAULT NULL, CHANGE content content LONGTEXT DEFAULT NULL, CHANGE created_at created_at DATETIME DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE blog DROP slug, CHANGE title title VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, CHANGE content content LONGTEXT CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, CHANGE created_at created_at DATETIME NOT NULL');
|
||||
}
|
||||
}
|
27
package.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@symfony/stimulus-bridge": "^2.0.0",
|
||||
"@symfony/webpack-encore": "^1.0.0",
|
||||
"core-js": "^3.0.0",
|
||||
"file-loader": "^6.0.0",
|
||||
"regenerator-runtime": "^0.13.2",
|
||||
"sass": "^1.34.0",
|
||||
"sass-loader": "^11.0.0",
|
||||
"stimulus": "^2.0.0",
|
||||
"webpack-notifier": "^1.6.0"
|
||||
},
|
||||
"license": "UNLICENSED",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev-server": "encore dev-server",
|
||||
"dev": "encore dev",
|
||||
"watch": "encore dev --watch",
|
||||
"build": "encore production --progress"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.15.3",
|
||||
"@popperjs/core": "^2.9.2",
|
||||
"bootstrap": "^5.0.1",
|
||||
"copy-webpack-plugin": "^9.0.0"
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ class BlogCrudController extends AbstractCrudController
|
||||
AssociationField::new('author')
|
||||
->autocomplete(),
|
||||
TextField::new('title'),
|
||||
TextField::new('slug'),
|
||||
TextEditorField::new('teaser'),
|
||||
TextEditorField::new('content'),
|
||||
DateTimeField::new('createdAt'),
|
||||
|
@ -29,11 +29,11 @@ class BlogController extends AbstractController
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
#[Route('/blog/{id}', name: 'blog')]
|
||||
public function show($id, BlogRepository $blogRepository): Response
|
||||
#[Route('/blog/{slug}', name: 'blog')]
|
||||
public function show($slug, BlogRepository $blogRepository): Response
|
||||
{
|
||||
return $this->render('blog/show.html.twig', [
|
||||
'blog' => $blogRepository->findOneBy(['id' => $id])
|
||||
'blog' => $blogRepository->findOneBy(['slug' => $slug])
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
94
src/Controller/RegistrationController.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Form\RegistrationFormType;
|
||||
use App\Security\EmailVerifier;
|
||||
use App\Repository\UserRepository;
|
||||
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
|
||||
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
|
||||
|
||||
class RegistrationController extends AbstractController
|
||||
{
|
||||
private $emailVerifier;
|
||||
|
||||
public function __construct(EmailVerifier $emailVerifier)
|
||||
{
|
||||
$this->emailVerifier = $emailVerifier;
|
||||
}
|
||||
|
||||
#[Route('/register', name: 'app_register')]
|
||||
public function register(Request $request, UserPasswordEncoderInterface $passwordEncoder): Response
|
||||
{
|
||||
$user = new User();
|
||||
$form = $this->createForm(RegistrationFormType::class, $user);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
// encode the plain password
|
||||
$user->setPassword(
|
||||
$passwordEncoder->encodePassword(
|
||||
$user,
|
||||
$form->get('plainPassword')->getData()
|
||||
)
|
||||
);
|
||||
|
||||
$entityManager = $this->getDoctrine()->getManager();
|
||||
$entityManager->persist($user);
|
||||
$entityManager->flush();
|
||||
|
||||
// generate a signed url and email it to the user
|
||||
$this->emailVerifier->sendEmailConfirmation('app_verify_email', $user,
|
||||
(new TemplatedEmail())
|
||||
->from(new Address('tracer@24unix.net', '24unix'))
|
||||
->to($user->getEmail())
|
||||
->subject('Please Confirm your Email')
|
||||
->htmlTemplate('registration/confirmation_email.html.twig')
|
||||
);
|
||||
// do anything else you need here, like send an email
|
||||
|
||||
return $this->redirectToRoute('blogs');
|
||||
}
|
||||
|
||||
return $this->render('registration/register.html.twig', [
|
||||
'registrationForm' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/verify/email', name: 'app_verify_email')]
|
||||
public function verifyUserEmail(Request $request, UserRepository $userRepository): Response
|
||||
{
|
||||
$id = $request->get('id');
|
||||
|
||||
if (null === $id) {
|
||||
return $this->redirectToRoute('app_register');
|
||||
}
|
||||
|
||||
$user = $userRepository->find($id);
|
||||
|
||||
if (null === $user) {
|
||||
return $this->redirectToRoute('app_register');
|
||||
}
|
||||
|
||||
// validate email confirmation link, sets User::isVerified=true and persists
|
||||
try {
|
||||
$this->emailVerifier->handleEmailConfirmation($request, $user);
|
||||
} catch (VerifyEmailExceptionInterface $exception) {
|
||||
$this->addFlash('verify_email_error', $exception->getReason());
|
||||
|
||||
return $this->redirectToRoute('app_register');
|
||||
}
|
||||
|
||||
// @TODO Change the redirect on success and handle or remove the flash message in your templates
|
||||
$this->addFlash('success', 'Your email address has been verified.');
|
||||
|
||||
return $this->redirectToRoute('app_register');
|
||||
}
|
||||
}
|
@ -6,9 +6,12 @@ use App\Repository\BlogRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use JetBrains\PhpStorm\Pure;
|
||||
use App\Repository\SectionRepository;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass=BlogRepository::class)
|
||||
* @ORM\HasLifecycleCallbacks()
|
||||
*/
|
||||
class Blog
|
||||
{
|
||||
@ -22,28 +25,28 @@ class Blog
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
private $title;
|
||||
private ?string $title;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=true)
|
||||
*/
|
||||
private $teaser;
|
||||
private ?string $teaser;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
*/
|
||||
private $teaserImage;
|
||||
private ?string $teaserImage;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text")
|
||||
*/
|
||||
private $content;
|
||||
private ?string $content;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=User::class, inversedBy="blogs")
|
||||
* @ORM\JoinColumn(nullable=false)
|
||||
*/
|
||||
private $author;
|
||||
private ?User $author;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity=Section::class, inversedBy="blogs")
|
||||
@ -53,17 +56,17 @@ class Blog
|
||||
/**
|
||||
* @ORM\Column(type="datetime")
|
||||
*/
|
||||
private $createdAt;
|
||||
private ?\DateTimeInterface $createdAt;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime", nullable=true)
|
||||
*/
|
||||
private $editedAt;
|
||||
private ?\DateTimeInterface $editedAt;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=User::class)
|
||||
*/
|
||||
private $editedBy;
|
||||
private ?User $editedBy;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
@ -75,16 +78,25 @@ class Blog
|
||||
*/
|
||||
private $comments;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
private $slug;
|
||||
|
||||
#[Pure]
|
||||
public function __construct()
|
||||
{
|
||||
$this->section = new ArrayCollection();
|
||||
$this->comments = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
|
||||
public function getId(): ?int
|
||||
@ -253,4 +265,16 @@ class Blog
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSlug(): ?string
|
||||
{
|
||||
return $this->slug;
|
||||
}
|
||||
|
||||
public function setSlug(string $slug): self
|
||||
{
|
||||
$this->slug = $slug;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,12 @@ use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use JetBrains\PhpStorm\Pure;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass=UserRepository::class)
|
||||
* @UniqueEntity(fields={"username"}, message="There is already an account with this username")
|
||||
*/
|
||||
class User implements UserInterface
|
||||
{
|
||||
@ -71,22 +73,27 @@ class User implements UserInterface
|
||||
* @ORM\OneToMany(targetEntity=Comment::class, mappedBy="author")
|
||||
*/
|
||||
private $comments;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
private $isVerified = false;
|
||||
|
||||
#[Pure] public function __construct()
|
||||
{
|
||||
$this->blogs = new ArrayCollection();
|
||||
$this->comments = new ArrayCollection();
|
||||
}
|
||||
{
|
||||
$this->blogs = new ArrayCollection();
|
||||
$this->comments = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* A visual identifier that represents this user.
|
||||
@ -94,50 +101,50 @@ class User implements UserInterface
|
||||
* @see UserInterface
|
||||
*/
|
||||
public function getUsername(): string
|
||||
{
|
||||
return (string)$this->username;
|
||||
}
|
||||
{
|
||||
return (string)$this->username;
|
||||
}
|
||||
|
||||
public function setUsername(string $username): self
|
||||
{
|
||||
$this->username = $username;
|
||||
|
||||
return $this;
|
||||
}
|
||||
{
|
||||
$this->username = $username;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserInterface
|
||||
*/
|
||||
public function getRoles(): array
|
||||
{
|
||||
$roles = $this->roles;
|
||||
// guarantee every user at least has ROLE_USER
|
||||
$roles[] = 'ROLE_USER';
|
||||
|
||||
return array_unique($roles);
|
||||
}
|
||||
{
|
||||
$roles = $this->roles;
|
||||
// guarantee every user at least has ROLE_USER
|
||||
$roles[] = 'ROLE_USER';
|
||||
|
||||
return array_unique($roles);
|
||||
}
|
||||
|
||||
public function setRoles(array $roles): self
|
||||
{
|
||||
$this->roles = $roles;
|
||||
|
||||
return $this;
|
||||
}
|
||||
{
|
||||
$this->roles = $roles;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserInterface
|
||||
*/
|
||||
public function getPassword(): string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function setPassword(string $password): self
|
||||
{
|
||||
$this->password = $password;
|
||||
|
||||
return $this;
|
||||
}
|
||||
{
|
||||
$this->password = $password;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returning a salt is only needed, if you are not using a modern
|
||||
@ -146,136 +153,148 @@ class User implements UserInterface
|
||||
* @see UserInterface
|
||||
*/
|
||||
public function getSalt(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserInterface
|
||||
*/
|
||||
public function eraseCredentials()
|
||||
{
|
||||
// If you store any temporary, sensitive data on the user, clear it here
|
||||
// $this->plainPassword = null;
|
||||
}
|
||||
{
|
||||
// If you store any temporary, sensitive data on the user, clear it here
|
||||
// $this->plainPassword = null;
|
||||
}
|
||||
|
||||
public function getFirstName(): ?string
|
||||
{
|
||||
return $this->firstName;
|
||||
}
|
||||
{
|
||||
return $this->firstName;
|
||||
}
|
||||
|
||||
public function setFirstName(?string $firstName): self
|
||||
{
|
||||
$this->firstName = $firstName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
{
|
||||
$this->firstName = $firstName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLastName(): ?string
|
||||
{
|
||||
return $this->lastName;
|
||||
}
|
||||
{
|
||||
return $this->lastName;
|
||||
}
|
||||
|
||||
public function setLastName(?string $lastName): self
|
||||
{
|
||||
$this->lastName = $lastName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
{
|
||||
$this->lastName = $lastName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function setEmail(string $email): self
|
||||
{
|
||||
$this->email = $email;
|
||||
|
||||
return $this;
|
||||
}
|
||||
{
|
||||
$this->email = $email;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function setCreatedAt(\DateTimeInterface $createdAt): self
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLastLoginAt(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->lastLoginAt;
|
||||
}
|
||||
{
|
||||
return $this->lastLoginAt;
|
||||
}
|
||||
|
||||
public function setLastLoginAt(?\DateTimeInterface $lastLoginAt): self
|
||||
{
|
||||
$this->lastLoginAt = $lastLoginAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
{
|
||||
$this->lastLoginAt = $lastLoginAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection|Blog[]
|
||||
*/
|
||||
public function getBlogs(): Collection
|
||||
{
|
||||
return $this->blogs;
|
||||
}
|
||||
{
|
||||
return $this->blogs;
|
||||
}
|
||||
|
||||
public function addBlog(Blog $blog): self
|
||||
{
|
||||
if (!$this->blogs->contains($blog)) {
|
||||
$this->blogs[] = $blog;
|
||||
$blog->setAuthor($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
{
|
||||
if (!$this->blogs->contains($blog)) {
|
||||
$this->blogs[] = $blog;
|
||||
$blog->setAuthor($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeBlog(Blog $blog): self
|
||||
{
|
||||
if ($this->blogs->removeElement($blog)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($blog->getAuthor() === $this) {
|
||||
$blog->setAuthor(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
{
|
||||
if ($this->blogs->removeElement($blog)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($blog->getAuthor() === $this) {
|
||||
$blog->setAuthor(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection|Comment[]
|
||||
*/
|
||||
public function getComments(): Collection
|
||||
{
|
||||
return $this->comments;
|
||||
}
|
||||
{
|
||||
return $this->comments;
|
||||
}
|
||||
|
||||
public function addComment(Comment $comment): self
|
||||
{
|
||||
if (!$this->comments->contains($comment)) {
|
||||
$this->comments[] = $comment;
|
||||
$comment->setAuthor($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
{
|
||||
if (!$this->comments->contains($comment)) {
|
||||
$this->comments[] = $comment;
|
||||
$comment->setAuthor($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeComment(Comment $comment): self
|
||||
{
|
||||
if ($this->comments->removeElement($comment)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($comment->getAuthor() === $this) {
|
||||
$comment->setAuthor(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
{
|
||||
if ($this->comments->removeElement($comment)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($comment->getAuthor() === $this) {
|
||||
$comment->setAuthor(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isVerified(): bool
|
||||
{
|
||||
return $this->isVerified;
|
||||
}
|
||||
|
||||
public function setIsVerified(bool $isVerified): self
|
||||
{
|
||||
$this->isVerified = $isVerified;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
55
src/Form/RegistrationFormType.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?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\PasswordType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\IsTrue;
|
||||
use Symfony\Component\Validator\Constraints\Length;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
|
||||
class RegistrationFormType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('username')
|
||||
->add('agreeTerms', CheckboxType::class, [
|
||||
'mapped' => false,
|
||||
'constraints' => [
|
||||
new IsTrue([
|
||||
'message' => 'You should agree to our terms.',
|
||||
]),
|
||||
],
|
||||
])
|
||||
->add('plainPassword', PasswordType::class, [
|
||||
// instead of being set onto the object directly,
|
||||
// this is read and encoded in the controller
|
||||
'mapped' => false,
|
||||
'attr' => ['autocomplete' => 'new-password'],
|
||||
'constraints' => [
|
||||
new NotBlank([
|
||||
'message' => 'Please enter a password',
|
||||
]),
|
||||
new Length([
|
||||
'min' => 6,
|
||||
'minMessage' => 'Your password should be at least {{ limit }} characters',
|
||||
// max length allowed by Symfony for security reasons
|
||||
'max' => 4096,
|
||||
]),
|
||||
],
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => User::class,
|
||||
]);
|
||||
}
|
||||
}
|
66
src/Security/EmailVerifier.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Security;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
|
||||
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
|
||||
* @package App\Security
|
||||
*/
|
||||
class EmailVerifier
|
||||
{
|
||||
private VerifyEmailHelperInterface $verifyEmailHelper;
|
||||
private MailerInterface $mailer;
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
public function __construct(VerifyEmailHelperInterface $helper, MailerInterface $mailer, EntityManagerInterface $manager)
|
||||
{
|
||||
$this->verifyEmailHelper = $helper;
|
||||
$this->mailer = $mailer;
|
||||
$this->entityManager = $manager;
|
||||
}
|
||||
|
||||
public function sendEmailConfirmation(string $verifyEmailRouteName, UserInterface $user, TemplatedEmail $email): void
|
||||
{
|
||||
$signatureComponents = $this->verifyEmailHelper->generateSignature(
|
||||
$verifyEmailRouteName,
|
||||
$user->getId(),
|
||||
$user->getEmail(),
|
||||
['id' => $user->getId()]
|
||||
);
|
||||
|
||||
$context = $email->getContext();
|
||||
$context['signedUrl'] = $signatureComponents->getSignedUrl();
|
||||
$context['expiresAtMessageKey'] = $signatureComponents->getExpirationMessageKey();
|
||||
$context['expiresAtMessageData'] = $signatureComponents->getExpirationMessageData();
|
||||
|
||||
$email->context($context);
|
||||
|
||||
try {
|
||||
$this->mailer->send($email);
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
die("Error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws VerifyEmailExceptionInterface
|
||||
*/
|
||||
public function handleEmailConfirmation(Request $request, UserInterface $user): void
|
||||
{
|
||||
$this->verifyEmailHelper->validateEmailConfirmation($request->getUri(), $user->getId(), $user->getEmail());
|
||||
|
||||
$user->setIsVerified(true);
|
||||
|
||||
$this->entityManager->persist($user);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
}
|
58
symfony.lock
@ -91,9 +91,15 @@
|
||||
"ref": "b131e6cbfe1b898a508987851963fff485986285"
|
||||
}
|
||||
},
|
||||
"egulias/email-validator": {
|
||||
"version": "3.1.1"
|
||||
},
|
||||
"friendsofphp/proxy-manager-lts": {
|
||||
"version": "v1.0.5"
|
||||
},
|
||||
"knplabs/knp-time-bundle": {
|
||||
"version": "v1.16.0"
|
||||
},
|
||||
"laminas/laminas-code": {
|
||||
"version": "4.3.0"
|
||||
},
|
||||
@ -216,6 +222,18 @@
|
||||
"symfony/intl": {
|
||||
"version": "v5.2.7"
|
||||
},
|
||||
"symfony/mailer": {
|
||||
"version": "4.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "master",
|
||||
"version": "4.3",
|
||||
"ref": "15658c2a0176cda2e7dba66276a2030b52bd81b2"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/mailer.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/maker-bundle": {
|
||||
"version": "1.0",
|
||||
"recipe": {
|
||||
@ -225,18 +243,27 @@
|
||||
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
|
||||
}
|
||||
},
|
||||
"symfony/mime": {
|
||||
"version": "v5.2.9"
|
||||
},
|
||||
"symfony/options-resolver": {
|
||||
"version": "v5.2.4"
|
||||
},
|
||||
"symfony/orm-pack": {
|
||||
"version": "v2.1.0"
|
||||
},
|
||||
"symfony/password-hasher": {
|
||||
"version": "v5.3.0"
|
||||
},
|
||||
"symfony/polyfill-intl-grapheme": {
|
||||
"version": "v1.23.0"
|
||||
},
|
||||
"symfony/polyfill-intl-icu": {
|
||||
"version": "v1.23.0"
|
||||
},
|
||||
"symfony/polyfill-intl-idn": {
|
||||
"version": "v1.23.0"
|
||||
},
|
||||
"symfony/polyfill-intl-normalizer": {
|
||||
"version": "v1.23.0"
|
||||
},
|
||||
@ -249,6 +276,9 @@
|
||||
"symfony/polyfill-php80": {
|
||||
"version": "v1.23.0"
|
||||
},
|
||||
"symfony/polyfill-php81": {
|
||||
"version": "v1.23.0"
|
||||
},
|
||||
"symfony/polyfill-uuid": {
|
||||
"version": "v1.23.0"
|
||||
},
|
||||
@ -383,12 +413,40 @@
|
||||
"config/routes/dev/web_profiler.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/webpack-encore-bundle": {
|
||||
"version": "1.9",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "master",
|
||||
"version": "1.9",
|
||||
"ref": "9399a0bfc6ee7a0c019f952bca46d6a6045dd451"
|
||||
},
|
||||
"files": [
|
||||
"assets/app.js",
|
||||
"assets/bootstrap.js",
|
||||
"assets/controllers.json",
|
||||
"assets/controllers/hello_controller.js",
|
||||
"assets/styles/app.css",
|
||||
"config/packages/assets.yaml",
|
||||
"config/packages/prod/webpack_encore.yaml",
|
||||
"config/packages/test/webpack_encore.yaml",
|
||||
"config/packages/webpack_encore.yaml",
|
||||
"package.json",
|
||||
"webpack.config.js"
|
||||
]
|
||||
},
|
||||
"symfony/yaml": {
|
||||
"version": "v5.2.9"
|
||||
},
|
||||
"symfonycasts/verify-email-bundle": {
|
||||
"version": "v1.5.0"
|
||||
},
|
||||
"twig/extra-bundle": {
|
||||
"version": "v3.3.1"
|
||||
},
|
||||
"twig/intl-extra": {
|
||||
"version": "v3.3.0"
|
||||
},
|
||||
"twig/twig": {
|
||||
"version": "v3.3.2"
|
||||
}
|
||||
|
10
templates/_footer.html.twig
Normal file
@ -0,0 +1,10 @@
|
||||
<footer class="footer">
|
||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-bottom navbar-bottom">
|
||||
<div id="legal">
|
||||
</div>
|
||||
<div class="powered">
|
||||
powered by <a href="#"><img src="{{ asset('build/images/Spookie/spookie_64x64.png') }}" alt="Spookie"></a>
|
||||
</div>
|
||||
</nav>
|
||||
</footer>
|
||||
|
53
templates/_header.html.twig
Normal file
@ -0,0 +1,53 @@
|
||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top navbar-top">
|
||||
|
||||
<a class="navbar-brand" href="{{ path('blogs') }}">
|
||||
<img src="{{ asset('build/images/24unix/24_logo_bg_64x64.png') }}" alt="24unix.net" id="site-logo">
|
||||
</a>
|
||||
|
||||
<button class="navbar-toggler border-0" type="button" data-toggle="collapse" data-target="#CollapsingNavbar">
|
||||
☰
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="CollapsingNavbar">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
{% if is_granted('ROLE_USER') %}
|
||||
<li class="nav-item dropdown my-2 my-lg-0">
|
||||
<button type="button" id="navbar-dropdown" data-toggle="dropdown"
|
||||
class="btn btn-primary dropdown-toggle ml-auto button-login">
|
||||
{{ app.user.username }}
|
||||
</button>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right" id="dropdown-menu" aria-labelledby="navbar-dropdown">
|
||||
<a class="dropdown-item" href="{{ path('blogs') }}">
|
||||
<span class="fas fa-user" aria-hidden="true"></span>
|
||||
Profile</a>
|
||||
<a class="dropdown-item" href="#">
|
||||
<span class="fas fa-cog" aria-hidden="true"></span>
|
||||
Settings</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
{% if is_granted('ROLE_ADMIN') %}
|
||||
<a class="dropdown-item" href="{{ path('admin') }}">
|
||||
<span class="fas fa-cog" aria-hidden="true"></span>
|
||||
Administration
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
{% endif %}
|
||||
|
||||
<a class="dropdown-item" href="{{ path('app_logout') }}">
|
||||
<span class="fas fa-sign-out-alt" aria-hidden="true"></span>
|
||||
Logout
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="btn btn-primary button-login" href="{{ path('app_login') }}" role="button" id="buttonLogin">
|
||||
Login
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</nav>
|
@ -1,19 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %}Welcome!{% endblock %}</title>
|
||||
{# Run `composer require symfony/webpack-encore-bundle`
|
||||
and uncomment the following Encore helpers to start using Symfony UX #}
|
||||
<title>{% block title %}Spookie{% endblock %}</title>
|
||||
|
||||
{% block stylesheets %}
|
||||
{#{{ encore_entry_link_tags('app') }}#}
|
||||
{{ encore_entry_link_tags('app') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{#{{ encore_entry_script_tags('app') }}#}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% block body %}{% endblock %}
|
||||
|
||||
{% include '_header.html.twig' %}
|
||||
|
||||
<div class="container-fluid text-center">
|
||||
<div class="row content d-flex justify-content-sm-center">
|
||||
<div class="col-sm-2 sidenav-left box" id="main-menu">
|
||||
<p>
|
||||
<a href="{{ path('blogs') }}">Blogs</a><br>
|
||||
<a href="//git.24unix.net">git.24unix.net</a>
|
||||
<a href="//pastebin.24unix.net">pastebin.24unix.net</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-9 text-left center-block" id="main-content">
|
||||
{% block body %} {% endblock %}
|
||||
</div>
|
||||
|
||||
<div class="col-sm-1 sidenav" id="spacer">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include '_footer.html.twig' %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{ encore_entry_script_tags('app') }}
|
||||
{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,12 +1,38 @@
|
||||
{# templates/blog/blog_show.html.twig #}
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Hello BlogController!{% endblock %}
|
||||
{% block title %} Blogpost {% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{% for blog in blogs %}
|
||||
<h4>{{ blog }}</h4>
|
||||
<p>{{ blog.teaser }}</p>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<!-- blog List -->
|
||||
<div class="col-sm-12">
|
||||
{% for blogpost in blogs %}
|
||||
<div class="blog-container my-4">
|
||||
<a href="{{ path('blog', { slug: blogpost.slug }) }} ">
|
||||
{% if blogpost.teaserImage %}
|
||||
<img class="blog-img" src="{{ asset('build/images/asteroid.jpeg') }}" alt="asteroid">
|
||||
{% endif %}
|
||||
<div class="article-title d-inline-block pl-3 align-middle">
|
||||
<span>{{ blogpost.title }}</span>
|
||||
</div>
|
||||
</a>
|
||||
<br>
|
||||
<span class="align-left blog-details">
|
||||
<img class="article-author-img rounded-circle"
|
||||
src="{{ asset('build/images/alien-profile.png') }}" alt="profile">
|
||||
{{ blogpost.author }}
|
||||
</span>
|
||||
<span class="pl-5 blog-details float-right">{{ blogpost.createdAt|ago }}</span>
|
||||
<br>
|
||||
<span class="blog-teaser">
|
||||
{{ blogpost.teaser }}
|
||||
</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<a href="{{ path('blog', { id: blog.id }) }}">View more …</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
11
templates/registration/confirmation_email.html.twig
Normal file
@ -0,0 +1,11 @@
|
||||
<h1>Hi! Please confirm your email!</h1>
|
||||
|
||||
<p>
|
||||
Please confirm your email address by clicking the following link: <br><br>
|
||||
<a href="{{ signedUrl }}">Confirm my Email</a>.
|
||||
This link will expire in {{ expiresAtMessageKey|trans(expiresAtMessageData, 'VerifyEmailBundle') }}.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Cheers!
|
||||
</p>
|
21
templates/registration/register.html.twig
Normal file
@ -0,0 +1,21 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Register{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{% for flashError in app.flashes('verify_email_error') %}
|
||||
<div class="alert alert-danger" role="alert">{{ flashError }}</div>
|
||||
{% endfor %}
|
||||
|
||||
<h1>Register</h1>
|
||||
|
||||
{{ form_start(registrationForm) }}
|
||||
{{ form_row(registrationForm.username) }}
|
||||
{{ form_row(registrationForm.plainPassword, {
|
||||
label: 'Password'
|
||||
}) }}
|
||||
{{ form_row(registrationForm.agreeTerms) }}
|
||||
|
||||
<button type="submit" class="btn">Register</button>
|
||||
{{ form_end(registrationForm) }}
|
||||
{% endblock %}
|
84
webpack.config.js
Normal file
@ -0,0 +1,84 @@
|
||||
const Encore = require("@symfony/webpack-encore");
|
||||
|
||||
// Manually configure the runtime environment if not already configured yet by the "encore" command.
|
||||
// It's useful when you use tools that rely on webpack.config.js file.
|
||||
if (!Encore.isRuntimeEnvironmentConfigured()) {
|
||||
Encore.configureRuntimeEnvironment(process.env.NODE_ENV || "dev");
|
||||
}
|
||||
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
|
||||
Encore
|
||||
// directory where compiled assets will be stored
|
||||
.setOutputPath("public/build/")
|
||||
.copyFiles({
|
||||
from: "./assets/images",
|
||||
to: "images/[path][name].[ext]"
|
||||
})
|
||||
|
||||
// public path used by the web server to access the output path
|
||||
.setPublicPath("/build")
|
||||
// only needed for CDN's or sub-directory deploy
|
||||
//.setManifestKeyPrefix('build/')
|
||||
|
||||
|
||||
/*
|
||||
* ENTRY CONFIG
|
||||
*
|
||||
* Add 1 entry for each "page" of your app
|
||||
* (including one that's included on every page - e.g. "app")
|
||||
*
|
||||
* Each entry will result in one JavaScript file (e.g. app.js)
|
||||
* and one CSS file (e.g. app.scss) if your JavaScript imports CSS.
|
||||
*/
|
||||
.addEntry("app", "./assets/js/app.js")
|
||||
//.addEntry('page1', './assets/js/page1.js')
|
||||
//.addEntry('page2', './assets/js/page2.js')
|
||||
|
||||
// When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
|
||||
.splitEntryChunks()
|
||||
|
||||
// will require an extra script tag for runtime.js
|
||||
// but, you probably want this, unless you're building a single-page app
|
||||
//.enableSingleRuntimeChunk()
|
||||
.disableSingleRuntimeChunk()
|
||||
|
||||
/*
|
||||
* FEATURE CONFIG
|
||||
*
|
||||
* Enable & configure other features below. For a full
|
||||
* list of features, see:
|
||||
* https://symfony.com/doc/current/frontend.html#adding-more-features
|
||||
*/
|
||||
.cleanupOutputBeforeBuild()
|
||||
.enableBuildNotifications()
|
||||
.enableSourceMaps(!Encore.isProduction())
|
||||
// enables hashed filenames (e.g. app.abc123.css)
|
||||
.enableVersioning(Encore.isProduction())
|
||||
|
||||
// enables @babel/preset-env polyfills
|
||||
.configureBabel(() => {
|
||||
}, {
|
||||
useBuiltIns: "usage",
|
||||
corejs: 3
|
||||
})
|
||||
|
||||
// enables Sass/SCSS support
|
||||
.enableSassLoader()
|
||||
|
||||
// uncomment if you use TypeScript
|
||||
//.enableTypeScriptLoader()
|
||||
|
||||
// uncomment to get integrity="..." attributes on your script & link tags
|
||||
// requires WebpackEncoreBundle 1.4 or higher
|
||||
//.enableIntegrityHashes(Encore.isProduction())
|
||||
|
||||
// uncomment if you're having problems with a jQuery plugin
|
||||
//.autoProvidejQuery()
|
||||
|
||||
// uncomment if you use API Platform Admin (composer req api-admin)
|
||||
//.enableReactPreset()
|
||||
//.addEntry('admin', './assets/js/admin.js')
|
||||
;
|
||||
|
||||
module.exports = Encore.getWebpackConfig();
|