Compare commits
9 Commits
symfony_de
...
master
Author | SHA1 | Date | |
---|---|---|---|
8764e7d0e4 | |||
15ca4a0cf5 | |||
3abe3d2d97 | |||
10078cdf1b | |||
6792bdb079 | |||
a0ecf6de88 | |||
4ecc506693 | |||
0fdd876bc4 | |||
e1d462311f |
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,6 +1,8 @@
|
|||||||
# Comment to include Symfony project
|
# Comment to include Symfony project
|
||||||
#app/*
|
# app
|
||||||
!app/.keep
|
|
||||||
|
|
||||||
# Ignore Database datas
|
# Ignore Database datas
|
||||||
data
|
data
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea
|
||||||
|
16
README.md
16
README.md
@ -56,6 +56,21 @@ Le dépôt utilise plusieurs branches qui peuvent être utilisée selon le point
|
|||||||
* Control migrations: `bin/console doctrine:migrations:status`
|
* Control migrations: `bin/console doctrine:migrations:status`
|
||||||
* ...
|
* ...
|
||||||
|
|
||||||
|
## Yarn
|
||||||
|
|
||||||
|
* installation de webpack-encore: `composer require symfony/webpack-encore-bundle`
|
||||||
|
* pour finir il faut installer et lancer yarn:
|
||||||
|
* un script bash `docker-node.sh` lancera nodejs dans un conteneur quand on a besoin de yarn:
|
||||||
|
* au premier lancement du container node:14, j'initialise la mise en place:
|
||||||
|
* `npm install yarn`
|
||||||
|
* `npm install --force`
|
||||||
|
* puis je lance (ce qu'il faudra faire à chaque fois):
|
||||||
|
* `yarn encore dev` avec éventuellement l'option `--watch`
|
||||||
|
|
||||||
|
## Sass
|
||||||
|
|
||||||
|
* dans le container node, installation de sass: `npm install sass-loader@^11.0.0 sass --save-dev`
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -83,5 +98,4 @@ Le dépôt utilise plusieurs branches qui peuvent être utilisée selon le point
|
|||||||
* To clear volumes: `docker volume rm $(docker volume ls -q)`
|
* To clear volumes: `docker volume rm $(docker volume ls -q)`
|
||||||
* To clear networks: `docker network rm $(docker network ls | tail -n+2 | awk '{if($2 !~ /bridge|none|host/){ print $1 }}')`
|
* To clear networks: `docker network rm $(docker network ls | tail -n+2 | awk '{if($2 !~ /bridge|none|host/){ print $1 }}')`
|
||||||
|
|
||||||
Disclaimer: This project has been generated on phpdocker.io
|
|
||||||
|
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
; top-most EditorConfig file
|
|
||||||
root = true
|
|
||||||
|
|
||||||
; Unix-style newlines
|
|
||||||
[*]
|
|
||||||
end_of_line = LF
|
|
||||||
|
|
||||||
[*.php]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
34
app/.env
34
app/.env
@ -1,34 +0,0 @@
|
|||||||
# In all environments, the following files are loaded if they exist,
|
|
||||||
# the latter taking precedence over the former:
|
|
||||||
#
|
|
||||||
# * .env contains default values for the environment variables needed by the app
|
|
||||||
# * .env.local uncommitted file with local overrides
|
|
||||||
# * .env.$APP_ENV committed environment-specific defaults
|
|
||||||
# * .env.$APP_ENV.local uncommitted environment-specific overrides
|
|
||||||
#
|
|
||||||
# Real environment variables win over .env files.
|
|
||||||
#
|
|
||||||
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
|
|
||||||
#
|
|
||||||
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
|
|
||||||
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
|
|
||||||
|
|
||||||
###> symfony/framework-bundle ###
|
|
||||||
APP_ENV=dev
|
|
||||||
APP_SECRET=2ca64f8d83b9e89f5f19d672841d6bb8
|
|
||||||
#TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
|
||||||
#TRUSTED_HOSTS='^(localhost|example\.com)$'
|
|
||||||
###< symfony/framework-bundle ###
|
|
||||||
|
|
||||||
###> doctrine/doctrine-bundle ###
|
|
||||||
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
|
|
||||||
# For a MySQL database, use: "mysql://db_user:db_password@127.0.0.1:3306/db_name"
|
|
||||||
# For a PostgreSQL database, use: "postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=11&charset=utf8"
|
|
||||||
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
|
|
||||||
#DATABASE_URL=sqlite:///%kernel.project_dir%/data/database.sqlite
|
|
||||||
DATABASE_URL="postgresql://postgres:secret@db:5432/postgres?serverVersion=12&charset=utf8"
|
|
||||||
###< doctrine/doctrine-bundle ###
|
|
||||||
|
|
||||||
###> symfony/mailer ###
|
|
||||||
# MAILER_DSN=smtp://localhost
|
|
||||||
###< symfony/mailer ###
|
|
@ -1,6 +0,0 @@
|
|||||||
# define your env variables for the test env here
|
|
||||||
KERNEL_CLASS='App\Kernel'
|
|
||||||
APP_SECRET='$ecretf0rt3st'
|
|
||||||
SYMFONY_DEPRECATIONS_HELPER=999999
|
|
||||||
PANTHER_APP_ENV=panther
|
|
||||||
DATABASE_URL=sqlite:///%kernel.project_dir%/data/database_test.sqlite
|
|
80
app/.github/workflows/ci.yaml
vendored
80
app/.github/workflows/ci.yaml
vendored
@ -1,80 +0,0 @@
|
|||||||
name: "CI"
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'master'
|
|
||||||
|
|
||||||
env:
|
|
||||||
fail-fast: true
|
|
||||||
PHPUNIT_FLAGS: "-v"
|
|
||||||
SYMFONY_PHPUNIT_DIR: "$HOME/symfony-bridge/.phpunit"
|
|
||||||
SYMFONY_REQUIRE: ">=4.4"
|
|
||||||
# 40x: Since symfony/monolog-bridge 5.2:
|
|
||||||
# Passing an actionLevel (int|string) as constructor's 3rd argument of
|
|
||||||
# "Symfony\Bridge\Monolog\Handler\FingersCrossed\HttpCodeActivationStrategy"
|
|
||||||
# is deprecated, "Monolog\Handler\FingersCrossed\ActivationStrategyInterface" expected.
|
|
||||||
SYMFONY_DEPRECATIONS_HELPER: 40
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
name: "${{ matrix.operating-system }} / PHP ${{ matrix.php-version }}"
|
|
||||||
runs-on: ${{ matrix.operating-system }}
|
|
||||||
continue-on-error: false
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
operating-system: ['ubuntu-latest', 'windows-latest', 'macos-latest']
|
|
||||||
php-version: ['7.2.9', '7.3', '7.4', '8.0']
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: "Checkout code"
|
|
||||||
uses: actions/checkout@v2.3.3
|
|
||||||
|
|
||||||
- name: "Install PHP with extensions"
|
|
||||||
uses: shivammathur/setup-php@2.7.0
|
|
||||||
with:
|
|
||||||
coverage: "none"
|
|
||||||
extensions: "intl, mbstring, pdo_sqlite"
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer:v2
|
|
||||||
|
|
||||||
- name: "Add PHPUnit matcher"
|
|
||||||
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
|
|
||||||
|
|
||||||
- name: "Set composer cache directory"
|
|
||||||
id: composer-cache
|
|
||||||
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
|
|
||||||
|
|
||||||
- name: "Cache composer"
|
|
||||||
uses: actions/cache@v2.1.2
|
|
||||||
with:
|
|
||||||
path: ${{ steps.composer-cache.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('composer.json') }}
|
|
||||||
restore-keys: ${{ runner.os }}-${{ matrix.php-version }}-composer-
|
|
||||||
|
|
||||||
- name: "Require symfony/flex"
|
|
||||||
run: composer global require --no-progress --no-scripts --no-plugins symfony/flex dev-main
|
|
||||||
|
|
||||||
- if: matrix.php-version != '8.0'
|
|
||||||
run: composer update
|
|
||||||
|
|
||||||
- if: matrix.php-version == '8.0'
|
|
||||||
run: composer update --ignore-platform-req=php
|
|
||||||
|
|
||||||
- if: matrix.php-version != '8.0'
|
|
||||||
name: "Install PHPUnit"
|
|
||||||
run: vendor/bin/simple-phpunit install
|
|
||||||
|
|
||||||
- if: matrix.php-version == '8.0'
|
|
||||||
name: "Install PHPUnit for PHP 8"
|
|
||||||
run: |
|
|
||||||
echo 'SYMFONY_PHPUNIT_VERSION=9.4' >> $GITHUB_ENV
|
|
||||||
vendor/bin/simple-phpunit install
|
|
||||||
|
|
||||||
- name: "PHPUnit version"
|
|
||||||
run: vendor/bin/simple-phpunit --version
|
|
||||||
|
|
||||||
- name: "Run tests"
|
|
||||||
run: vendor/bin/simple-phpunit ${{ env.PHPUNIT_FLAGS }}
|
|
88
app/.github/workflows/lint.yaml
vendored
88
app/.github/workflows/lint.yaml
vendored
@ -1,88 +0,0 @@
|
|||||||
name: "Lint"
|
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
env:
|
|
||||||
fail-fast: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
php-cs-fixer:
|
|
||||||
name: PHP-CS-Fixer
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: "Checkout code"
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: PHP-CS-Fixer
|
|
||||||
uses: docker://oskarstark/php-cs-fixer-ga
|
|
||||||
with:
|
|
||||||
args: --diff --dry-run
|
|
||||||
|
|
||||||
linters:
|
|
||||||
name: Linters
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
php-version: ['7.4']
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: "Checkout code"
|
|
||||||
uses: actions/checkout@v2.3.3
|
|
||||||
|
|
||||||
- name: "Install PHP with extensions"
|
|
||||||
uses: shivammathur/setup-php@2.7.0
|
|
||||||
with:
|
|
||||||
coverage: "none"
|
|
||||||
extensions: intl
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer:v2
|
|
||||||
|
|
||||||
- name: "Set composer cache directory"
|
|
||||||
id: composer-cache
|
|
||||||
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
|
|
||||||
|
|
||||||
- name: "Cache composer"
|
|
||||||
uses: actions/cache@v2.1.2
|
|
||||||
with:
|
|
||||||
path: ${{ steps.composer-cache.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('composer.json') }}
|
|
||||||
restore-keys: ${{ runner.os }}-${{ matrix.php-version }}-composer-
|
|
||||||
|
|
||||||
- name: "Require symfony/flex"
|
|
||||||
run: composer global require --no-progress --no-scripts --no-plugins symfony/flex dev-main
|
|
||||||
|
|
||||||
- name: "Composer update"
|
|
||||||
id: install
|
|
||||||
run: composer update --no-scripts
|
|
||||||
|
|
||||||
- name: Lint YAML files
|
|
||||||
if: always() && steps.install.outcome == 'success'
|
|
||||||
run: ./bin/console lint:yaml config --parse-tags
|
|
||||||
|
|
||||||
- name: Lint Twig templates
|
|
||||||
if: always() && steps.install.outcome == 'success'
|
|
||||||
run: ./bin/console lint:twig templates --env=prod
|
|
||||||
|
|
||||||
- name: Lint XLIFF translations
|
|
||||||
if: always() && steps.install.outcome == 'success'
|
|
||||||
run: ./bin/console lint:xliff translations
|
|
||||||
|
|
||||||
- name: Lint Parameters and Services
|
|
||||||
if: always() && steps.install.outcome == 'success'
|
|
||||||
run: ./bin/console lint:container
|
|
||||||
|
|
||||||
- name: Lint Doctrine entities
|
|
||||||
if: always() && steps.install.outcome == 'success'
|
|
||||||
run: ./bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction
|
|
||||||
|
|
||||||
- name: Lint Composer config
|
|
||||||
if: always() && steps.install.outcome == 'success'
|
|
||||||
run: composer validate --strict
|
|
||||||
|
|
||||||
- name: Download Symfony CLI
|
|
||||||
if: always() && steps.install.outcome == 'success'
|
|
||||||
run: wget https://get.symfony.com/cli/installer -O - | bash
|
|
||||||
|
|
||||||
- name: Check if any dependencies are compromised
|
|
||||||
if: always() && steps.install.outcome == 'success'
|
|
||||||
run: /home/runner/.symfony/bin/symfony check:security
|
|
8
app/.gitignore
vendored
8
app/.gitignore
vendored
@ -1,5 +1,4 @@
|
|||||||
/public/build/fonts/glyphicons-*
|
/.env
|
||||||
/public/build/images/glyphicons-*
|
|
||||||
|
|
||||||
###> symfony/framework-bundle ###
|
###> symfony/framework-bundle ###
|
||||||
/.env.local
|
/.env.local
|
||||||
@ -11,11 +10,6 @@
|
|||||||
/vendor/
|
/vendor/
|
||||||
###< symfony/framework-bundle ###
|
###< symfony/framework-bundle ###
|
||||||
|
|
||||||
###> symfony/phpunit-bridge ###
|
|
||||||
.phpunit
|
|
||||||
.phpunit.result.cache
|
|
||||||
/phpunit.xml
|
|
||||||
###< symfony/phpunit-bridge ###
|
|
||||||
###> symfony/webpack-encore-bundle ###
|
###> symfony/webpack-encore-bundle ###
|
||||||
/node_modules/
|
/node_modules/
|
||||||
/public/build/
|
/public/build/
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
$fileHeaderComment = <<<COMMENT
|
|
||||||
This file is part of the Symfony package.
|
|
||||||
|
|
||||||
(c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
|
|
||||||
For the full copyright and license information, please view the LICENSE
|
|
||||||
file that was distributed with this source code.
|
|
||||||
COMMENT;
|
|
||||||
|
|
||||||
$finder = PhpCsFixer\Finder::create()
|
|
||||||
->in(__DIR__)
|
|
||||||
->exclude('config')
|
|
||||||
->exclude('var')
|
|
||||||
->exclude('public/bundles')
|
|
||||||
->exclude('public/build')
|
|
||||||
// exclude files generated by Symfony Flex recipes
|
|
||||||
->notPath('bin/console')
|
|
||||||
->notPath('public/index.php')
|
|
||||||
;
|
|
||||||
|
|
||||||
return (new PhpCsFixer\Config())
|
|
||||||
->setRiskyAllowed(true)
|
|
||||||
->setRules([
|
|
||||||
'@Symfony' => true,
|
|
||||||
'@Symfony:risky' => true,
|
|
||||||
'header_comment' => ['header' => $fileHeaderComment, 'separate' => 'both'],
|
|
||||||
'linebreak_after_opening_tag' => true,
|
|
||||||
'mb_str_functions' => true,
|
|
||||||
'no_php4_constructor' => true,
|
|
||||||
'no_unreachable_default_argument_value' => true,
|
|
||||||
'no_useless_else' => true,
|
|
||||||
'no_useless_return' => true,
|
|
||||||
'php_unit_strict' => true,
|
|
||||||
'phpdoc_order' => true,
|
|
||||||
'strict_comparison' => true,
|
|
||||||
'strict_param' => true,
|
|
||||||
])
|
|
||||||
->setFinder($finder)
|
|
||||||
->setCacheFile(__DIR__.'/var/.php_cs.cache')
|
|
||||||
;
|
|
@ -1,7 +0,0 @@
|
|||||||
Contributing
|
|
||||||
============
|
|
||||||
|
|
||||||
The Symfony Demo application is an open source project. Contributions made by
|
|
||||||
the community are welcome. Send us your ideas, code reviews, pull requests and
|
|
||||||
feature requests to help us improve this project. All contributions must follow
|
|
||||||
the [usual Symfony contribution requirements](https://symfony.com/doc/current/contributing/index.html).
|
|
@ -1,60 +0,0 @@
|
|||||||
Symfony Demo Application
|
|
||||||
========================
|
|
||||||
|
|
||||||
The "Symfony Demo Application" is a reference application created to show how
|
|
||||||
to develop applications following the [Symfony Best Practices][1].
|
|
||||||
|
|
||||||
Requirements
|
|
||||||
------------
|
|
||||||
|
|
||||||
* PHP 7.2.9 or higher;
|
|
||||||
* PDO-SQLite PHP extension enabled;
|
|
||||||
* and the [usual Symfony application requirements][2].
|
|
||||||
|
|
||||||
Installation
|
|
||||||
------------
|
|
||||||
|
|
||||||
[Download Symfony][4] to install the `symfony` binary on your computer and run
|
|
||||||
this command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ symfony new --demo my_project
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, you can use Composer:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ composer create-project symfony/symfony-demo my_project
|
|
||||||
```
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
|
|
||||||
There's no need to configure anything to run the application. If you have
|
|
||||||
[installed Symfony][4] binary, run this command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ cd my_project/
|
|
||||||
$ symfony serve
|
|
||||||
```
|
|
||||||
|
|
||||||
Then access the application in your browser at the given URL (<https://localhost:8000> by default).
|
|
||||||
|
|
||||||
If you don't have the Symfony binary installed, run `php -S localhost:8000 -t public/`
|
|
||||||
to use the built-in PHP web server or [configure a web server][3] like Nginx or
|
|
||||||
Apache to run the application.
|
|
||||||
|
|
||||||
Tests
|
|
||||||
-----
|
|
||||||
|
|
||||||
Execute this command to run tests:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ cd my_project/
|
|
||||||
$ ./bin/phpunit
|
|
||||||
```
|
|
||||||
|
|
||||||
[1]: https://symfony.com/doc/current/best_practices.html
|
|
||||||
[2]: https://symfony.com/doc/current/reference/requirements.html
|
|
||||||
[3]: https://symfony.com/doc/current/cookbook/configuration/web_server_configuration.html
|
|
||||||
[4]: https://symfony.com/download
|
|
15
app/assets/app.js
Normal file
15
app/assets/app.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
// SCSS
|
||||||
|
import './styles/app.scss';
|
||||||
|
|
||||||
|
// start the Stimulus application
|
||||||
|
import './bootstrap';
|
8
app/assets/bootstrap.js
vendored
Normal file
8
app/assets/bootstrap.js
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
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?$/
|
||||||
|
));
|
4
app/assets/controllers.json
Normal file
4
app/assets/controllers.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"controllers": [],
|
||||||
|
"entrypoints": []
|
||||||
|
}
|
16
app/assets/controllers/hello_controller.js
Normal file
16
app/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';
|
||||||
|
}
|
||||||
|
}
|
@ -1,64 +0,0 @@
|
|||||||
import '../scss/admin.scss';
|
|
||||||
import 'eonasdan-bootstrap-datetimepicker';
|
|
||||||
import 'typeahead.js';
|
|
||||||
import Bloodhound from "bloodhound-js";
|
|
||||||
import 'bootstrap-tagsinput';
|
|
||||||
|
|
||||||
$(function() {
|
|
||||||
// Datetime picker initialization.
|
|
||||||
// See https://eonasdan.github.io/bootstrap-datetimepicker/
|
|
||||||
$('[data-toggle="datetimepicker"]').datetimepicker({
|
|
||||||
icons: {
|
|
||||||
time: 'fa fa-clock-o',
|
|
||||||
date: 'fa fa-calendar',
|
|
||||||
up: 'fa fa-chevron-up',
|
|
||||||
down: 'fa fa-chevron-down',
|
|
||||||
previous: 'fa fa-chevron-left',
|
|
||||||
next: 'fa fa-chevron-right',
|
|
||||||
today: 'fa fa-check-circle-o',
|
|
||||||
clear: 'fa fa-trash',
|
|
||||||
close: 'fa fa-remove'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Bootstrap-tagsinput initialization
|
|
||||||
// https://bootstrap-tagsinput.github.io/bootstrap-tagsinput/examples/
|
|
||||||
var $input = $('input[data-toggle="tagsinput"]');
|
|
||||||
if ($input.length) {
|
|
||||||
var source = new Bloodhound({
|
|
||||||
local: $input.data('tags'),
|
|
||||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
|
||||||
datumTokenizer: Bloodhound.tokenizers.whitespace
|
|
||||||
});
|
|
||||||
source.initialize();
|
|
||||||
|
|
||||||
$input.tagsinput({
|
|
||||||
trimValue: true,
|
|
||||||
focusClass: 'focus',
|
|
||||||
typeaheadjs: {
|
|
||||||
name: 'tags',
|
|
||||||
source: source.ttAdapter()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handling the modal confirmation message.
|
|
||||||
$(document).on('submit', 'form[data-confirmation]', function (event) {
|
|
||||||
var $form = $(this),
|
|
||||||
$confirm = $('#confirmationModal');
|
|
||||||
|
|
||||||
if ($confirm.data('result') !== 'yes') {
|
|
||||||
//cancel submit event
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
$confirm
|
|
||||||
.off('click', '#btnYes')
|
|
||||||
.on('click', '#btnYes', function () {
|
|
||||||
$confirm.data('result', 'yes');
|
|
||||||
$form.find('input[type="submit"]').attr('disabled', 'disabled');
|
|
||||||
$form.submit();
|
|
||||||
})
|
|
||||||
.modal('show');
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,15 +0,0 @@
|
|||||||
import '../scss/app.scss';
|
|
||||||
|
|
||||||
// loads the Bootstrap jQuery plugins
|
|
||||||
import 'bootstrap-sass/assets/javascripts/bootstrap/transition.js';
|
|
||||||
import 'bootstrap-sass/assets/javascripts/bootstrap/alert.js';
|
|
||||||
import 'bootstrap-sass/assets/javascripts/bootstrap/collapse.js';
|
|
||||||
import 'bootstrap-sass/assets/javascripts/bootstrap/dropdown.js';
|
|
||||||
import 'bootstrap-sass/assets/javascripts/bootstrap/modal.js';
|
|
||||||
import 'jquery'
|
|
||||||
|
|
||||||
// loads the code syntax highlighting library
|
|
||||||
import './highlight.js';
|
|
||||||
|
|
||||||
// Creates links to the Symfony documentation
|
|
||||||
import './doclinks.js';
|
|
@ -1,58 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
// Wraps some elements in anchor tags referencing to the Symfony documentation
|
|
||||||
$(function() {
|
|
||||||
var $modal = $('#sourceCodeModal');
|
|
||||||
var $controllerCode = $modal.find('code.php');
|
|
||||||
var $templateCode = $modal.find('code.twig');
|
|
||||||
|
|
||||||
function anchor(url, content) {
|
|
||||||
return '<a class="doclink" target="_blank" href="' + url + '">' + content + '</a>';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Wraps links to the Symfony documentation
|
|
||||||
$modal.find('.hljs-comment').each(function() {
|
|
||||||
$(this).html($(this).html().replace(/https:\/\/symfony.com\/doc\/[\w/.#-]+/g, function(url) {
|
|
||||||
return anchor(url, url);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wraps Symfony's annotations
|
|
||||||
var annotations = {
|
|
||||||
'@Cache': 'https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/cache.html',
|
|
||||||
'@IsGranted': 'https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/security.html#isgranted',
|
|
||||||
'@ParamConverter': 'https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html',
|
|
||||||
'@Route': 'https://symfony.com/doc/current/routing.html#creating-routes-as-annotations',
|
|
||||||
'@Security': 'https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/security.html#security'
|
|
||||||
};
|
|
||||||
|
|
||||||
$controllerCode.find('.hljs-doctag').each(function() {
|
|
||||||
var annotation = $(this).text();
|
|
||||||
|
|
||||||
if (annotations[annotation]) {
|
|
||||||
$(this).html(anchor(annotations[annotation], annotation));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wraps Twig's tags
|
|
||||||
$templateCode.find('.hljs-template-tag > .hljs-name').each(function() {
|
|
||||||
var tag = $(this).text();
|
|
||||||
|
|
||||||
if ('else' === tag || tag.match(/^end/)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = 'https://twig.symfony.com/doc/3.x/tags/' + tag + '.html#' + tag;
|
|
||||||
|
|
||||||
$(this).html(anchor(url, tag));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wraps Twig's functions
|
|
||||||
$templateCode.find('.hljs-template-variable > .hljs-name').each(function() {
|
|
||||||
var func = $(this).text();
|
|
||||||
|
|
||||||
var url = 'https://twig.symfony.com/doc/3.x/functions/' + func + '.html#' + func;
|
|
||||||
|
|
||||||
$(this).html(anchor(url, func));
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,8 +0,0 @@
|
|||||||
import hljs from 'highlight.js/lib/highlight';
|
|
||||||
import php from 'highlight.js/lib/languages/php';
|
|
||||||
import twig from 'highlight.js/lib/languages/twig';
|
|
||||||
|
|
||||||
hljs.registerLanguage('php', php);
|
|
||||||
hljs.registerLanguage('twig', twig);
|
|
||||||
|
|
||||||
hljs.initHighlightingOnLoad();
|
|
@ -1,106 +0,0 @@
|
|||||||
/**
|
|
||||||
* jQuery plugin for an instant searching.
|
|
||||||
*
|
|
||||||
* @author Oleg Voronkovich <oleg-voronkovich@yandex.ru>
|
|
||||||
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
|
||||||
*/
|
|
||||||
(function ($) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
String.prototype.render = function (parameters) {
|
|
||||||
return this.replace(/({{ (\w+) }})/g, function (match, pattern, name) {
|
|
||||||
return parameters[name];
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
// INSTANTS SEARCH PUBLIC CLASS DEFINITION
|
|
||||||
// =======================================
|
|
||||||
|
|
||||||
var InstantSearch = function (element, options) {
|
|
||||||
this.$input = $(element);
|
|
||||||
this.$form = this.$input.closest('form');
|
|
||||||
this.$preview = $('<ul class="search-preview list-group">').appendTo(this.$form);
|
|
||||||
this.options = $.extend({}, InstantSearch.DEFAULTS, this.$input.data(), options);
|
|
||||||
|
|
||||||
this.$input.keyup(this.debounce());
|
|
||||||
};
|
|
||||||
|
|
||||||
InstantSearch.DEFAULTS = {
|
|
||||||
minQueryLength: 2,
|
|
||||||
limit: 10,
|
|
||||||
delay: 500,
|
|
||||||
noResultsMessage: 'No results found',
|
|
||||||
itemTemplate: '\
|
|
||||||
<article class="post">\
|
|
||||||
<h2><a href="{{ url }}">{{ title }}</a></h2>\
|
|
||||||
<p class="post-metadata">\
|
|
||||||
<span class="metadata"><i class="fa fa-calendar"></i> {{ date }}</span>\
|
|
||||||
<span class="metadata"><i class="fa fa-user"></i> {{ author }}</span>\
|
|
||||||
</p>\
|
|
||||||
<p>{{ summary }}</p>\
|
|
||||||
</article>'
|
|
||||||
};
|
|
||||||
|
|
||||||
InstantSearch.prototype.debounce = function () {
|
|
||||||
var delay = this.options.delay;
|
|
||||||
var search = this.search;
|
|
||||||
var timer = null;
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
return function () {
|
|
||||||
clearTimeout(timer);
|
|
||||||
timer = setTimeout(function () {
|
|
||||||
search.apply(self);
|
|
||||||
}, delay);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
InstantSearch.prototype.search = function () {
|
|
||||||
var query = $.trim(this.$input.val()).replace(/\s{2,}/g, ' ');
|
|
||||||
if (query.length < this.options.minQueryLength) {
|
|
||||||
this.$preview.empty();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
var data = this.$form.serializeArray();
|
|
||||||
data['l'] = this.limit;
|
|
||||||
|
|
||||||
$.getJSON(this.$form.attr('action'), data, function (items) {
|
|
||||||
self.show(items);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
InstantSearch.prototype.show = function (items) {
|
|
||||||
var $preview = this.$preview;
|
|
||||||
var itemTemplate = this.options.itemTemplate;
|
|
||||||
|
|
||||||
if (0 === items.length) {
|
|
||||||
$preview.html(this.options.noResultsMessage);
|
|
||||||
} else {
|
|
||||||
$preview.empty();
|
|
||||||
$.each(items, function (index, item) {
|
|
||||||
$preview.append(itemTemplate.render(item));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// INSTANTS SEARCH PLUGIN DEFINITION
|
|
||||||
// =================================
|
|
||||||
|
|
||||||
function Plugin(option) {
|
|
||||||
return this.each(function () {
|
|
||||||
var $this = $(this);
|
|
||||||
var instance = $this.data('instantSearch');
|
|
||||||
var options = typeof option === 'object' && option;
|
|
||||||
|
|
||||||
if (!instance) $this.data('instantSearch', (instance = new InstantSearch(this, options)));
|
|
||||||
|
|
||||||
if (option === 'search') instance.search();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
$.fn.instantSearch = Plugin;
|
|
||||||
$.fn.instantSearch.Constructor = InstantSearch;
|
|
||||||
|
|
||||||
})(window.jQuery);
|
|
@ -1,11 +0,0 @@
|
|||||||
$(function() {
|
|
||||||
var usernameEl = $('#username');
|
|
||||||
var passwordEl = $('#password');
|
|
||||||
|
|
||||||
// in a real application, the user/password should never be hardcoded
|
|
||||||
// but for the demo application it's very convenient to do so
|
|
||||||
if (!usernameEl.val() || 'jane_admin' === usernameEl.val()) {
|
|
||||||
usernameEl.val('jane_admin');
|
|
||||||
passwordEl.val('kitten');
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,9 +0,0 @@
|
|||||||
import './jquery.instantSearch.js';
|
|
||||||
|
|
||||||
$(function() {
|
|
||||||
$('.search-field')
|
|
||||||
.instantSearch({
|
|
||||||
delay: 100,
|
|
||||||
})
|
|
||||||
.keyup();
|
|
||||||
});
|
|
@ -1,26 +0,0 @@
|
|||||||
@import "~bootswatch/flatly/variables";
|
|
||||||
@import "~eonasdan-bootstrap-datetimepicker/src/sass/bootstrap-datetimepicker-build.scss";
|
|
||||||
@import "bootstrap-tagsinput.scss";
|
|
||||||
|
|
||||||
/* Page: 'Backend post index'
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
body#admin_post_index .item-actions {
|
|
||||||
white-space: nowrap
|
|
||||||
}
|
|
||||||
|
|
||||||
body#admin_post_index .item-actions a.btn + a.btn {
|
|
||||||
margin-left: 4px
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Page: 'Backend post show'
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
body#admin_post_show .post-tags .label-default {
|
|
||||||
background-color: #e9ecec;
|
|
||||||
color: #6D8283;
|
|
||||||
font-size: 16px;
|
|
||||||
margin-right: 10px;
|
|
||||||
padding: .4em 1em .5em;
|
|
||||||
}
|
|
||||||
body#admin_post_show .post-tags .label-default i {
|
|
||||||
color: #95A6A7;
|
|
||||||
}
|
|
@ -1,360 +0,0 @@
|
|||||||
// setting the value of this variable to an empty data URL is the only working solution
|
|
||||||
// to load the Bootswatch web fonts locally and avoid loading them from Google servers
|
|
||||||
// see https://github.com/thomaspark/bootswatch/issues/55#issuecomment-298093182
|
|
||||||
$web-font-path: 'data:text/css;base64,';
|
|
||||||
|
|
||||||
// Make sure the bootstrap-sass and lato fonts are resolved correctly
|
|
||||||
$icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/";
|
|
||||||
$lato-font-path: '~lato-font/fonts';
|
|
||||||
|
|
||||||
@import "~bootswatch/flatly/variables";
|
|
||||||
@import "~bootstrap-sass/assets/stylesheets/bootstrap";
|
|
||||||
@import "~bootswatch/flatly/bootswatch";
|
|
||||||
@import "~@fortawesome/fontawesome-free/css/all.css";
|
|
||||||
@import "~@fortawesome/fontawesome-free/css/v4-shims.css";
|
|
||||||
@import "~highlight.js/styles/solarized-light.css";
|
|
||||||
|
|
||||||
// pick the Lato fonts individually to avoid importing the entire font family
|
|
||||||
@import '~lato-font/scss/public-api';
|
|
||||||
@include lato-include-font('normal');
|
|
||||||
@include lato-include-font('bold');
|
|
||||||
|
|
||||||
/* Basic styles
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
p, ul, ol {
|
|
||||||
font-size: 19px;
|
|
||||||
margin-bottom: 1.5em
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin-bottom: 0.5em
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
background: #ecf0f1;
|
|
||||||
color: #2c3e50
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-danger, .text-danger:hover {
|
|
||||||
color: #e74c3c
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
margin-right: 0.25em
|
|
||||||
}
|
|
||||||
|
|
||||||
.table.table-middle-aligned th,
|
|
||||||
.table.table-middle-aligned td {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doclink {
|
|
||||||
color: inherit
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Utilities
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
.m-b-0 { margin-bottom: 0 }
|
|
||||||
|
|
||||||
/* Page elements
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: 100vh
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
margin-bottom: 2em
|
|
||||||
}
|
|
||||||
|
|
||||||
header ul.nav li {
|
|
||||||
margin-bottom: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
header .locales {
|
|
||||||
min-width: 190px;
|
|
||||||
}
|
|
||||||
header .locales a {
|
|
||||||
color: #212529;
|
|
||||||
padding: 3px 15px;
|
|
||||||
}
|
|
||||||
header .locales a small {
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 2px solid #dee2e6;
|
|
||||||
color: #7b8a8b;
|
|
||||||
float: left;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.1;
|
|
||||||
margin: 2px 10px 0 0;
|
|
||||||
min-width: 26px;
|
|
||||||
padding: 0px 3px;
|
|
||||||
text-align: center;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
header .locales .active small,
|
|
||||||
header .locales a:hover small {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.body-container {
|
|
||||||
flex: 1;
|
|
||||||
/* needed to prevent pages with a very small height and browsers not supporting flex */
|
|
||||||
min-height: 600px
|
|
||||||
}
|
|
||||||
|
|
||||||
.body-container #main h1, .body-container #main h2 {
|
|
||||||
margin-top: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
#sidebar .section {
|
|
||||||
margin-bottom: 2em
|
|
||||||
}
|
|
||||||
|
|
||||||
#sidebar p {
|
|
||||||
font-size: 15px
|
|
||||||
}
|
|
||||||
|
|
||||||
#sidebar p + p {
|
|
||||||
margin: 1.5em 0 0
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
background: #ecf0f1;
|
|
||||||
margin-top: 2em;
|
|
||||||
padding-top: 2em;
|
|
||||||
padding-bottom: 2em
|
|
||||||
}
|
|
||||||
|
|
||||||
footer p {
|
|
||||||
color: #7b8a8b;
|
|
||||||
font-size: 13px;
|
|
||||||
margin-bottom: 0.25em
|
|
||||||
}
|
|
||||||
|
|
||||||
footer #footer-resources {
|
|
||||||
text-align: right
|
|
||||||
}
|
|
||||||
|
|
||||||
footer #footer-resources i {
|
|
||||||
color: #7b8a8b;
|
|
||||||
font-size: 28.5px;
|
|
||||||
margin-left: 0.5em
|
|
||||||
}
|
|
||||||
|
|
||||||
#sourceCodeModal h3 {
|
|
||||||
font-size: 19px;
|
|
||||||
margin-top: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
#sourceCodeModal h3 small {
|
|
||||||
color: #7b8a8b;
|
|
||||||
font-size: 80%
|
|
||||||
}
|
|
||||||
|
|
||||||
#sourceCodeModal pre {
|
|
||||||
margin-bottom: 2em;
|
|
||||||
padding: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
#confirmationModal .modal-dialog {
|
|
||||||
width: 500px
|
|
||||||
}
|
|
||||||
|
|
||||||
#confirmationModal .modal-footer button {
|
|
||||||
min-width: 75px
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Misc. elements
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
.section.rss a {
|
|
||||||
color: #f39c12;
|
|
||||||
font-size: 21px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Forms
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
.form-group.has-error .form-control {
|
|
||||||
border-color: #e74c3c
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group.has-error .control-label {
|
|
||||||
color: #e74c3c
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group.has-error .help-block {
|
|
||||||
background-color: #e74c3c;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 15px;
|
|
||||||
padding: 1em
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group.has-error .help-block ul,
|
|
||||||
.form-group.has-error .help-block li {
|
|
||||||
margin-bottom: 0
|
|
||||||
}
|
|
||||||
.form-group.has-error .help-block li + li {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
max-width: 100%
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Page: 'Technical Requirements Checker'
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
body#requirements_checker header h1 {
|
|
||||||
margin-bottom: 0;
|
|
||||||
margin-top: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
body#requirements_checker header h1 span {
|
|
||||||
font-size: 120%;
|
|
||||||
opacity: 0.7;
|
|
||||||
padding: 0 5px
|
|
||||||
}
|
|
||||||
|
|
||||||
body#requirements_checker .panel li {
|
|
||||||
margin-bottom: 1em
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Page: 'Homepage'
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
body#homepage {
|
|
||||||
text-align: center
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Page: 'Login'
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
body#login #login-users-help p {
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1.42857
|
|
||||||
}
|
|
||||||
|
|
||||||
body#login #login-users-help p:last-child {
|
|
||||||
margin-bottom: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
body#login #login-users-help p .label {
|
|
||||||
margin-right: 5px
|
|
||||||
}
|
|
||||||
|
|
||||||
body#login #login-users-help p .console {
|
|
||||||
display: block;
|
|
||||||
margin: 5px 0;
|
|
||||||
padding: 10px
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Common Blog page elements
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
.post-metadata {
|
|
||||||
color: #b4bcc2;
|
|
||||||
font-size: 19px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
.post-metadata .metadata {
|
|
||||||
margin-right: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-tags .label {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Page: 'Blog index'
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
body#blog_index #main h1,
|
|
||||||
body#blog_index #main p {
|
|
||||||
margin-bottom: 0.5em
|
|
||||||
}
|
|
||||||
|
|
||||||
body#blog_index article.post {
|
|
||||||
margin-bottom: 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
body#blog_index .post-metadata {
|
|
||||||
font-size: 16px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body#blog_index .post-tags .label-default {
|
|
||||||
background-color: #e9ecec;
|
|
||||||
color: #6d8283;
|
|
||||||
}
|
|
||||||
body#blog_index .post-tags .label-default i {
|
|
||||||
color: #a3b2b2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Page: 'Blog post show'
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
body#blog_post_show #main h3 {
|
|
||||||
margin-bottom: 0.75em
|
|
||||||
}
|
|
||||||
|
|
||||||
body#blog_post_show .post-tags .label-default {
|
|
||||||
background-color: #e9ecec;
|
|
||||||
color: #6D8283;
|
|
||||||
font-size: 16px;
|
|
||||||
margin-right: 10px;
|
|
||||||
padding: .4em 1em .5em;
|
|
||||||
}
|
|
||||||
body#blog_post_show .post-tags .label-default i {
|
|
||||||
color: #95A6A7;
|
|
||||||
}
|
|
||||||
|
|
||||||
body#blog_post_show #post-add-comment {
|
|
||||||
margin: 2em 0
|
|
||||||
}
|
|
||||||
|
|
||||||
body#blog_post_show #post-add-comment p {
|
|
||||||
margin-bottom: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
body#blog_post_show #post-add-comment p a.btn {
|
|
||||||
margin-right: 0.5em
|
|
||||||
}
|
|
||||||
|
|
||||||
body#blog_post_show .post-comment {
|
|
||||||
margin-bottom: 2em
|
|
||||||
}
|
|
||||||
|
|
||||||
body#blog_post_show .post-comment h4 {
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.42857;
|
|
||||||
margin-top: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
body#blog_post_show .post-comment h4 strong {
|
|
||||||
display: block
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Page: 'Comment form error'
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
body#comment_form_error h1.text-danger {
|
|
||||||
margin-bottom: 1em
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) and (max-width: 1200px) {
|
|
||||||
.container {
|
|
||||||
width: 98%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Page: 'Blog search'
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
body#blog_search #main h1,
|
|
||||||
body#blog_search #main p {
|
|
||||||
margin-bottom: 0.5em
|
|
||||||
}
|
|
||||||
|
|
||||||
body#blog_search article.post:first-child {
|
|
||||||
margin-top: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
body#blog_search article.post {
|
|
||||||
margin-bottom: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
body#blog_search .post-metadata {
|
|
||||||
font-size: 16px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
194
app/assets/scss/bootstrap-tagsinput.scss
vendored
194
app/assets/scss/bootstrap-tagsinput.scss
vendored
@ -1,194 +0,0 @@
|
|||||||
/* ------------------------------------------------------------------------------
|
|
||||||
*
|
|
||||||
* # Twiter Typeahead
|
|
||||||
*
|
|
||||||
* Styles for tagsinput.js - input suggestion engine
|
|
||||||
*
|
|
||||||
* ---------------------------------------------------------------------------- */
|
|
||||||
.twitter-typeahead {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.typeahead,
|
|
||||||
.tt-query,
|
|
||||||
.tt-hint {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
.tt-hint {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
.tt-menu{
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 1px;
|
|
||||||
min-width: 180px;
|
|
||||||
padding: 7px 0;
|
|
||||||
background-color: #fff;
|
|
||||||
border: 1px solid rgba(0,0,0,0.15);
|
|
||||||
border-radius: 4px;
|
|
||||||
max-height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
-webkit-box-shadow: 0 6px 12px rgba(0,0,0,0.175);
|
|
||||||
box-shadow: 0 6px 12px rgba(0,0,0,0.175);
|
|
||||||
-webkit-background-clip: padding-box;
|
|
||||||
background-clip: padding-box;
|
|
||||||
}
|
|
||||||
.typeahead-scrollable .tt-menu{
|
|
||||||
max-height: 250px;
|
|
||||||
}
|
|
||||||
.typeahead-rtl .tt-menu{
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.tt-suggestion {
|
|
||||||
padding: 8px 15px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.tt-suggestion.tt-cursor {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
.tt-suggestion p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.tt-suggestion.tt-selectable:before {
|
|
||||||
content: '\f02b';
|
|
||||||
font-family: 'Font Awesome 5 Free';
|
|
||||||
font-weight: 900;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 15px;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
color: inherit;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
.tt-dataset-group .tt-suggestion {
|
|
||||||
padding-left: 24px;
|
|
||||||
padding-right: 24px;
|
|
||||||
}
|
|
||||||
.tt-heading {
|
|
||||||
font-size: 11px;
|
|
||||||
line-height: 1.82;
|
|
||||||
padding: 8px 15px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
display: block;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-top: 2px;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
.tt-suggestion:hover,
|
|
||||||
.tt-suggestion:focus {
|
|
||||||
color: #ffffff;
|
|
||||||
text-decoration: none;
|
|
||||||
outline: 0;
|
|
||||||
background-color: #18bc9c;
|
|
||||||
}
|
|
||||||
/* ------------------------------------------------------------------------------
|
|
||||||
*
|
|
||||||
* # Bootstrap tags input
|
|
||||||
*
|
|
||||||
* Styles for tagsinput.js - tags input for Bootstrap
|
|
||||||
*
|
|
||||||
* ---------------------------------------------------------------------------- */
|
|
||||||
.bootstrap-tagsinput {
|
|
||||||
display: table-cell;
|
|
||||||
vertical-align: middle;
|
|
||||||
width: 100%;
|
|
||||||
height: 45px;
|
|
||||||
padding: 0;
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1.42857143;
|
|
||||||
color: #2c3e50;
|
|
||||||
background-color: #ffffff;
|
|
||||||
background-image: none;
|
|
||||||
border: 2px solid #dce4ec;
|
|
||||||
border-radius: 4px;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
-webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
|
|
||||||
-o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
|
|
||||||
transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
|
|
||||||
}
|
|
||||||
.has-error .bootstrap-tagsinput {
|
|
||||||
border-color: #e74c3c !important;
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput.focus {
|
|
||||||
border-color: #2c3e50;
|
|
||||||
outline: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput input {
|
|
||||||
border: 0;
|
|
||||||
outline: 0;
|
|
||||||
background-color: transparent;
|
|
||||||
padding: 5px 11px;
|
|
||||||
margin-top: 2px;
|
|
||||||
margin-left: 2px;
|
|
||||||
width: auto !important;
|
|
||||||
min-width: 100px;
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1.6666667;
|
|
||||||
-webkit-box-shadow: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput input:focus {
|
|
||||||
border: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput .twitter-typeahead {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput .tt-menu {
|
|
||||||
margin-top: 5px;
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput .tag {
|
|
||||||
margin: 1px 0 0 3px;
|
|
||||||
border: 0;
|
|
||||||
border-radius: .25em;
|
|
||||||
padding: 5px 11px;
|
|
||||||
padding-right: 30px;
|
|
||||||
float: left;
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1.6666667;
|
|
||||||
font-weight: 400;
|
|
||||||
text-transform: none;
|
|
||||||
position: relative;
|
|
||||||
background-color: #18bc9c;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.has-error .bootstrap-tagsinput .tag {
|
|
||||||
background-color: #e74c3c !important;
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput .tag [data-role="remove"] {
|
|
||||||
cursor: pointer;
|
|
||||||
color: inherit;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
right: 11px;
|
|
||||||
line-height: 1;
|
|
||||||
margin-top: -5.5px;
|
|
||||||
opacity: 0.7;
|
|
||||||
filter: alpha(opacity=70);
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput .tag [data-role="remove"]:hover {
|
|
||||||
opacity: 1;
|
|
||||||
filter: alpha(opacity=100);
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput .tag:before {
|
|
||||||
content: '\f02b';
|
|
||||||
font-family: 'Font Awesome 5 Free';
|
|
||||||
font-weight: 900;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 15px;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
color: #fff;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput .tag [data-role="remove"]:after {
|
|
||||||
content: '\f00d';
|
|
||||||
font-family: 'Font Awesome 5 Free';
|
|
||||||
font-weight: 900;
|
|
||||||
display: block;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #fff;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
3
app/assets/styles/app.css
Normal file
3
app/assets/styles/app.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
body {
|
||||||
|
background-color: lightgray;
|
||||||
|
}
|
4
app/assets/styles/app.scss
Normal file
4
app/assets/styles/app.scss
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 400%;
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
if (!file_exists(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
|
|
||||||
echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === getenv('SYMFONY_PHPUNIT_DIR')) {
|
|
||||||
putenv('SYMFONY_PHPUNIT_DIR='.__DIR__.'/.phpunit');
|
|
||||||
}
|
|
||||||
|
|
||||||
require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
|
|
@ -1,59 +1,34 @@
|
|||||||
{
|
{
|
||||||
"license": "proprietary",
|
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"minimum-stability": "stable",
|
"license": "proprietary",
|
||||||
|
"minimum-stability": "dev",
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"replace": {
|
|
||||||
"symfony/polyfill-php70": "*",
|
|
||||||
"symfony/polyfill-php72": "*"
|
|
||||||
},
|
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.2.9",
|
"php": ">=7.2.5",
|
||||||
"ext-pdo_sqlite": "*",
|
"ext-ctype": "*",
|
||||||
"composer/package-versions-deprecated": "^1.8",
|
"ext-iconv": "*",
|
||||||
"doctrine/doctrine-bundle": "^1.12|^2.0",
|
"composer/package-versions-deprecated": "1.11.99.1",
|
||||||
|
"doctrine/doctrine-bundle": "^2.2",
|
||||||
"doctrine/doctrine-migrations-bundle": "^3.0",
|
"doctrine/doctrine-migrations-bundle": "^3.0",
|
||||||
"doctrine/orm": "^2.5.11",
|
"doctrine/orm": "^2.8",
|
||||||
"erusev/parsedown": "^1.6",
|
"sensio/framework-extra-bundle": "^6.1",
|
||||||
"sensio/framework-extra-bundle": "^5.6",
|
"symfony/console": "5.2.*",
|
||||||
"symfony/apache-pack": "^1.0",
|
"symfony/dotenv": "5.2.*",
|
||||||
"symfony/asset": "^5.2",
|
"symfony/flex": "^1.3.1",
|
||||||
"symfony/console": "^5.2",
|
"symfony/form": "5.2.*",
|
||||||
"symfony/dotenv": "^5.2",
|
"symfony/framework-bundle": "5.2.*",
|
||||||
"symfony/expression-language": "^5.2",
|
"symfony/maker-bundle": "^1.29",
|
||||||
"symfony/flex": "^1.1",
|
"symfony/proxy-manager-bridge": "5.2.*",
|
||||||
"symfony/form": "^5.2",
|
"symfony/security-csrf": "5.2.*",
|
||||||
"symfony/framework-bundle": "^5.2",
|
"symfony/twig-bundle": "^5.2",
|
||||||
"symfony/intl": "^5.2",
|
"symfony/validator": "5.2.*",
|
||||||
"symfony/mailer": "^5.2",
|
"symfony/webpack-encore-bundle": "^1.11",
|
||||||
"symfony/monolog-bundle": "^3.1",
|
"symfony/yaml": "5.2.*",
|
||||||
"symfony/polyfill-intl-messageformatter": "^1.12",
|
"twig/extra-bundle": "^2.12|^3.0",
|
||||||
"symfony/security-bundle": "^5.2",
|
"twig/twig": "^2.12|^3.0"
|
||||||
"symfony/string": "^5.2",
|
|
||||||
"symfony/translation": "^5.2",
|
|
||||||
"symfony/twig-pack": "^1.0",
|
|
||||||
"symfony/validator": "^5.2",
|
|
||||||
"symfony/webpack-encore-bundle": "^1.4",
|
|
||||||
"symfony/yaml": "^5.2",
|
|
||||||
"tgalopin/html-sanitizer-bundle": "^1.2",
|
|
||||||
"twig/intl-extra": "^3.0",
|
|
||||||
"twig/markdown-extra": "^3.0"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"dama/doctrine-test-bundle": "^6.2",
|
|
||||||
"doctrine/doctrine-fixtures-bundle": "^3.0",
|
|
||||||
"symfony/browser-kit": "^5.2",
|
|
||||||
"symfony/css-selector": "^5.2",
|
|
||||||
"symfony/debug-bundle": "^5.2",
|
|
||||||
"symfony/maker-bundle": "^1.11",
|
|
||||||
"symfony/phpunit-bridge": "^5.2",
|
|
||||||
"symfony/stopwatch": "^5.2",
|
|
||||||
"symfony/web-profiler-bundle": "^5.2"
|
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"platform": {
|
"optimize-autoloader": true,
|
||||||
"php": "7.2.9"
|
|
||||||
},
|
|
||||||
"preferred-install": {
|
"preferred-install": {
|
||||||
"*": "dist"
|
"*": "dist"
|
||||||
},
|
},
|
||||||
@ -69,10 +44,15 @@
|
|||||||
"App\\Tests\\": "tests/"
|
"App\\Tests\\": "tests/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"replace": {
|
||||||
|
"symfony/polyfill-ctype": "*",
|
||||||
|
"symfony/polyfill-iconv": "*",
|
||||||
|
"symfony/polyfill-php72": "*"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"auto-scripts": {
|
"auto-scripts": {
|
||||||
"cache:clear": "symfony-cmd",
|
"cache:clear": "symfony-cmd",
|
||||||
"assets:install --symlink --relative %PUBLIC_DIR%": "symfony-cmd"
|
"assets:install %PUBLIC_DIR%": "symfony-cmd"
|
||||||
},
|
},
|
||||||
"post-install-cmd": [
|
"post-install-cmd": [
|
||||||
"@auto-scripts"
|
"@auto-scripts"
|
||||||
@ -86,7 +66,14 @@
|
|||||||
},
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
"symfony": {
|
"symfony": {
|
||||||
"allow-contrib": true
|
"allow-contrib": false,
|
||||||
|
"require": "5.2.*"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/debug-bundle": "5.2.*",
|
||||||
|
"symfony/stopwatch": "^5.2",
|
||||||
|
"symfony/var-dumper": "5.2.*",
|
||||||
|
"symfony/web-profiler-bundle": "^5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3145
app/composer.lock
generated
3145
app/composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -2,18 +2,13 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
|
||||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
|
||||||
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
|
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
|
||||||
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
|
|
||||||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||||
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true],
|
|
||||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||||
DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true],
|
|
||||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
|
||||||
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
|
|
||||||
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
|
|
||||||
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
|
||||||
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
||||||
HtmlSanitizer\Bundle\HtmlSanitizerBundle::class => ['all' => true],
|
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||||
|
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||||
|
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||||
|
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
|
||||||
|
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true],
|
||||||
];
|
];
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
framework:
|
|
||||||
mailer:
|
|
||||||
# this disables delivery of messages entirely
|
|
||||||
dsn: 'null://null'
|
|
@ -1,19 +0,0 @@
|
|||||||
monolog:
|
|
||||||
handlers:
|
|
||||||
main:
|
|
||||||
type: stream
|
|
||||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
|
||||||
level: debug
|
|
||||||
channels: ["!event"]
|
|
||||||
# uncomment to get logging in your browser
|
|
||||||
# you may have to allow bigger header sizes in your Web server configuration
|
|
||||||
#firephp:
|
|
||||||
# type: firephp
|
|
||||||
# level: info
|
|
||||||
#chromephp:
|
|
||||||
# type: chromephp
|
|
||||||
# level: info
|
|
||||||
console:
|
|
||||||
type: console
|
|
||||||
process_psr_3_messages: false
|
|
||||||
channels: ["!event", "!doctrine", "!console"]
|
|
@ -4,7 +4,7 @@ doctrine:
|
|||||||
|
|
||||||
# IMPORTANT: You MUST configure your server version,
|
# IMPORTANT: You MUST configure your server version,
|
||||||
# either here or in the DATABASE_URL env var (see .env file)
|
# either here or in the DATABASE_URL env var (see .env file)
|
||||||
#server_version: '5.7'
|
#server_version: '13'
|
||||||
orm:
|
orm:
|
||||||
auto_generate_proxy_classes: true
|
auto_generate_proxy_classes: true
|
||||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# 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: true
|
#http_method_override: true
|
||||||
|
|
||||||
# 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.
|
||||||
# Remove or comment this section to explicitly disable session support.
|
# Remove or comment this section to explicitly disable session support.
|
||||||
@ -11,17 +11,7 @@ framework:
|
|||||||
cookie_secure: auto
|
cookie_secure: auto
|
||||||
cookie_samesite: lax
|
cookie_samesite: lax
|
||||||
|
|
||||||
# When using the HTTP Cache, ESI allows to render page fragments separately
|
#esi: true
|
||||||
# and with different cache configurations for each fragment
|
#fragments: true
|
||||||
# https://symfony.com/doc/current/http_cache/esi.html
|
|
||||||
esi: true
|
|
||||||
fragments: true
|
|
||||||
|
|
||||||
php_errors:
|
php_errors:
|
||||||
log: true
|
log: true
|
||||||
|
|
||||||
# The 'ide' option turns all of the file paths in an exception page
|
|
||||||
# into clickable links that open the given file using your favorite IDE.
|
|
||||||
# When 'ide' is set to null the file is opened in your web browser.
|
|
||||||
# See https://symfony.com/doc/current/reference/configuration/framework.html#ide
|
|
||||||
ide: null
|
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
html_sanitizer:
|
|
||||||
default_sanitizer: 'default'
|
|
||||||
sanitizers:
|
|
||||||
default:
|
|
||||||
# Read https://github.com/tgalopin/html-sanitizer/blob/master/docs/1-getting-started.md#extensions
|
|
||||||
# to learn more about which extensions you would like to enable.
|
|
||||||
extensions:
|
|
||||||
- 'basic'
|
|
||||||
- 'list'
|
|
||||||
- 'table'
|
|
||||||
- 'image'
|
|
||||||
- 'code'
|
|
||||||
# - 'iframe'
|
|
||||||
# - 'extra'
|
|
||||||
|
|
||||||
# Read https://github.com/tgalopin/html-sanitizer/blob/master/docs/3-configuration-reference.md
|
|
||||||
# to discover all the available options for each extension.
|
|
@ -1,3 +0,0 @@
|
|||||||
framework:
|
|
||||||
mailer:
|
|
||||||
dsn: '%env(MAILER_DSN)%'
|
|
@ -1,8 +0,0 @@
|
|||||||
# As of Symfony 5.1, deprecations are logged in the dedicated "deprecation" channel when it exists
|
|
||||||
#monolog:
|
|
||||||
# channels: [deprecation]
|
|
||||||
# handlers:
|
|
||||||
# deprecation:
|
|
||||||
# type: stream
|
|
||||||
# channels: [deprecation]
|
|
||||||
# path: "%kernel.logs_dir%/%kernel.environment%.deprecations.log"
|
|
@ -1,16 +0,0 @@
|
|||||||
monolog:
|
|
||||||
handlers:
|
|
||||||
main:
|
|
||||||
type: fingers_crossed
|
|
||||||
action_level: error
|
|
||||||
handler: nested
|
|
||||||
excluded_http_codes: [404, 405]
|
|
||||||
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
|
|
||||||
nested:
|
|
||||||
type: stream
|
|
||||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
|
||||||
level: debug
|
|
||||||
console:
|
|
||||||
type: console
|
|
||||||
process_psr_3_messages: false
|
|
||||||
channels: ["!event", "!doctrine"]
|
|
@ -1,63 +0,0 @@
|
|||||||
security:
|
|
||||||
encoders:
|
|
||||||
# Our user class and the algorithm we'll use to encode passwords
|
|
||||||
# 'auto' means to let Symfony choose the best possible password hasher (Argon2 or Bcrypt)
|
|
||||||
# https://symfony.com/doc/current/security.html#c-encoding-passwords
|
|
||||||
App\Entity\User: 'auto'
|
|
||||||
|
|
||||||
providers:
|
|
||||||
# https://symfony.com/doc/current/security/user_provider.html
|
|
||||||
# In this example, users are stored via Doctrine in the database
|
|
||||||
# To see the users at src/App/DataFixtures/ORM/LoadFixtures.php
|
|
||||||
# To load users from somewhere else: https://symfony.com/doc/current/security/user_provider.html#creating-a-custom-user-provider
|
|
||||||
database_users:
|
|
||||||
entity: { class: App\Entity\User, property: username }
|
|
||||||
|
|
||||||
# https://symfony.com/doc/current/security.html#a-authentication-firewalls
|
|
||||||
firewalls:
|
|
||||||
dev:
|
|
||||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
|
||||||
security: false
|
|
||||||
|
|
||||||
main:
|
|
||||||
# this firewall applies to all URLs
|
|
||||||
pattern: ^/
|
|
||||||
|
|
||||||
# but the firewall does not require login on every page
|
|
||||||
# denying access is done in access_control or in your controllers
|
|
||||||
anonymous: true
|
|
||||||
lazy: true
|
|
||||||
|
|
||||||
# The user provider to use.
|
|
||||||
provider: database_users
|
|
||||||
|
|
||||||
# This allows the user to login by submitting a username and password
|
|
||||||
# Reference: https://symfony.com/doc/current/security/form_login_setup.html
|
|
||||||
form_login:
|
|
||||||
# The route name that the login form submits to
|
|
||||||
check_path: security_login
|
|
||||||
# The name of the route where the login form lives
|
|
||||||
# When the user tries to access a protected page, they are redirected here
|
|
||||||
login_path: security_login
|
|
||||||
# Secure the login form against CSRF
|
|
||||||
# Reference: https://symfony.com/doc/current/security/csrf.html#csrf-protection-in-login-forms
|
|
||||||
csrf_token_generator: security.csrf.token_manager
|
|
||||||
# The page users are redirect to when there is no previous page stored in the
|
|
||||||
# session (for example when the users access directly to the login page).
|
|
||||||
default_target_path: blog_index
|
|
||||||
|
|
||||||
logout:
|
|
||||||
# The route name the user can go to in order to logout
|
|
||||||
path: security_logout
|
|
||||||
# The name of the route to redirect to after logging out
|
|
||||||
target: homepage
|
|
||||||
|
|
||||||
# Easy way to control access for large sections of your site
|
|
||||||
# Note: Only the *first* access control that matches will be used
|
|
||||||
access_control:
|
|
||||||
# this is a catch-all for the admin area
|
|
||||||
# additional security lives in the controllers
|
|
||||||
- { path: '^/(%app_locales%)/admin', roles: ROLE_ADMIN }
|
|
||||||
|
|
||||||
role_hierarchy:
|
|
||||||
ROLE_ADMIN: ROLE_USER
|
|
@ -1,4 +0,0 @@
|
|||||||
dama_doctrine_test:
|
|
||||||
enable_static_connection: true
|
|
||||||
enable_static_meta_data_cache: true
|
|
||||||
enable_static_query_cache: true
|
|
@ -1,4 +0,0 @@
|
|||||||
framework:
|
|
||||||
mailer:
|
|
||||||
# this disables delivery of messages entirely
|
|
||||||
dsn: 'null://null'
|
|
@ -1,12 +0,0 @@
|
|||||||
monolog:
|
|
||||||
handlers:
|
|
||||||
main:
|
|
||||||
type: fingers_crossed
|
|
||||||
action_level: error
|
|
||||||
handler: nested
|
|
||||||
excluded_http_codes: [404, 405]
|
|
||||||
channels: ["!event"]
|
|
||||||
nested:
|
|
||||||
type: stream
|
|
||||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
|
||||||
level: debug
|
|
@ -1,6 +0,0 @@
|
|||||||
# this configuration simplifies testing URLs protected by the security mechanism
|
|
||||||
# See https://symfony.com/doc/current/testing/http_authentication.html
|
|
||||||
security:
|
|
||||||
firewalls:
|
|
||||||
main:
|
|
||||||
http_basic: ~
|
|
@ -1,6 +0,0 @@
|
|||||||
framework:
|
|
||||||
default_locale: '%locale%'
|
|
||||||
translator:
|
|
||||||
default_path: '%kernel.project_dir%/translations'
|
|
||||||
fallbacks:
|
|
||||||
- '%locale%'
|
|
@ -1,5 +1,3 @@
|
|||||||
twig:
|
twig:
|
||||||
default_path: '%kernel.project_dir%/templates'
|
default_path: '%kernel.project_dir%/templates'
|
||||||
form_themes:
|
form_themes: ['bootstrap_4_layout.html.twig']
|
||||||
- 'form/layout.html.twig'
|
|
||||||
- 'form/fields.html.twig'
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
framework:
|
framework:
|
||||||
validation:
|
validation:
|
||||||
enable_annotations: true
|
|
||||||
email_validation_mode: html5
|
email_validation_mode: html5
|
||||||
|
|
||||||
# Enables validator auto-mapping support.
|
# Enables validator auto-mapping support.
|
||||||
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
|
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
|
||||||
auto_mapping:
|
#auto_mapping:
|
||||||
App\Entity\: []
|
# App\Entity\: []
|
||||||
|
@ -4,16 +4,21 @@ webpack_encore:
|
|||||||
# If multiple builds are defined (as shown below), you can disable the default build:
|
# If multiple builds are defined (as shown below), you can disable the default build:
|
||||||
# output_path: false
|
# output_path: false
|
||||||
|
|
||||||
# if using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials')
|
# 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'
|
# crossorigin: 'anonymous'
|
||||||
|
|
||||||
# preload all rendered script and link tags automatically via the http2 Link header
|
# Preload all rendered script and link tags automatically via the HTTP/2 Link header
|
||||||
# preload: true
|
# preload: true
|
||||||
|
|
||||||
# Throw an exception if the entrypoints.json file is missing or an entry is missing from the data
|
# Throw an exception if the entrypoints.json file is missing or an entry is missing from the data
|
||||||
# strict_mode: false
|
# strict_mode: false
|
||||||
|
|
||||||
# if you have multiple builds:
|
# If you have multiple builds:
|
||||||
# builds:
|
# builds:
|
||||||
# pass "frontend" as the 3rg arg to the Twig functions
|
# pass "frontend" as the 3rg arg to the Twig functions
|
||||||
# {{ encore_entry_script_tags('entry1', null, 'frontend') }}
|
# {{ encore_entry_script_tags('entry1', null, 'frontend') }}
|
||||||
|
@ -1,12 +1,3 @@
|
|||||||
# These lines define a route using YAML configuration. The controller used by
|
#index:
|
||||||
# the route (FrameworkBundle:Template:template) is a convenient shortcut when
|
# path: /
|
||||||
# the template can be rendered without executing any logic in your own controller.
|
# controller: App\Controller\DefaultController::index
|
||||||
# See https://symfony.com/doc/current/templates.html#rendering-a-template-directly-from-a-route
|
|
||||||
homepage:
|
|
||||||
path: /{_locale}
|
|
||||||
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController::templateAction
|
|
||||||
requirements:
|
|
||||||
_locale: '%app_locales%'
|
|
||||||
defaults:
|
|
||||||
template: default/homepage.html.twig
|
|
||||||
_locale: '%locale%'
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
controllers:
|
controllers:
|
||||||
resource: '../../src/Controller/'
|
resource: ../../src/Controller/
|
||||||
|
type: annotation
|
||||||
|
|
||||||
|
kernel:
|
||||||
|
resource: ../../src/Kernel.php
|
||||||
type: annotation
|
type: annotation
|
||||||
prefix: /{_locale}
|
|
||||||
requirements:
|
|
||||||
_locale: '%app_locales%'
|
|
||||||
defaults:
|
|
||||||
_locale: '%locale%'
|
|
||||||
|
@ -4,20 +4,12 @@
|
|||||||
# Put parameters here that don't need to change on each machine where the app is deployed
|
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||||
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
|
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
|
||||||
parameters:
|
parameters:
|
||||||
locale: 'en'
|
|
||||||
# This parameter defines the codes of the locales (languages) enabled in the application
|
|
||||||
app_locales: ar|en|fr|de|es|cs|nl|ru|uk|ro|pt_BR|pl|it|ja|id|ca|sl|hr|zh_CN|bg|tr|lt
|
|
||||||
app.notifications.email_sender: anonymous@example.com
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# default configuration for services in *this* file
|
# default configuration for services in *this* file
|
||||||
_defaults:
|
_defaults:
|
||||||
autowire: true # Automatically injects dependencies in your services.
|
autowire: true # Automatically injects dependencies in your services.
|
||||||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||||
bind: # defines the scalar arguments once and apply them to any service defined/created in this file
|
|
||||||
string $locales: '%app_locales%'
|
|
||||||
string $defaultLocale: '%locale%'
|
|
||||||
string $emailSender: '%app.notifications.email_sender%'
|
|
||||||
|
|
||||||
# makes classes in src/ available to be used as services
|
# makes classes in src/ available to be used as services
|
||||||
# this creates a service per class whose id is the fully-qualified class name
|
# this creates a service per class whose id is the fully-qualified class name
|
||||||
@ -35,7 +27,5 @@ services:
|
|||||||
resource: '../src/Controller/'
|
resource: '../src/Controller/'
|
||||||
tags: ['controller.service_arguments']
|
tags: ['controller.service_arguments']
|
||||||
|
|
||||||
# when the service definition only contains arguments, you can omit the
|
# add more service definitions when explicit configuration is needed
|
||||||
# 'arguments' key and define the arguments just below the service class
|
# please note that last definitions always *replace* previous ones
|
||||||
App\EventSubscriber\CommentNotificationSubscriber:
|
|
||||||
$sender: '%app.notifications.email_sender%'
|
|
||||||
|
34
app/migrations/Version20210218191150.php
Normal file
34
app/migrations/Version20210218191150.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?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 Version20210218191150 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 SEQUENCE beer_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||||
|
$this->addSql('CREATE TABLE beer (id INT NOT NULL, name VARCHAR(255) NOT NULL, alcool DOUBLE PRECISION DEFAULT NULL, PRIMARY KEY(id))');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema) : void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE SCHEMA public');
|
||||||
|
$this->addSql('DROP SEQUENCE beer_id_seq CASCADE');
|
||||||
|
$this->addSql('DROP TABLE beer');
|
||||||
|
}
|
||||||
|
}
|
8650
app/package-lock.json
generated
Normal file
8650
app/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,27 +1,23 @@
|
|||||||
{
|
{
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.8.1",
|
"@symfony/stimulus-bridge": "^2.0.0",
|
||||||
"@symfony/webpack-encore": "^0.31.0",
|
"@symfony/webpack-encore": "^1.0.0",
|
||||||
"bloodhound-js": "^1.2.3",
|
|
||||||
"bootstrap-sass": "^3.3.7",
|
|
||||||
"bootstrap-tagsinput": "^0.7.1",
|
|
||||||
"bootswatch": "^3.3.7",
|
|
||||||
"core-js": "^3.0.0",
|
"core-js": "^3.0.0",
|
||||||
"eonasdan-bootstrap-datetimepicker": "^4.17.47",
|
"regenerator-runtime": "^0.13.2",
|
||||||
"highlight.js": "^10.4.1",
|
"sass": "^1.32.7",
|
||||||
"imports-loader": "^0.8.0",
|
"sass-loader": "^11.0.1",
|
||||||
"jquery": "^3.5.1",
|
"stimulus": "^2.0.0",
|
||||||
"lato-font": "^3.0.0",
|
"webpack-notifier": "^1.6.0"
|
||||||
"node-sass": "^4.9.3",
|
|
||||||
"sass-loader": "^9.0.1",
|
|
||||||
"typeahead.js": "^0.11.1"
|
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "UNLICENSED",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev-server": "encore dev-server",
|
"dev-server": "encore dev-server",
|
||||||
"dev": "encore dev",
|
"dev": "encore dev",
|
||||||
"watch": "encore dev --watch",
|
"watch": "encore dev --watch",
|
||||||
"build": "encore production --progress"
|
"build": "encore production --progress"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"yarn": "^1.22.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
|
|
||||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:noNamespaceSchemaLocation="bin/.phpunit/phpunit.xsd"
|
|
||||||
backupGlobals="false"
|
|
||||||
colors="true"
|
|
||||||
bootstrap="tests/bootstrap.php"
|
|
||||||
>
|
|
||||||
<php>
|
|
||||||
<ini name="error_reporting" value="-1" />
|
|
||||||
<server name="APP_ENV" value="test" force="true" />
|
|
||||||
<server name="SHELL_VERBOSITY" value="-1" />
|
|
||||||
<server name="SYMFONY_PHPUNIT_REMOVE" value="" />
|
|
||||||
<server name="SYMFONY_PHPUNIT_VERSION" value="8" />
|
|
||||||
</php>
|
|
||||||
|
|
||||||
<testsuites>
|
|
||||||
<testsuite name="Project Test Suite">
|
|
||||||
<directory>tests</directory>
|
|
||||||
</testsuite>
|
|
||||||
</testsuites>
|
|
||||||
|
|
||||||
<filter>
|
|
||||||
<whitelist>
|
|
||||||
<directory>src</directory>
|
|
||||||
</whitelist>
|
|
||||||
</filter>
|
|
||||||
|
|
||||||
<listeners>
|
|
||||||
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
|
|
||||||
</listeners>
|
|
||||||
|
|
||||||
<extensions>
|
|
||||||
<!-- it begins a database transaction before every testcase and rolls it back after
|
|
||||||
the test finished, so tests can manipulate the database without affecting other tests -->
|
|
||||||
<extension class="\DAMA\DoctrineTestBundle\PHPUnit\PHPUnitExtension" />
|
|
||||||
</extensions>
|
|
||||||
</phpunit>
|
|
@ -1,66 +0,0 @@
|
|||||||
# Use the front controller as index file. It serves as a fallback solution when
|
|
||||||
# every other rewrite/redirect fails (e.g. in an aliased environment without
|
|
||||||
# mod_rewrite). Additionally, this reduces the matching process for the
|
|
||||||
# start page (path "/") because otherwise Apache will apply the rewriting rules
|
|
||||||
# to each configured DirectoryIndex file (e.g. index.php, index.html, index.pl).
|
|
||||||
DirectoryIndex index.php
|
|
||||||
|
|
||||||
# By default, Apache does not evaluate symbolic links if you did not enable this
|
|
||||||
# feature in your server configuration. Uncomment the following line if you
|
|
||||||
# install assets as symlinks or if you experience problems related to symlinks
|
|
||||||
# when compiling LESS/Sass/CoffeScript assets.
|
|
||||||
# Options +FollowSymlinks
|
|
||||||
|
|
||||||
# Disabling MultiViews prevents unwanted negotiation, e.g. "/index" should not resolve
|
|
||||||
# to the front controller "/index.php" but be rewritten to "/index.php/index".
|
|
||||||
<IfModule mod_negotiation.c>
|
|
||||||
Options -MultiViews
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
<IfModule mod_rewrite.c>
|
|
||||||
RewriteEngine On
|
|
||||||
|
|
||||||
# Determine the RewriteBase automatically and set it as environment variable.
|
|
||||||
# If you are using Apache aliases to do mass virtual hosting or installed the
|
|
||||||
# project in a subdirectory, the base path will be prepended to allow proper
|
|
||||||
# resolution of the index.php file and to redirect to the correct URI. It will
|
|
||||||
# work in environments without path prefix as well, providing a safe, one-size
|
|
||||||
# fits all solution. But as you do not need it in this case, you can comment
|
|
||||||
# the following 2 lines to eliminate the overhead.
|
|
||||||
RewriteCond %{REQUEST_URI}::$0 ^(/.+)/(.*)::\2$
|
|
||||||
RewriteRule .* - [E=BASE:%1]
|
|
||||||
|
|
||||||
# Sets the HTTP_AUTHORIZATION header removed by Apache
|
|
||||||
RewriteCond %{HTTP:Authorization} .+
|
|
||||||
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0]
|
|
||||||
|
|
||||||
# Redirect to URI without front controller to prevent duplicate content
|
|
||||||
# (with and without `/index.php`). Only do this redirect on the initial
|
|
||||||
# rewrite by Apache and not on subsequent cycles. Otherwise we would get an
|
|
||||||
# endless redirect loop (request -> rewrite to front controller ->
|
|
||||||
# redirect -> request -> ...).
|
|
||||||
# So in case you get a "too many redirects" error or you always get redirected
|
|
||||||
# to the start page because your Apache does not expose the REDIRECT_STATUS
|
|
||||||
# environment variable, you have 2 choices:
|
|
||||||
# - disable this feature by commenting the following 2 lines or
|
|
||||||
# - use Apache >= 2.3.9 and replace all L flags by END flags and remove the
|
|
||||||
# following RewriteCond (best solution)
|
|
||||||
RewriteCond %{ENV:REDIRECT_STATUS} =""
|
|
||||||
RewriteRule ^index\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L]
|
|
||||||
|
|
||||||
# If the requested filename exists, simply serve it.
|
|
||||||
# We only want to let Apache serve files and not directories.
|
|
||||||
# Rewrite all other queries to the front controller.
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-f
|
|
||||||
RewriteRule ^ %{ENV:BASE}/index.php [L]
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
<IfModule !mod_rewrite.c>
|
|
||||||
<IfModule mod_alias.c>
|
|
||||||
# When mod_rewrite is not available, we instruct a temporary redirect of
|
|
||||||
# the start page to the front controller explicitly so that the website
|
|
||||||
# and the generated links can still be used.
|
|
||||||
RedirectMatch 307 ^/$ /index.php/
|
|
||||||
# RedirectTemp cannot be used instead
|
|
||||||
</IfModule>
|
|
||||||
</IfModule>
|
|
Binary file not shown.
Before Width: | Height: | Size: 10 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB |
@ -15,14 +15,6 @@ if ($_SERVER['APP_DEBUG']) {
|
|||||||
Debug::enable();
|
Debug::enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? false) {
|
|
||||||
Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? false) {
|
|
||||||
Request::setTrustedHosts([$trustedHosts]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
|
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
|
||||||
$request = Request::createFromGlobals();
|
$request = Request::createFromGlobals();
|
||||||
$response = $kernel->handle($request);
|
$response = $kernel->handle($request);
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
# www.robotstxt.org/
|
|
||||||
# www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449
|
|
||||||
|
|
||||||
User-agent: *
|
|
@ -1,263 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Command;
|
|
||||||
|
|
||||||
use App\Entity\User;
|
|
||||||
use App\Repository\UserRepository;
|
|
||||||
use App\Utils\Validator;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
|
||||||
use Symfony\Component\Console\Exception\RuntimeException;
|
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
|
||||||
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
|
|
||||||
use Symfony\Component\Stopwatch\Stopwatch;
|
|
||||||
use function Symfony\Component\String\u;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A console command that creates users and stores them in the database.
|
|
||||||
*
|
|
||||||
* To use this command, open a terminal window, enter into your project
|
|
||||||
* directory and execute the following:
|
|
||||||
*
|
|
||||||
* $ php bin/console app:add-user
|
|
||||||
*
|
|
||||||
* To output detailed information, increase the command verbosity:
|
|
||||||
*
|
|
||||||
* $ php bin/console app:add-user -vv
|
|
||||||
*
|
|
||||||
* See https://symfony.com/doc/current/console.html
|
|
||||||
*
|
|
||||||
* We use the default services.yaml configuration, so command classes are registered as services.
|
|
||||||
* See https://symfony.com/doc/current/console/commands_as_services.html
|
|
||||||
*
|
|
||||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
|
||||||
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
|
||||||
*/
|
|
||||||
class AddUserCommand extends Command
|
|
||||||
{
|
|
||||||
// to make your command lazily loaded, configure the $defaultName static property,
|
|
||||||
// so it will be instantiated only when the command is actually called.
|
|
||||||
protected static $defaultName = 'app:add-user';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var SymfonyStyle
|
|
||||||
*/
|
|
||||||
private $io;
|
|
||||||
|
|
||||||
private $entityManager;
|
|
||||||
private $passwordEncoder;
|
|
||||||
private $validator;
|
|
||||||
private $users;
|
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $em, UserPasswordEncoderInterface $encoder, Validator $validator, UserRepository $users)
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
|
|
||||||
$this->entityManager = $em;
|
|
||||||
$this->passwordEncoder = $encoder;
|
|
||||||
$this->validator = $validator;
|
|
||||||
$this->users = $users;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function configure(): void
|
|
||||||
{
|
|
||||||
$this
|
|
||||||
->setDescription('Creates users and stores them in the database')
|
|
||||||
->setHelp($this->getCommandHelp())
|
|
||||||
// commands can optionally define arguments and/or options (mandatory and optional)
|
|
||||||
// see https://symfony.com/doc/current/components/console/console_arguments.html
|
|
||||||
->addArgument('username', InputArgument::OPTIONAL, 'The username of the new user')
|
|
||||||
->addArgument('password', InputArgument::OPTIONAL, 'The plain password of the new user')
|
|
||||||
->addArgument('email', InputArgument::OPTIONAL, 'The email of the new user')
|
|
||||||
->addArgument('full-name', InputArgument::OPTIONAL, 'The full name of the new user')
|
|
||||||
->addOption('admin', null, InputOption::VALUE_NONE, 'If set, the user is created as an administrator')
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This optional method is the first one executed for a command after configure()
|
|
||||||
* and is useful to initialize properties based on the input arguments and options.
|
|
||||||
*/
|
|
||||||
protected function initialize(InputInterface $input, OutputInterface $output): void
|
|
||||||
{
|
|
||||||
// SymfonyStyle is an optional feature that Symfony provides so you can
|
|
||||||
// apply a consistent look to the commands of your application.
|
|
||||||
// See https://symfony.com/doc/current/console/style.html
|
|
||||||
$this->io = new SymfonyStyle($input, $output);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is executed after initialize() and before execute(). Its purpose
|
|
||||||
* is to check if some of the options/arguments are missing and interactively
|
|
||||||
* ask the user for those values.
|
|
||||||
*
|
|
||||||
* This method is completely optional. If you are developing an internal console
|
|
||||||
* command, you probably should not implement this method because it requires
|
|
||||||
* quite a lot of work. However, if the command is meant to be used by external
|
|
||||||
* users, this method is a nice way to fall back and prevent errors.
|
|
||||||
*/
|
|
||||||
protected function interact(InputInterface $input, OutputInterface $output)
|
|
||||||
{
|
|
||||||
if (null !== $input->getArgument('username') && null !== $input->getArgument('password') && null !== $input->getArgument('email') && null !== $input->getArgument('full-name')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->io->title('Add User Command Interactive Wizard');
|
|
||||||
$this->io->text([
|
|
||||||
'If you prefer to not use this interactive wizard, provide the',
|
|
||||||
'arguments required by this command as follows:',
|
|
||||||
'',
|
|
||||||
' $ php bin/console app:add-user username password email@example.com',
|
|
||||||
'',
|
|
||||||
'Now we\'ll ask you for the value of all the missing command arguments.',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Ask for the username if it's not defined
|
|
||||||
$username = $input->getArgument('username');
|
|
||||||
if (null !== $username) {
|
|
||||||
$this->io->text(' > <info>Username</info>: '.$username);
|
|
||||||
} else {
|
|
||||||
$username = $this->io->ask('Username', null, [$this->validator, 'validateUsername']);
|
|
||||||
$input->setArgument('username', $username);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask for the password if it's not defined
|
|
||||||
$password = $input->getArgument('password');
|
|
||||||
if (null !== $password) {
|
|
||||||
$this->io->text(' > <info>Password</info>: '.u('*')->repeat(u($password)->length()));
|
|
||||||
} else {
|
|
||||||
$password = $this->io->askHidden('Password (your type will be hidden)', [$this->validator, 'validatePassword']);
|
|
||||||
$input->setArgument('password', $password);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask for the email if it's not defined
|
|
||||||
$email = $input->getArgument('email');
|
|
||||||
if (null !== $email) {
|
|
||||||
$this->io->text(' > <info>Email</info>: '.$email);
|
|
||||||
} else {
|
|
||||||
$email = $this->io->ask('Email', null, [$this->validator, 'validateEmail']);
|
|
||||||
$input->setArgument('email', $email);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask for the full name if it's not defined
|
|
||||||
$fullName = $input->getArgument('full-name');
|
|
||||||
if (null !== $fullName) {
|
|
||||||
$this->io->text(' > <info>Full Name</info>: '.$fullName);
|
|
||||||
} else {
|
|
||||||
$fullName = $this->io->ask('Full Name', null, [$this->validator, 'validateFullName']);
|
|
||||||
$input->setArgument('full-name', $fullName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is executed after interact() and initialize(). It usually
|
|
||||||
* contains the logic to execute to complete this command task.
|
|
||||||
*/
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
|
||||||
{
|
|
||||||
$stopwatch = new Stopwatch();
|
|
||||||
$stopwatch->start('add-user-command');
|
|
||||||
|
|
||||||
$username = $input->getArgument('username');
|
|
||||||
$plainPassword = $input->getArgument('password');
|
|
||||||
$email = $input->getArgument('email');
|
|
||||||
$fullName = $input->getArgument('full-name');
|
|
||||||
$isAdmin = $input->getOption('admin');
|
|
||||||
|
|
||||||
// make sure to validate the user data is correct
|
|
||||||
$this->validateUserData($username, $plainPassword, $email, $fullName);
|
|
||||||
|
|
||||||
// create the user and encode its password
|
|
||||||
$user = new User();
|
|
||||||
$user->setFullName($fullName);
|
|
||||||
$user->setUsername($username);
|
|
||||||
$user->setEmail($email);
|
|
||||||
$user->setRoles([$isAdmin ? 'ROLE_ADMIN' : 'ROLE_USER']);
|
|
||||||
|
|
||||||
// See https://symfony.com/doc/current/security.html#c-encoding-passwords
|
|
||||||
$encodedPassword = $this->passwordEncoder->encodePassword($user, $plainPassword);
|
|
||||||
$user->setPassword($encodedPassword);
|
|
||||||
|
|
||||||
$this->entityManager->persist($user);
|
|
||||||
$this->entityManager->flush();
|
|
||||||
|
|
||||||
$this->io->success(sprintf('%s was successfully created: %s (%s)', $isAdmin ? 'Administrator user' : 'User', $user->getUsername(), $user->getEmail()));
|
|
||||||
|
|
||||||
$event = $stopwatch->stop('add-user-command');
|
|
||||||
if ($output->isVerbose()) {
|
|
||||||
$this->io->comment(sprintf('New user database id: %d / Elapsed time: %.2f ms / Consumed memory: %.2f MB', $user->getId(), $event->getDuration(), $event->getMemory() / (1024 ** 2)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function validateUserData($username, $plainPassword, $email, $fullName): void
|
|
||||||
{
|
|
||||||
// first check if a user with the same username already exists.
|
|
||||||
$existingUser = $this->users->findOneBy(['username' => $username]);
|
|
||||||
|
|
||||||
if (null !== $existingUser) {
|
|
||||||
throw new RuntimeException(sprintf('There is already a user registered with the "%s" username.', $username));
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate password and email if is not this input means interactive.
|
|
||||||
$this->validator->validatePassword($plainPassword);
|
|
||||||
$this->validator->validateEmail($email);
|
|
||||||
$this->validator->validateFullName($fullName);
|
|
||||||
|
|
||||||
// check if a user with the same email already exists.
|
|
||||||
$existingEmail = $this->users->findOneBy(['email' => $email]);
|
|
||||||
|
|
||||||
if (null !== $existingEmail) {
|
|
||||||
throw new RuntimeException(sprintf('There is already a user registered with the "%s" email.', $email));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The command help is usually included in the configure() method, but when
|
|
||||||
* it's too long, it's better to define a separate method to maintain the
|
|
||||||
* code readability.
|
|
||||||
*/
|
|
||||||
private function getCommandHelp(): string
|
|
||||||
{
|
|
||||||
return <<<'HELP'
|
|
||||||
The <info>%command.name%</info> command creates new users and saves them in the database:
|
|
||||||
|
|
||||||
<info>php %command.full_name%</info> <comment>username password email</comment>
|
|
||||||
|
|
||||||
By default the command creates regular users. To create administrator users,
|
|
||||||
add the <comment>--admin</comment> option:
|
|
||||||
|
|
||||||
<info>php %command.full_name%</info> username password email <comment>--admin</comment>
|
|
||||||
|
|
||||||
If you omit any of the three required arguments, the command will ask you to
|
|
||||||
provide the missing values:
|
|
||||||
|
|
||||||
# command will ask you for the email
|
|
||||||
<info>php %command.full_name%</info> <comment>username password</comment>
|
|
||||||
|
|
||||||
# command will ask you for the email and password
|
|
||||||
<info>php %command.full_name%</info> <comment>username</comment>
|
|
||||||
|
|
||||||
# command will ask you for all arguments
|
|
||||||
<info>php %command.full_name%</info>
|
|
||||||
|
|
||||||
HELP;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,132 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Command;
|
|
||||||
|
|
||||||
use App\Entity\User;
|
|
||||||
use App\Repository\UserRepository;
|
|
||||||
use App\Utils\Validator;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
|
||||||
use Symfony\Component\Console\Exception\RuntimeException;
|
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A console command that deletes users from the database.
|
|
||||||
*
|
|
||||||
* To use this command, open a terminal window, enter into your project
|
|
||||||
* directory and execute the following:
|
|
||||||
*
|
|
||||||
* $ php bin/console app:delete-user
|
|
||||||
*
|
|
||||||
* Check out the code of the src/Command/AddUserCommand.php file for
|
|
||||||
* the full explanation about Symfony commands.
|
|
||||||
*
|
|
||||||
* See https://symfony.com/doc/current/console.html
|
|
||||||
*
|
|
||||||
* @author Oleg Voronkovich <oleg-voronkovich@yandex.ru>
|
|
||||||
*/
|
|
||||||
class DeleteUserCommand extends Command
|
|
||||||
{
|
|
||||||
protected static $defaultName = 'app:delete-user';
|
|
||||||
|
|
||||||
/** @var SymfonyStyle */
|
|
||||||
private $io;
|
|
||||||
private $entityManager;
|
|
||||||
private $validator;
|
|
||||||
private $users;
|
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $em, Validator $validator, UserRepository $users)
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
|
|
||||||
$this->entityManager = $em;
|
|
||||||
$this->validator = $validator;
|
|
||||||
$this->users = $users;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function configure(): void
|
|
||||||
{
|
|
||||||
$this
|
|
||||||
->setDescription('Deletes users from the database')
|
|
||||||
->addArgument('username', InputArgument::REQUIRED, 'The username of an existing user')
|
|
||||||
->setHelp(<<<'HELP'
|
|
||||||
The <info>%command.name%</info> command deletes users from the database:
|
|
||||||
|
|
||||||
<info>php %command.full_name%</info> <comment>username</comment>
|
|
||||||
|
|
||||||
If you omit the argument, the command will ask you to
|
|
||||||
provide the missing value:
|
|
||||||
|
|
||||||
<info>php %command.full_name%</info>
|
|
||||||
HELP
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function initialize(InputInterface $input, OutputInterface $output): void
|
|
||||||
{
|
|
||||||
// SymfonyStyle is an optional feature that Symfony provides so you can
|
|
||||||
// apply a consistent look to the commands of your application.
|
|
||||||
// See https://symfony.com/doc/current/console/style.html
|
|
||||||
$this->io = new SymfonyStyle($input, $output);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function interact(InputInterface $input, OutputInterface $output)
|
|
||||||
{
|
|
||||||
if (null !== $input->getArgument('username')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->io->title('Delete User Command Interactive Wizard');
|
|
||||||
$this->io->text([
|
|
||||||
'If you prefer to not use this interactive wizard, provide the',
|
|
||||||
'arguments required by this command as follows:',
|
|
||||||
'',
|
|
||||||
' $ php bin/console app:delete-user username',
|
|
||||||
'',
|
|
||||||
'Now we\'ll ask you for the value of all the missing command arguments.',
|
|
||||||
'',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$username = $this->io->ask('Username', null, [$this->validator, 'validateUsername']);
|
|
||||||
$input->setArgument('username', $username);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
|
||||||
{
|
|
||||||
$username = $this->validator->validateUsername($input->getArgument('username'));
|
|
||||||
|
|
||||||
/** @var User $user */
|
|
||||||
$user = $this->users->findOneByUsername($username);
|
|
||||||
|
|
||||||
if (null === $user) {
|
|
||||||
throw new RuntimeException(sprintf('User with username "%s" not found.', $username));
|
|
||||||
}
|
|
||||||
|
|
||||||
// After an entity has been removed its in-memory state is the same
|
|
||||||
// as before the removal, except for generated identifiers.
|
|
||||||
// See https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/working-with-objects.html#removing-entities
|
|
||||||
$userId = $user->getId();
|
|
||||||
|
|
||||||
$this->entityManager->remove($user);
|
|
||||||
$this->entityManager->flush();
|
|
||||||
|
|
||||||
$this->io->success(sprintf('User "%s" (ID: %d, email: %s) was successfully deleted.', $user->getUsername(), $userId, $user->getEmail()));
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,146 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Command;
|
|
||||||
|
|
||||||
use App\Entity\User;
|
|
||||||
use App\Repository\UserRepository;
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
|
||||||
use Symfony\Component\Console\Output\BufferedOutput;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
|
||||||
use Symfony\Component\Mailer\MailerInterface;
|
|
||||||
use Symfony\Component\Mime\Email;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A console command that lists all the existing users.
|
|
||||||
*
|
|
||||||
* To use this command, open a terminal window, enter into your project directory
|
|
||||||
* and execute the following:
|
|
||||||
*
|
|
||||||
* $ php bin/console app:list-users
|
|
||||||
*
|
|
||||||
* Check out the code of the src/Command/AddUserCommand.php file for
|
|
||||||
* the full explanation about Symfony commands.
|
|
||||||
*
|
|
||||||
* See https://symfony.com/doc/current/console.html
|
|
||||||
*
|
|
||||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
|
||||||
*/
|
|
||||||
class ListUsersCommand extends Command
|
|
||||||
{
|
|
||||||
// a good practice is to use the 'app:' prefix to group all your custom application commands
|
|
||||||
protected static $defaultName = 'app:list-users';
|
|
||||||
|
|
||||||
private $mailer;
|
|
||||||
private $emailSender;
|
|
||||||
private $users;
|
|
||||||
|
|
||||||
public function __construct(MailerInterface $mailer, string $emailSender, UserRepository $users)
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
|
|
||||||
$this->mailer = $mailer;
|
|
||||||
$this->emailSender = $emailSender;
|
|
||||||
$this->users = $users;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function configure(): void
|
|
||||||
{
|
|
||||||
$this
|
|
||||||
->setDescription('Lists all the existing users')
|
|
||||||
->setHelp(<<<'HELP'
|
|
||||||
The <info>%command.name%</info> command lists all the users registered in the application:
|
|
||||||
|
|
||||||
<info>php %command.full_name%</info>
|
|
||||||
|
|
||||||
By default the command only displays the 50 most recent users. Set the number of
|
|
||||||
results to display with the <comment>--max-results</comment> option:
|
|
||||||
|
|
||||||
<info>php %command.full_name%</info> <comment>--max-results=2000</comment>
|
|
||||||
|
|
||||||
In addition to displaying the user list, you can also send this information to
|
|
||||||
the email address specified in the <comment>--send-to</comment> option:
|
|
||||||
|
|
||||||
<info>php %command.full_name%</info> <comment>--send-to=fabien@symfony.com</comment>
|
|
||||||
|
|
||||||
HELP
|
|
||||||
)
|
|
||||||
// commands can optionally define arguments and/or options (mandatory and optional)
|
|
||||||
// see https://symfony.com/doc/current/components/console/console_arguments.html
|
|
||||||
->addOption('max-results', null, InputOption::VALUE_OPTIONAL, 'Limits the number of users listed', 50)
|
|
||||||
->addOption('send-to', null, InputOption::VALUE_OPTIONAL, 'If set, the result is sent to the given email address')
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is executed after initialize(). It usually contains the logic
|
|
||||||
* to execute to complete this command task.
|
|
||||||
*/
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
|
||||||
{
|
|
||||||
$maxResults = $input->getOption('max-results');
|
|
||||||
// Use ->findBy() instead of ->findAll() to allow result sorting and limiting
|
|
||||||
$allUsers = $this->users->findBy([], ['id' => 'DESC'], $maxResults);
|
|
||||||
|
|
||||||
// Doctrine query returns an array of objects and we need an array of plain arrays
|
|
||||||
$usersAsPlainArrays = array_map(function (User $user) {
|
|
||||||
return [
|
|
||||||
$user->getId(),
|
|
||||||
$user->getFullName(),
|
|
||||||
$user->getUsername(),
|
|
||||||
$user->getEmail(),
|
|
||||||
implode(', ', $user->getRoles()),
|
|
||||||
];
|
|
||||||
}, $allUsers);
|
|
||||||
|
|
||||||
// In your console commands you should always use the regular output type,
|
|
||||||
// which outputs contents directly in the console window. However, this
|
|
||||||
// command uses the BufferedOutput type instead, to be able to get the output
|
|
||||||
// contents before displaying them. This is needed because the command allows
|
|
||||||
// to send the list of users via email with the '--send-to' option
|
|
||||||
$bufferedOutput = new BufferedOutput();
|
|
||||||
$io = new SymfonyStyle($input, $bufferedOutput);
|
|
||||||
$io->table(
|
|
||||||
['ID', 'Full Name', 'Username', 'Email', 'Roles'],
|
|
||||||
$usersAsPlainArrays
|
|
||||||
);
|
|
||||||
|
|
||||||
// instead of just displaying the table of users, store its contents in a variable
|
|
||||||
$usersAsATable = $bufferedOutput->fetch();
|
|
||||||
$output->write($usersAsATable);
|
|
||||||
|
|
||||||
if (null !== $email = $input->getOption('send-to')) {
|
|
||||||
$this->sendReport($usersAsATable, $email);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends the given $contents to the $recipient email address.
|
|
||||||
*/
|
|
||||||
private function sendReport(string $contents, string $recipient): void
|
|
||||||
{
|
|
||||||
$email = (new Email())
|
|
||||||
->from($this->emailSender)
|
|
||||||
->to($recipient)
|
|
||||||
->subject(sprintf('app:list-users report (%s)', date('Y-m-d H:i:s')))
|
|
||||||
->text($contents);
|
|
||||||
|
|
||||||
$this->mailer->send($email);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,177 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Controller\Admin;
|
|
||||||
|
|
||||||
use App\Entity\Post;
|
|
||||||
use App\Form\PostType;
|
|
||||||
use App\Repository\PostRepository;
|
|
||||||
use App\Security\PostVoter;
|
|
||||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller used to manage blog contents in the backend.
|
|
||||||
*
|
|
||||||
* Please note that the application backend is developed manually for learning
|
|
||||||
* purposes. However, in your real Symfony application you should use any of the
|
|
||||||
* existing bundles that let you generate ready-to-use backends without effort.
|
|
||||||
*
|
|
||||||
* See http://knpbundles.com/keyword/admin
|
|
||||||
*
|
|
||||||
* @Route("/admin/post")
|
|
||||||
* @IsGranted("ROLE_ADMIN")
|
|
||||||
*
|
|
||||||
* @author Ryan Weaver <weaverryan@gmail.com>
|
|
||||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
|
||||||
*/
|
|
||||||
class BlogController extends AbstractController
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Lists all Post entities.
|
|
||||||
*
|
|
||||||
* This controller responds to two different routes with the same URL:
|
|
||||||
* * 'admin_post_index' is the route with a name that follows the same
|
|
||||||
* structure as the rest of the controllers of this class.
|
|
||||||
* * 'admin_index' is a nice shortcut to the backend homepage. This allows
|
|
||||||
* to create simpler links in the templates. Moreover, in the future we
|
|
||||||
* could move this annotation to any other controller while maintaining
|
|
||||||
* the route name and therefore, without breaking any existing link.
|
|
||||||
*
|
|
||||||
* @Route("/", methods="GET", name="admin_index")
|
|
||||||
* @Route("/", methods="GET", name="admin_post_index")
|
|
||||||
*/
|
|
||||||
public function index(PostRepository $posts): Response
|
|
||||||
{
|
|
||||||
$authorPosts = $posts->findBy(['author' => $this->getUser()], ['publishedAt' => 'DESC']);
|
|
||||||
|
|
||||||
return $this->render('admin/blog/index.html.twig', ['posts' => $authorPosts]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Post entity.
|
|
||||||
*
|
|
||||||
* @Route("/new", methods="GET|POST", name="admin_post_new")
|
|
||||||
*
|
|
||||||
* NOTE: the Method annotation is optional, but it's a recommended practice
|
|
||||||
* to constraint the HTTP methods each controller responds to (by default
|
|
||||||
* it responds to all methods).
|
|
||||||
*/
|
|
||||||
public function new(Request $request): Response
|
|
||||||
{
|
|
||||||
$post = new Post();
|
|
||||||
$post->setAuthor($this->getUser());
|
|
||||||
|
|
||||||
// See https://symfony.com/doc/current/form/multiple_buttons.html
|
|
||||||
$form = $this->createForm(PostType::class, $post)
|
|
||||||
->add('saveAndCreateNew', SubmitType::class);
|
|
||||||
|
|
||||||
$form->handleRequest($request);
|
|
||||||
|
|
||||||
// the isSubmitted() method is completely optional because the other
|
|
||||||
// isValid() method already checks whether the form is submitted.
|
|
||||||
// However, we explicitly add it to improve code readability.
|
|
||||||
// See https://symfony.com/doc/current/forms.html#processing-forms
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
|
||||||
$em = $this->getDoctrine()->getManager();
|
|
||||||
$em->persist($post);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Flash messages are used to notify the user about the result of the
|
|
||||||
// actions. They are deleted automatically from the session as soon
|
|
||||||
// as they are accessed.
|
|
||||||
// See https://symfony.com/doc/current/controller.html#flash-messages
|
|
||||||
$this->addFlash('success', 'post.created_successfully');
|
|
||||||
|
|
||||||
if ($form->get('saveAndCreateNew')->isClicked()) {
|
|
||||||
return $this->redirectToRoute('admin_post_new');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->redirectToRoute('admin_post_index');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('admin/blog/new.html.twig', [
|
|
||||||
'post' => $post,
|
|
||||||
'form' => $form->createView(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds and displays a Post entity.
|
|
||||||
*
|
|
||||||
* @Route("/{id<\d+>}", methods="GET", name="admin_post_show")
|
|
||||||
*/
|
|
||||||
public function show(Post $post): Response
|
|
||||||
{
|
|
||||||
// This security check can also be performed
|
|
||||||
// using an annotation: @IsGranted("show", subject="post", message="Posts can only be shown to their authors.")
|
|
||||||
$this->denyAccessUnlessGranted(PostVoter::SHOW, $post, 'Posts can only be shown to their authors.');
|
|
||||||
|
|
||||||
return $this->render('admin/blog/show.html.twig', [
|
|
||||||
'post' => $post,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a form to edit an existing Post entity.
|
|
||||||
*
|
|
||||||
* @Route("/{id<\d+>}/edit", methods="GET|POST", name="admin_post_edit")
|
|
||||||
* @IsGranted("edit", subject="post", message="Posts can only be edited by their authors.")
|
|
||||||
*/
|
|
||||||
public function edit(Request $request, Post $post): Response
|
|
||||||
{
|
|
||||||
$form = $this->createForm(PostType::class, $post);
|
|
||||||
$form->handleRequest($request);
|
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
|
||||||
$this->getDoctrine()->getManager()->flush();
|
|
||||||
|
|
||||||
$this->addFlash('success', 'post.updated_successfully');
|
|
||||||
|
|
||||||
return $this->redirectToRoute('admin_post_edit', ['id' => $post->getId()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('admin/blog/edit.html.twig', [
|
|
||||||
'post' => $post,
|
|
||||||
'form' => $form->createView(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a Post entity.
|
|
||||||
*
|
|
||||||
* @Route("/{id}/delete", methods="POST", name="admin_post_delete")
|
|
||||||
* @IsGranted("delete", subject="post")
|
|
||||||
*/
|
|
||||||
public function delete(Request $request, Post $post): Response
|
|
||||||
{
|
|
||||||
if (!$this->isCsrfTokenValid('delete', $request->request->get('token'))) {
|
|
||||||
return $this->redirectToRoute('admin_post_index');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the tags associated with this blog post. This is done automatically
|
|
||||||
// by Doctrine, except for SQLite (the database used in this application)
|
|
||||||
// because foreign key support is not enabled by default in SQLite
|
|
||||||
$post->getTags()->clear();
|
|
||||||
|
|
||||||
$em = $this->getDoctrine()->getManager();
|
|
||||||
$em->remove($post);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
$this->addFlash('success', 'post.deleted_successfully');
|
|
||||||
|
|
||||||
return $this->redirectToRoute('admin_post_index');
|
|
||||||
}
|
|
||||||
}
|
|
94
app/src/Controller/BeerController.php
Normal file
94
app/src/Controller/BeerController.php
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\Beer;
|
||||||
|
use App\Form\BeerType;
|
||||||
|
use App\Repository\BeerRepository;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/beer")
|
||||||
|
*/
|
||||||
|
class BeerController extends AbstractController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Route("/", name="beer_index", methods={"GET"})
|
||||||
|
*/
|
||||||
|
public function index(BeerRepository $beerRepository): Response
|
||||||
|
{
|
||||||
|
return $this->render('beer/index.html.twig', [
|
||||||
|
'beers' => $beerRepository->findAll(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/new", name="beer_new", methods={"GET","POST"})
|
||||||
|
*/
|
||||||
|
public function new(Request $request): Response
|
||||||
|
{
|
||||||
|
$beer = new Beer();
|
||||||
|
$form = $this->createForm(BeerType::class, $beer);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$entityManager = $this->getDoctrine()->getManager();
|
||||||
|
$entityManager->persist($beer);
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
|
return $this->redirectToRoute('beer_index');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('beer/new.html.twig', [
|
||||||
|
'beer' => $beer,
|
||||||
|
'form' => $form->createView(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/{id}", name="beer_show", methods={"GET"})
|
||||||
|
*/
|
||||||
|
public function show(Beer $beer): Response
|
||||||
|
{
|
||||||
|
return $this->render('beer/show.html.twig', [
|
||||||
|
'beer' => $beer,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/{id}/edit", name="beer_edit", methods={"GET","POST"})
|
||||||
|
*/
|
||||||
|
public function edit(Request $request, Beer $beer): Response
|
||||||
|
{
|
||||||
|
$form = $this->createForm(BeerType::class, $beer);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$this->getDoctrine()->getManager()->flush();
|
||||||
|
|
||||||
|
return $this->redirectToRoute('beer_index');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('beer/edit.html.twig', [
|
||||||
|
'beer' => $beer,
|
||||||
|
'form' => $form->createView(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/{id}", name="beer_delete", methods={"DELETE"})
|
||||||
|
*/
|
||||||
|
public function delete(Request $request, Beer $beer): Response
|
||||||
|
{
|
||||||
|
if ($this->isCsrfTokenValid('delete'.$beer->getId(), $request->request->get('_token'))) {
|
||||||
|
$entityManager = $this->getDoctrine()->getManager();
|
||||||
|
$entityManager->remove($beer);
|
||||||
|
$entityManager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('beer_index');
|
||||||
|
}
|
||||||
|
}
|
@ -1,170 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Controller;
|
|
||||||
|
|
||||||
use App\Entity\Comment;
|
|
||||||
use App\Entity\Post;
|
|
||||||
use App\Event\CommentCreatedEvent;
|
|
||||||
use App\Form\CommentType;
|
|
||||||
use App\Repository\PostRepository;
|
|
||||||
use App\Repository\TagRepository;
|
|
||||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
|
|
||||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
|
|
||||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller used to manage blog contents in the public part of the site.
|
|
||||||
*
|
|
||||||
* @Route("/blog")
|
|
||||||
*
|
|
||||||
* @author Ryan Weaver <weaverryan@gmail.com>
|
|
||||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
|
||||||
*/
|
|
||||||
class BlogController extends AbstractController
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @Route("/", defaults={"page": "1", "_format"="html"}, methods="GET", name="blog_index")
|
|
||||||
* @Route("/rss.xml", defaults={"page": "1", "_format"="xml"}, methods="GET", name="blog_rss")
|
|
||||||
* @Route("/page/{page<[1-9]\d*>}", defaults={"_format"="html"}, methods="GET", name="blog_index_paginated")
|
|
||||||
* @Cache(smaxage="10")
|
|
||||||
*
|
|
||||||
* NOTE: For standard formats, Symfony will also automatically choose the best
|
|
||||||
* Content-Type header for the response.
|
|
||||||
* See https://symfony.com/doc/current/routing.html#special-parameters
|
|
||||||
*/
|
|
||||||
public function index(Request $request, int $page, string $_format, PostRepository $posts, TagRepository $tags): Response
|
|
||||||
{
|
|
||||||
$tag = null;
|
|
||||||
if ($request->query->has('tag')) {
|
|
||||||
$tag = $tags->findOneBy(['name' => $request->query->get('tag')]);
|
|
||||||
}
|
|
||||||
$latestPosts = $posts->findLatest($page, $tag);
|
|
||||||
|
|
||||||
// Every template name also has two extensions that specify the format and
|
|
||||||
// engine for that template.
|
|
||||||
// See https://symfony.com/doc/current/templates.html#template-naming
|
|
||||||
return $this->render('blog/index.'.$_format.'.twig', [
|
|
||||||
'paginator' => $latestPosts,
|
|
||||||
'tagName' => $tag ? $tag->getName() : null,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Route("/posts/{slug}", methods="GET", name="blog_post")
|
|
||||||
*
|
|
||||||
* NOTE: The $post controller argument is automatically injected by Symfony
|
|
||||||
* after performing a database query looking for a Post with the 'slug'
|
|
||||||
* value given in the route.
|
|
||||||
* See https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
|
|
||||||
*/
|
|
||||||
public function postShow(Post $post): Response
|
|
||||||
{
|
|
||||||
// Symfony's 'dump()' function is an improved version of PHP's 'var_dump()' but
|
|
||||||
// it's not available in the 'prod' environment to prevent leaking sensitive information.
|
|
||||||
// It can be used both in PHP files and Twig templates, but it requires to
|
|
||||||
// have enabled the DebugBundle. Uncomment the following line to see it in action:
|
|
||||||
//
|
|
||||||
// dump($post, $this->getUser(), new \DateTime());
|
|
||||||
|
|
||||||
return $this->render('blog/post_show.html.twig', ['post' => $post]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Route("/comment/{postSlug}/new", methods="POST", name="comment_new")
|
|
||||||
* @IsGranted("IS_AUTHENTICATED_FULLY")
|
|
||||||
* @ParamConverter("post", options={"mapping": {"postSlug": "slug"}})
|
|
||||||
*
|
|
||||||
* NOTE: The ParamConverter mapping is required because the route parameter
|
|
||||||
* (postSlug) doesn't match any of the Doctrine entity properties (slug).
|
|
||||||
* See https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html#doctrine-converter
|
|
||||||
*/
|
|
||||||
public function commentNew(Request $request, Post $post, EventDispatcherInterface $eventDispatcher): Response
|
|
||||||
{
|
|
||||||
$comment = new Comment();
|
|
||||||
$comment->setAuthor($this->getUser());
|
|
||||||
$post->addComment($comment);
|
|
||||||
|
|
||||||
$form = $this->createForm(CommentType::class, $comment);
|
|
||||||
$form->handleRequest($request);
|
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
|
||||||
$em = $this->getDoctrine()->getManager();
|
|
||||||
$em->persist($comment);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// When an event is dispatched, Symfony notifies it to all the listeners
|
|
||||||
// and subscribers registered to it. Listeners can modify the information
|
|
||||||
// passed in the event and they can even modify the execution flow, so
|
|
||||||
// there's no guarantee that the rest of this controller will be executed.
|
|
||||||
// See https://symfony.com/doc/current/components/event_dispatcher.html
|
|
||||||
$eventDispatcher->dispatch(new CommentCreatedEvent($comment));
|
|
||||||
|
|
||||||
return $this->redirectToRoute('blog_post', ['slug' => $post->getSlug()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('blog/comment_form_error.html.twig', [
|
|
||||||
'post' => $post,
|
|
||||||
'form' => $form->createView(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This controller is called directly via the render() function in the
|
|
||||||
* blog/post_show.html.twig template. That's why it's not needed to define
|
|
||||||
* a route name for it.
|
|
||||||
*
|
|
||||||
* The "id" of the Post is passed in and then turned into a Post object
|
|
||||||
* automatically by the ParamConverter.
|
|
||||||
*/
|
|
||||||
public function commentForm(Post $post): Response
|
|
||||||
{
|
|
||||||
$form = $this->createForm(CommentType::class);
|
|
||||||
|
|
||||||
return $this->render('blog/_comment_form.html.twig', [
|
|
||||||
'post' => $post,
|
|
||||||
'form' => $form->createView(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Route("/search", methods="GET", name="blog_search")
|
|
||||||
*/
|
|
||||||
public function search(Request $request, PostRepository $posts): Response
|
|
||||||
{
|
|
||||||
$query = $request->query->get('q', '');
|
|
||||||
$limit = $request->query->get('l', 10);
|
|
||||||
|
|
||||||
if (!$request->isXmlHttpRequest()) {
|
|
||||||
return $this->render('blog/search.html.twig', ['query' => $query]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$foundPosts = $posts->findBySearchQuery($query, $limit);
|
|
||||||
|
|
||||||
$results = [];
|
|
||||||
foreach ($foundPosts as $post) {
|
|
||||||
$results[] = [
|
|
||||||
'title' => htmlspecialchars($post->getTitle(), ENT_COMPAT | ENT_HTML5),
|
|
||||||
'date' => $post->getPublishedAt()->format('M d, Y'),
|
|
||||||
'author' => htmlspecialchars($post->getAuthor()->getFullName(), ENT_COMPAT | ENT_HTML5),
|
|
||||||
'summary' => htmlspecialchars($post->getSummary(), ENT_COMPAT | ENT_HTML5),
|
|
||||||
'url' => $this->generateUrl('blog_post', ['slug' => $post->getSlug()]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->json($results);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Controller;
|
|
||||||
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
|
||||||
use Symfony\Component\Security\Core\Security;
|
|
||||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
|
||||||
use Symfony\Component\Security\Http\Util\TargetPathTrait;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller used to manage the application security.
|
|
||||||
* See https://symfony.com/doc/current/security/form_login_setup.html.
|
|
||||||
*
|
|
||||||
* @author Ryan Weaver <weaverryan@gmail.com>
|
|
||||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
|
||||||
*/
|
|
||||||
class SecurityController extends AbstractController
|
|
||||||
{
|
|
||||||
use TargetPathTrait;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Route("/login", name="security_login")
|
|
||||||
*/
|
|
||||||
public function login(Request $request, Security $security, AuthenticationUtils $helper): Response
|
|
||||||
{
|
|
||||||
// if user is already logged in, don't display the login page again
|
|
||||||
if ($security->isGranted('ROLE_USER')) {
|
|
||||||
return $this->redirectToRoute('blog_index');
|
|
||||||
}
|
|
||||||
|
|
||||||
// this statement solves an edge-case: if you change the locale in the login
|
|
||||||
// page, after a successful login you are redirected to a page in the previous
|
|
||||||
// locale. This code regenerates the referrer URL whenever the login page is
|
|
||||||
// browsed, to ensure that its locale is always the current one.
|
|
||||||
$this->saveTargetPath($request->getSession(), 'main', $this->generateUrl('admin_index'));
|
|
||||||
|
|
||||||
return $this->render('security/login.html.twig', [
|
|
||||||
// last username entered by the user (if any)
|
|
||||||
'last_username' => $helper->getLastUsername(),
|
|
||||||
// last authentication error (if any)
|
|
||||||
'error' => $helper->getLastAuthenticationError(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the route the user can use to logout.
|
|
||||||
*
|
|
||||||
* But, this will never be executed. Symfony will intercept this first
|
|
||||||
* and handle the logout automatically. See logout in config/packages/security.yaml
|
|
||||||
*
|
|
||||||
* @Route("/logout", name="security_logout")
|
|
||||||
*/
|
|
||||||
public function logout(): void
|
|
||||||
{
|
|
||||||
throw new \Exception('This should never be reached!');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Controller;
|
|
||||||
|
|
||||||
use App\Form\Type\ChangePasswordType;
|
|
||||||
use App\Form\UserType;
|
|
||||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
|
||||||
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller used to manage current user.
|
|
||||||
*
|
|
||||||
* @Route("/profile")
|
|
||||||
* @IsGranted("ROLE_USER")
|
|
||||||
*
|
|
||||||
* @author Romain Monteil <monteil.romain@gmail.com>
|
|
||||||
*/
|
|
||||||
class UserController extends AbstractController
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @Route("/edit", methods="GET|POST", name="user_edit")
|
|
||||||
*/
|
|
||||||
public function edit(Request $request): Response
|
|
||||||
{
|
|
||||||
$user = $this->getUser();
|
|
||||||
|
|
||||||
$form = $this->createForm(UserType::class, $user);
|
|
||||||
$form->handleRequest($request);
|
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
|
||||||
$this->getDoctrine()->getManager()->flush();
|
|
||||||
|
|
||||||
$this->addFlash('success', 'user.updated_successfully');
|
|
||||||
|
|
||||||
return $this->redirectToRoute('user_edit');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('user/edit.html.twig', [
|
|
||||||
'user' => $user,
|
|
||||||
'form' => $form->createView(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Route("/change-password", methods="GET|POST", name="user_change_password")
|
|
||||||
*/
|
|
||||||
public function changePassword(Request $request, UserPasswordEncoderInterface $encoder): Response
|
|
||||||
{
|
|
||||||
$user = $this->getUser();
|
|
||||||
|
|
||||||
$form = $this->createForm(ChangePasswordType::class);
|
|
||||||
$form->handleRequest($request);
|
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
|
||||||
$user->setPassword($encoder->encodePassword($user, $form->get('newPassword')->getData()));
|
|
||||||
|
|
||||||
$this->getDoctrine()->getManager()->flush();
|
|
||||||
|
|
||||||
return $this->redirectToRoute('security_logout');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('user/change_password.html.twig', [
|
|
||||||
'form' => $form->createView(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,241 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\DataFixtures;
|
|
||||||
|
|
||||||
use App\Entity\Comment;
|
|
||||||
use App\Entity\Post;
|
|
||||||
use App\Entity\Tag;
|
|
||||||
use App\Entity\User;
|
|
||||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
|
||||||
use Doctrine\Persistence\ObjectManager;
|
|
||||||
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
|
|
||||||
use Symfony\Component\String\Slugger\SluggerInterface;
|
|
||||||
use function Symfony\Component\String\u;
|
|
||||||
|
|
||||||
class AppFixtures extends Fixture
|
|
||||||
{
|
|
||||||
private $passwordEncoder;
|
|
||||||
private $slugger;
|
|
||||||
|
|
||||||
public function __construct(UserPasswordEncoderInterface $passwordEncoder, SluggerInterface $slugger)
|
|
||||||
{
|
|
||||||
$this->passwordEncoder = $passwordEncoder;
|
|
||||||
$this->slugger = $slugger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function load(ObjectManager $manager): void
|
|
||||||
{
|
|
||||||
$this->loadUsers($manager);
|
|
||||||
$this->loadTags($manager);
|
|
||||||
$this->loadPosts($manager);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function loadUsers(ObjectManager $manager): void
|
|
||||||
{
|
|
||||||
foreach ($this->getUserData() as [$fullname, $username, $password, $email, $roles]) {
|
|
||||||
$user = new User();
|
|
||||||
$user->setFullName($fullname);
|
|
||||||
$user->setUsername($username);
|
|
||||||
$user->setPassword($this->passwordEncoder->encodePassword($user, $password));
|
|
||||||
$user->setEmail($email);
|
|
||||||
$user->setRoles($roles);
|
|
||||||
|
|
||||||
$manager->persist($user);
|
|
||||||
$this->addReference($username, $user);
|
|
||||||
}
|
|
||||||
|
|
||||||
$manager->flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function loadTags(ObjectManager $manager): void
|
|
||||||
{
|
|
||||||
foreach ($this->getTagData() as $index => $name) {
|
|
||||||
$tag = new Tag();
|
|
||||||
$tag->setName($name);
|
|
||||||
|
|
||||||
$manager->persist($tag);
|
|
||||||
$this->addReference('tag-'.$name, $tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
$manager->flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function loadPosts(ObjectManager $manager): void
|
|
||||||
{
|
|
||||||
foreach ($this->getPostData() as [$title, $slug, $summary, $content, $publishedAt, $author, $tags]) {
|
|
||||||
$post = new Post();
|
|
||||||
$post->setTitle($title);
|
|
||||||
$post->setSlug($slug);
|
|
||||||
$post->setSummary($summary);
|
|
||||||
$post->setContent($content);
|
|
||||||
$post->setPublishedAt($publishedAt);
|
|
||||||
$post->setAuthor($author);
|
|
||||||
$post->addTag(...$tags);
|
|
||||||
|
|
||||||
foreach (range(1, 5) as $i) {
|
|
||||||
$comment = new Comment();
|
|
||||||
$comment->setAuthor($this->getReference('john_user'));
|
|
||||||
$comment->setContent($this->getRandomText(random_int(255, 512)));
|
|
||||||
$comment->setPublishedAt(new \DateTime('now + '.$i.'seconds'));
|
|
||||||
|
|
||||||
$post->addComment($comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
$manager->persist($post);
|
|
||||||
}
|
|
||||||
|
|
||||||
$manager->flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getUserData(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
// $userData = [$fullname, $username, $password, $email, $roles];
|
|
||||||
['Jane Doe', 'jane_admin', 'kitten', 'jane_admin@symfony.com', ['ROLE_ADMIN']],
|
|
||||||
['Tom Doe', 'tom_admin', 'kitten', 'tom_admin@symfony.com', ['ROLE_ADMIN']],
|
|
||||||
['John Doe', 'john_user', 'kitten', 'john_user@symfony.com', ['ROLE_USER']],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getTagData(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'lorem',
|
|
||||||
'ipsum',
|
|
||||||
'consectetur',
|
|
||||||
'adipiscing',
|
|
||||||
'incididunt',
|
|
||||||
'labore',
|
|
||||||
'voluptate',
|
|
||||||
'dolore',
|
|
||||||
'pariatur',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getPostData()
|
|
||||||
{
|
|
||||||
$posts = [];
|
|
||||||
foreach ($this->getPhrases() as $i => $title) {
|
|
||||||
// $postData = [$title, $slug, $summary, $content, $publishedAt, $author, $tags, $comments];
|
|
||||||
$posts[] = [
|
|
||||||
$title,
|
|
||||||
$this->slugger->slug($title)->lower(),
|
|
||||||
$this->getRandomText(),
|
|
||||||
$this->getPostContent(),
|
|
||||||
new \DateTime('now - '.$i.'days'),
|
|
||||||
// Ensure that the first post is written by Jane Doe to simplify tests
|
|
||||||
$this->getReference(['jane_admin', 'tom_admin'][0 === $i ? 0 : random_int(0, 1)]),
|
|
||||||
$this->getRandomTags(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $posts;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getPhrases(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'Lorem ipsum dolor sit amet consectetur adipiscing elit',
|
|
||||||
'Pellentesque vitae velit ex',
|
|
||||||
'Mauris dapibus risus quis suscipit vulputate',
|
|
||||||
'Eros diam egestas libero eu vulputate risus',
|
|
||||||
'In hac habitasse platea dictumst',
|
|
||||||
'Morbi tempus commodo mattis',
|
|
||||||
'Ut suscipit posuere justo at vulputate',
|
|
||||||
'Ut eleifend mauris et risus ultrices egestas',
|
|
||||||
'Aliquam sodales odio id eleifend tristique',
|
|
||||||
'Urna nisl sollicitudin id varius orci quam id turpis',
|
|
||||||
'Nulla porta lobortis ligula vel egestas',
|
|
||||||
'Curabitur aliquam euismod dolor non ornare',
|
|
||||||
'Sed varius a risus eget aliquam',
|
|
||||||
'Nunc viverra elit ac laoreet suscipit',
|
|
||||||
'Pellentesque et sapien pulvinar consectetur',
|
|
||||||
'Ubi est barbatus nix',
|
|
||||||
'Abnobas sunt hilotaes de placidus vita',
|
|
||||||
'Ubi est audax amicitia',
|
|
||||||
'Eposs sunt solems de superbus fortis',
|
|
||||||
'Vae humani generis',
|
|
||||||
'Diatrias tolerare tanquam noster caesium',
|
|
||||||
'Teres talis saepe tractare de camerarius flavum sensorem',
|
|
||||||
'Silva de secundus galatae demitto quadra',
|
|
||||||
'Sunt accentores vitare salvus flavum parses',
|
|
||||||
'Potus sensim ad ferox abnoba',
|
|
||||||
'Sunt seculaes transferre talis camerarius fluctuies',
|
|
||||||
'Era brevis ratione est',
|
|
||||||
'Sunt torquises imitari velox mirabilis medicinaes',
|
|
||||||
'Mineralis persuadere omnes finises desiderium',
|
|
||||||
'Bassus fatalis classiss virtualiter transferre de flavum',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getRandomText(int $maxLength = 255): string
|
|
||||||
{
|
|
||||||
$phrases = $this->getPhrases();
|
|
||||||
shuffle($phrases);
|
|
||||||
|
|
||||||
do {
|
|
||||||
$text = u('. ')->join($phrases)->append('.');
|
|
||||||
array_pop($phrases);
|
|
||||||
} while ($text->length() > $maxLength);
|
|
||||||
|
|
||||||
return $text;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getPostContent(): string
|
|
||||||
{
|
|
||||||
return <<<'MARKDOWN'
|
|
||||||
Lorem ipsum dolor sit amet consectetur adipisicing elit, sed do eiusmod tempor
|
|
||||||
incididunt ut labore et **dolore magna aliqua**: Duis aute irure dolor in
|
|
||||||
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
|
||||||
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
|
|
||||||
deserunt mollit anim id est laborum.
|
|
||||||
|
|
||||||
* Ut enim ad minim veniam
|
|
||||||
* Quis nostrud exercitation *ullamco laboris*
|
|
||||||
* Nisi ut aliquip ex ea commodo consequat
|
|
||||||
|
|
||||||
Praesent id fermentum lorem. Ut est lorem, fringilla at accumsan nec, euismod at
|
|
||||||
nunc. Aenean mattis sollicitudin mattis. Nullam pulvinar vestibulum bibendum.
|
|
||||||
Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos
|
|
||||||
himenaeos. Fusce nulla purus, gravida ac interdum ut, blandit eget ex. Duis a
|
|
||||||
luctus dolor.
|
|
||||||
|
|
||||||
Integer auctor massa maximus nulla scelerisque accumsan. *Aliquam ac malesuada*
|
|
||||||
ex. Pellentesque tortor magna, vulputate eu vulputate ut, venenatis ac lectus.
|
|
||||||
Praesent ut lacinia sem. Mauris a lectus eget felis mollis feugiat. Quisque
|
|
||||||
efficitur, mi ut semper pulvinar, urna urna blandit massa, eget tincidunt augue
|
|
||||||
nulla vitae est.
|
|
||||||
|
|
||||||
Ut posuere aliquet tincidunt. Aliquam erat volutpat. **Class aptent taciti**
|
|
||||||
sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi
|
|
||||||
arcu orci, gravida eget aliquam eu, suscipit et ante. Morbi vulputate metus vel
|
|
||||||
ipsum finibus, ut dapibus massa feugiat. Vestibulum vel lobortis libero. Sed
|
|
||||||
tincidunt tellus et viverra scelerisque. Pellentesque tincidunt cursus felis.
|
|
||||||
Sed in egestas erat.
|
|
||||||
|
|
||||||
Aliquam pulvinar interdum massa, vel ullamcorper ante consectetur eu. Vestibulum
|
|
||||||
lacinia ac enim vel placerat. Integer pulvinar magna nec dui malesuada, nec
|
|
||||||
congue nisl dictum. Donec mollis nisl tortor, at congue erat consequat a. Nam
|
|
||||||
tempus elit porta, blandit elit vel, viverra lorem. Sed sit amet tellus
|
|
||||||
tincidunt, faucibus nisl in, aliquet libero.
|
|
||||||
MARKDOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getRandomTags(): array
|
|
||||||
{
|
|
||||||
$tagNames = $this->getTagData();
|
|
||||||
shuffle($tagNames);
|
|
||||||
$selectedTags = \array_slice($tagNames, 0, random_int(2, 4));
|
|
||||||
|
|
||||||
return array_map(function ($tagName) { return $this->getReference('tag-'.$tagName); }, $selectedTags);
|
|
||||||
}
|
|
||||||
}
|
|
0
app/src/Entity/.gitignore
vendored
Normal file
0
app/src/Entity/.gitignore
vendored
Normal file
58
app/src/Entity/Beer.php
Normal file
58
app/src/Entity/Beer.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\BeerRepository;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity(repositoryClass=BeerRepository::class)
|
||||||
|
*/
|
||||||
|
class Beer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @ORM\Id
|
||||||
|
* @ORM\GeneratedValue
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=255)
|
||||||
|
*/
|
||||||
|
private $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="float", nullable=true)
|
||||||
|
*/
|
||||||
|
private $alcool;
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): ?string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setName(string $name): self
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAlcool(): ?float
|
||||||
|
{
|
||||||
|
return $this->alcool;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAlcool(?float $alcool): self
|
||||||
|
{
|
||||||
|
$this->alcool = $alcool;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@ -1,138 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Entity;
|
|
||||||
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
|
||||||
use function Symfony\Component\String\u;
|
|
||||||
use Symfony\Component\Validator\Constraints as Assert;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ORM\Entity
|
|
||||||
* @ORM\Table(name="symfony_demo_comment")
|
|
||||||
*
|
|
||||||
* Defines the properties of the Comment entity to represent the blog comments.
|
|
||||||
* See https://symfony.com/doc/current/doctrine.html#creating-an-entity-class
|
|
||||||
*
|
|
||||||
* Tip: if you have an existing database, you can generate these entity class automatically.
|
|
||||||
* See https://symfony.com/doc/current/doctrine/reverse_engineering.html
|
|
||||||
*
|
|
||||||
* @author Ryan Weaver <weaverryan@gmail.com>
|
|
||||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
|
||||||
*/
|
|
||||||
class Comment
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var int
|
|
||||||
*
|
|
||||||
* @ORM\Id
|
|
||||||
* @ORM\GeneratedValue
|
|
||||||
* @ORM\Column(type="integer")
|
|
||||||
*/
|
|
||||||
private $id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Post
|
|
||||||
*
|
|
||||||
* @ORM\ManyToOne(targetEntity="Post", inversedBy="comments")
|
|
||||||
* @ORM\JoinColumn(nullable=false)
|
|
||||||
*/
|
|
||||||
private $post;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*
|
|
||||||
* @ORM\Column(type="text")
|
|
||||||
* @Assert\NotBlank(message="comment.blank")
|
|
||||||
* @Assert\Length(
|
|
||||||
* min=5,
|
|
||||||
* minMessage="comment.too_short",
|
|
||||||
* max=10000,
|
|
||||||
* maxMessage="comment.too_long"
|
|
||||||
* )
|
|
||||||
*/
|
|
||||||
private $content;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \DateTime
|
|
||||||
*
|
|
||||||
* @ORM\Column(type="datetime")
|
|
||||||
*/
|
|
||||||
private $publishedAt;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var User
|
|
||||||
*
|
|
||||||
* @ORM\ManyToOne(targetEntity="App\Entity\User")
|
|
||||||
* @ORM\JoinColumn(nullable=false)
|
|
||||||
*/
|
|
||||||
private $author;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->publishedAt = new \DateTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Assert\IsTrue(message="comment.is_spam")
|
|
||||||
*/
|
|
||||||
public function isLegitComment(): bool
|
|
||||||
{
|
|
||||||
$containsInvalidCharacters = null !== u($this->content)->indexOf('@');
|
|
||||||
|
|
||||||
return !$containsInvalidCharacters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): ?int
|
|
||||||
{
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getContent(): ?string
|
|
||||||
{
|
|
||||||
return $this->content;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setContent(string $content): void
|
|
||||||
{
|
|
||||||
$this->content = $content;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPublishedAt(): \DateTime
|
|
||||||
{
|
|
||||||
return $this->publishedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPublishedAt(\DateTime $publishedAt): void
|
|
||||||
{
|
|
||||||
$this->publishedAt = $publishedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAuthor(): ?User
|
|
||||||
{
|
|
||||||
return $this->author;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setAuthor(User $author): void
|
|
||||||
{
|
|
||||||
$this->author = $author;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPost(): ?Post
|
|
||||||
{
|
|
||||||
return $this->post;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPost(Post $post): void
|
|
||||||
{
|
|
||||||
$this->post = $post;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,226 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Entity;
|
|
||||||
|
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
|
||||||
use Doctrine\Common\Collections\Collection;
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
|
||||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
|
||||||
use Symfony\Component\Validator\Constraints as Assert;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ORM\Entity(repositoryClass="App\Repository\PostRepository")
|
|
||||||
* @ORM\Table(name="symfony_demo_post")
|
|
||||||
* @UniqueEntity(fields={"slug"}, errorPath="title", message="post.slug_unique")
|
|
||||||
*
|
|
||||||
* Defines the properties of the Post entity to represent the blog posts.
|
|
||||||
*
|
|
||||||
* See https://symfony.com/doc/current/doctrine.html#creating-an-entity-class
|
|
||||||
*
|
|
||||||
* Tip: if you have an existing database, you can generate these entity class automatically.
|
|
||||||
* See https://symfony.com/doc/current/doctrine/reverse_engineering.html
|
|
||||||
*
|
|
||||||
* @author Ryan Weaver <weaverryan@gmail.com>
|
|
||||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
|
||||||
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
|
||||||
*/
|
|
||||||
class Post
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var int
|
|
||||||
*
|
|
||||||
* @ORM\Id
|
|
||||||
* @ORM\GeneratedValue
|
|
||||||
* @ORM\Column(type="integer")
|
|
||||||
*/
|
|
||||||
private $id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*
|
|
||||||
* @ORM\Column(type="string")
|
|
||||||
* @Assert\NotBlank
|
|
||||||
*/
|
|
||||||
private $title;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*
|
|
||||||
* @ORM\Column(type="string")
|
|
||||||
*/
|
|
||||||
private $slug;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*
|
|
||||||
* @ORM\Column(type="string")
|
|
||||||
* @Assert\NotBlank(message="post.blank_summary")
|
|
||||||
* @Assert\Length(max=255)
|
|
||||||
*/
|
|
||||||
private $summary;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*
|
|
||||||
* @ORM\Column(type="text")
|
|
||||||
* @Assert\NotBlank(message="post.blank_content")
|
|
||||||
* @Assert\Length(min=10, minMessage="post.too_short_content")
|
|
||||||
*/
|
|
||||||
private $content;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \DateTime
|
|
||||||
*
|
|
||||||
* @ORM\Column(type="datetime")
|
|
||||||
*/
|
|
||||||
private $publishedAt;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var User
|
|
||||||
*
|
|
||||||
* @ORM\ManyToOne(targetEntity="App\Entity\User")
|
|
||||||
* @ORM\JoinColumn(nullable=false)
|
|
||||||
*/
|
|
||||||
private $author;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Comment[]|Collection
|
|
||||||
*
|
|
||||||
* @ORM\OneToMany(
|
|
||||||
* targetEntity="Comment",
|
|
||||||
* mappedBy="post",
|
|
||||||
* orphanRemoval=true,
|
|
||||||
* cascade={"persist"}
|
|
||||||
* )
|
|
||||||
* @ORM\OrderBy({"publishedAt": "DESC"})
|
|
||||||
*/
|
|
||||||
private $comments;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Tag[]|Collection
|
|
||||||
*
|
|
||||||
* @ORM\ManyToMany(targetEntity="App\Entity\Tag", cascade={"persist"})
|
|
||||||
* @ORM\JoinTable(name="symfony_demo_post_tag")
|
|
||||||
* @ORM\OrderBy({"name": "ASC"})
|
|
||||||
* @Assert\Count(max="4", maxMessage="post.too_many_tags")
|
|
||||||
*/
|
|
||||||
private $tags;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->publishedAt = new \DateTime();
|
|
||||||
$this->comments = new ArrayCollection();
|
|
||||||
$this->tags = new ArrayCollection();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): ?int
|
|
||||||
{
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTitle(): ?string
|
|
||||||
{
|
|
||||||
return $this->title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTitle(?string $title): void
|
|
||||||
{
|
|
||||||
$this->title = $title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSlug(): ?string
|
|
||||||
{
|
|
||||||
return $this->slug;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setSlug(string $slug): void
|
|
||||||
{
|
|
||||||
$this->slug = $slug;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getContent(): ?string
|
|
||||||
{
|
|
||||||
return $this->content;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setContent(?string $content): void
|
|
||||||
{
|
|
||||||
$this->content = $content;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPublishedAt(): \DateTime
|
|
||||||
{
|
|
||||||
return $this->publishedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPublishedAt(\DateTime $publishedAt): void
|
|
||||||
{
|
|
||||||
$this->publishedAt = $publishedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAuthor(): ?User
|
|
||||||
{
|
|
||||||
return $this->author;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setAuthor(User $author): void
|
|
||||||
{
|
|
||||||
$this->author = $author;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getComments(): Collection
|
|
||||||
{
|
|
||||||
return $this->comments;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addComment(Comment $comment): void
|
|
||||||
{
|
|
||||||
$comment->setPost($this);
|
|
||||||
if (!$this->comments->contains($comment)) {
|
|
||||||
$this->comments->add($comment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function removeComment(Comment $comment): void
|
|
||||||
{
|
|
||||||
$this->comments->removeElement($comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSummary(): ?string
|
|
||||||
{
|
|
||||||
return $this->summary;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setSummary(?string $summary): void
|
|
||||||
{
|
|
||||||
$this->summary = $summary;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addTag(Tag ...$tags): void
|
|
||||||
{
|
|
||||||
foreach ($tags as $tag) {
|
|
||||||
if (!$this->tags->contains($tag)) {
|
|
||||||
$this->tags->add($tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function removeTag(Tag $tag): void
|
|
||||||
{
|
|
||||||
$this->tags->removeElement($tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTags(): Collection
|
|
||||||
{
|
|
||||||
return $this->tags;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Entity;
|
|
||||||
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ORM\Entity()
|
|
||||||
* @ORM\Table(name="symfony_demo_tag")
|
|
||||||
*
|
|
||||||
* Defines the properties of the Tag entity to represent the post tags.
|
|
||||||
*
|
|
||||||
* See https://symfony.com/doc/current/doctrine.html#creating-an-entity-class
|
|
||||||
*
|
|
||||||
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
|
||||||
*/
|
|
||||||
class Tag implements \JsonSerializable
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var int
|
|
||||||
*
|
|
||||||
* @ORM\Id
|
|
||||||
* @ORM\GeneratedValue
|
|
||||||
* @ORM\Column(type="integer")
|
|
||||||
*/
|
|
||||||
private $id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*
|
|
||||||
* @ORM\Column(type="string", unique=true)
|
|
||||||
*/
|
|
||||||
private $name;
|
|
||||||
|
|
||||||
public function getId(): ?int
|
|
||||||
{
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setName(string $name): void
|
|
||||||
{
|
|
||||||
$this->name = $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getName(): ?string
|
|
||||||
{
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function jsonSerialize(): string
|
|
||||||
{
|
|
||||||
// This entity implements JsonSerializable (http://php.net/manual/en/class.jsonserializable.php)
|
|
||||||
// so this method is used to customize its JSON representation when json_encode()
|
|
||||||
// is called, for example in tags|json_encode (templates/form/fields.html.twig)
|
|
||||||
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString(): string
|
|
||||||
{
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,188 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Entity;
|
|
||||||
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
|
||||||
use Symfony\Component\Security\Core\User\UserInterface;
|
|
||||||
use Symfony\Component\Validator\Constraints as Assert;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
|
|
||||||
* @ORM\Table(name="symfony_demo_user")
|
|
||||||
*
|
|
||||||
* Defines the properties of the User entity to represent the application users.
|
|
||||||
* See https://symfony.com/doc/current/doctrine.html#creating-an-entity-class
|
|
||||||
*
|
|
||||||
* Tip: if you have an existing database, you can generate these entity class automatically.
|
|
||||||
* See https://symfony.com/doc/current/doctrine/reverse_engineering.html
|
|
||||||
*
|
|
||||||
* @author Ryan Weaver <weaverryan@gmail.com>
|
|
||||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
|
||||||
*/
|
|
||||||
class User implements UserInterface, \Serializable
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var int
|
|
||||||
*
|
|
||||||
* @ORM\Id
|
|
||||||
* @ORM\GeneratedValue
|
|
||||||
* @ORM\Column(type="integer")
|
|
||||||
*/
|
|
||||||
private $id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*
|
|
||||||
* @ORM\Column(type="string")
|
|
||||||
* @Assert\NotBlank()
|
|
||||||
*/
|
|
||||||
private $fullName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*
|
|
||||||
* @ORM\Column(type="string", unique=true)
|
|
||||||
* @Assert\NotBlank()
|
|
||||||
* @Assert\Length(min=2, max=50)
|
|
||||||
*/
|
|
||||||
private $username;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*
|
|
||||||
* @ORM\Column(type="string", unique=true)
|
|
||||||
* @Assert\Email()
|
|
||||||
*/
|
|
||||||
private $email;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*
|
|
||||||
* @ORM\Column(type="string")
|
|
||||||
*/
|
|
||||||
private $password;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*
|
|
||||||
* @ORM\Column(type="json")
|
|
||||||
*/
|
|
||||||
private $roles = [];
|
|
||||||
|
|
||||||
public function getId(): ?int
|
|
||||||
{
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setFullName(string $fullName): void
|
|
||||||
{
|
|
||||||
$this->fullName = $fullName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFullName(): ?string
|
|
||||||
{
|
|
||||||
return $this->fullName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUsername(): ?string
|
|
||||||
{
|
|
||||||
return $this->username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUsername(string $username): void
|
|
||||||
{
|
|
||||||
$this->username = $username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEmail(): ?string
|
|
||||||
{
|
|
||||||
return $this->email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setEmail(string $email): void
|
|
||||||
{
|
|
||||||
$this->email = $email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPassword(): ?string
|
|
||||||
{
|
|
||||||
return $this->password;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPassword(string $password): void
|
|
||||||
{
|
|
||||||
$this->password = $password;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the roles or permissions granted to the user for security.
|
|
||||||
*/
|
|
||||||
public function getRoles(): array
|
|
||||||
{
|
|
||||||
$roles = $this->roles;
|
|
||||||
|
|
||||||
// guarantees that a user always has at least one role for security
|
|
||||||
if (empty($roles)) {
|
|
||||||
$roles[] = 'ROLE_USER';
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_unique($roles);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setRoles(array $roles): void
|
|
||||||
{
|
|
||||||
$this->roles = $roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the salt that was originally used to encode the password.
|
|
||||||
*
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getSalt(): ?string
|
|
||||||
{
|
|
||||||
// We're using bcrypt in security.yaml to encode the password, so
|
|
||||||
// the salt value is built-in and and you don't have to generate one
|
|
||||||
// See https://en.wikipedia.org/wiki/Bcrypt
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes sensitive data from the user.
|
|
||||||
*
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function eraseCredentials(): void
|
|
||||||
{
|
|
||||||
// if you had a plainPassword property, you'd nullify it here
|
|
||||||
// $this->plainPassword = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function serialize(): string
|
|
||||||
{
|
|
||||||
// add $this->salt too if you don't use Bcrypt or Argon2i
|
|
||||||
return serialize([$this->id, $this->username, $this->password]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function unserialize($serialized): void
|
|
||||||
{
|
|
||||||
// add $this->salt too if you don't use Bcrypt or Argon2i
|
|
||||||
[$this->id, $this->username, $this->password] = unserialize($serialized, ['allowed_classes' => false]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Event;
|
|
||||||
|
|
||||||
use App\Entity\Comment;
|
|
||||||
use Symfony\Contracts\EventDispatcher\Event;
|
|
||||||
|
|
||||||
class CommentCreatedEvent extends Event
|
|
||||||
{
|
|
||||||
protected $comment;
|
|
||||||
|
|
||||||
public function __construct(Comment $comment)
|
|
||||||
{
|
|
||||||
$this->comment = $comment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getComment(): Comment
|
|
||||||
{
|
|
||||||
return $this->comment;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,101 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\EventSubscriber;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Exception\DriverException;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
use Symfony\Component\Console\ConsoleEvents;
|
|
||||||
use Symfony\Component\Console\Event\ConsoleErrorEvent;
|
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
|
||||||
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
|
|
||||||
use Symfony\Component\HttpKernel\KernelEvents;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This application uses by default an SQLite database to store its information.
|
|
||||||
* That's why the 'sqlite3' extension must be enabled in PHP. This event
|
|
||||||
* subscriber listens to console events and in case of an exception caused by
|
|
||||||
* a disabled 'sqlite3' extension, it displays a meaningful error message.
|
|
||||||
*
|
|
||||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
|
||||||
*/
|
|
||||||
class CheckRequirementsSubscriber implements EventSubscriberInterface
|
|
||||||
{
|
|
||||||
private $entityManager;
|
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $entityManager)
|
|
||||||
{
|
|
||||||
$this->entityManager = $entityManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event Subscribers must define this method to declare the events they
|
|
||||||
// listen to. You can listen to several events, execute more than one method
|
|
||||||
// for each event and set the priority of each event too.
|
|
||||||
// See https://symfony.com/doc/current/event_dispatcher.html#creating-an-event-subscriber
|
|
||||||
public static function getSubscribedEvents(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
// Errors are one of the events defined by the Console. See the
|
|
||||||
// rest here: https://symfony.com/doc/current/components/console/events.html
|
|
||||||
ConsoleEvents::ERROR => 'handleConsoleError',
|
|
||||||
// See: https://symfony.com/doc/current/components/http_kernel.html#component-http-kernel-event-table
|
|
||||||
KernelEvents::EXCEPTION => 'handleKernelException',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method checks if there has been an error in a command related to
|
|
||||||
* the database and then, it checks if the 'sqlite3' PHP extension is enabled
|
|
||||||
* or not to display a better error message.
|
|
||||||
*/
|
|
||||||
public function handleConsoleError(ConsoleErrorEvent $event): void
|
|
||||||
{
|
|
||||||
$commandNames = ['doctrine:fixtures:load', 'doctrine:database:create', 'doctrine:schema:create', 'doctrine:database:drop'];
|
|
||||||
|
|
||||||
if ($event->getCommand() && \in_array($event->getCommand()->getName(), $commandNames, true)) {
|
|
||||||
if ($this->isSQLitePlatform() && !\extension_loaded('sqlite3')) {
|
|
||||||
$io = new SymfonyStyle($event->getInput(), $event->getOutput());
|
|
||||||
$io->error('This command requires to have the "sqlite3" PHP extension enabled because, by default, the Symfony Demo application uses SQLite to store its information.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method checks if the triggered exception is related to the database
|
|
||||||
* and then, it checks if the required 'sqlite3' PHP extension is enabled.
|
|
||||||
*/
|
|
||||||
public function handleKernelException(ExceptionEvent $event): void
|
|
||||||
{
|
|
||||||
$exception = $event->getThrowable();
|
|
||||||
// Since any exception thrown during a Twig template rendering is wrapped
|
|
||||||
// in a Twig_Error_Runtime, we must get the original exception.
|
|
||||||
$previousException = $exception->getPrevious();
|
|
||||||
|
|
||||||
// Driver exception may happen in controller or in twig template rendering
|
|
||||||
$isDriverException = ($exception instanceof DriverException || $previousException instanceof DriverException);
|
|
||||||
|
|
||||||
// Check if SQLite is enabled
|
|
||||||
if ($isDriverException && $this->isSQLitePlatform() && !\extension_loaded('sqlite3')) {
|
|
||||||
$event->setThrowable(new \Exception('PHP extension "sqlite3" must be enabled because, by default, the Symfony Demo application uses SQLite to store its information.'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the application is using SQLite as its database.
|
|
||||||
*/
|
|
||||||
private function isSQLitePlatform(): bool
|
|
||||||
{
|
|
||||||
$databasePlatform = $this->entityManager->getConnection()->getDatabasePlatform();
|
|
||||||
|
|
||||||
return $databasePlatform ? 'sqlite' === $databasePlatform->getName() : false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\EventSubscriber;
|
|
||||||
|
|
||||||
use App\Entity\Comment;
|
|
||||||
use App\Event\CommentCreatedEvent;
|
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
|
||||||
use Symfony\Component\Mailer\MailerInterface;
|
|
||||||
use Symfony\Component\Mime\Email;
|
|
||||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies post's author about new comments.
|
|
||||||
*
|
|
||||||
* @author Oleg Voronkovich <oleg-voronkovich@yandex.ru>
|
|
||||||
*/
|
|
||||||
class CommentNotificationSubscriber implements EventSubscriberInterface
|
|
||||||
{
|
|
||||||
private $mailer;
|
|
||||||
private $translator;
|
|
||||||
private $urlGenerator;
|
|
||||||
private $sender;
|
|
||||||
|
|
||||||
public function __construct(MailerInterface $mailer, UrlGeneratorInterface $urlGenerator, TranslatorInterface $translator, string $sender)
|
|
||||||
{
|
|
||||||
$this->mailer = $mailer;
|
|
||||||
$this->urlGenerator = $urlGenerator;
|
|
||||||
$this->translator = $translator;
|
|
||||||
$this->sender = $sender;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getSubscribedEvents(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
CommentCreatedEvent::class => 'onCommentCreated',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onCommentCreated(CommentCreatedEvent $event): void
|
|
||||||
{
|
|
||||||
/** @var Comment $comment */
|
|
||||||
$comment = $event->getComment();
|
|
||||||
$post = $comment->getPost();
|
|
||||||
|
|
||||||
$linkToPost = $this->urlGenerator->generate('blog_post', [
|
|
||||||
'slug' => $post->getSlug(),
|
|
||||||
'_fragment' => 'comment_'.$comment->getId(),
|
|
||||||
], UrlGeneratorInterface::ABSOLUTE_URL);
|
|
||||||
|
|
||||||
$subject = $this->translator->trans('notification.comment_created');
|
|
||||||
$body = $this->translator->trans('notification.comment_created.description', [
|
|
||||||
'%title%' => $post->getTitle(),
|
|
||||||
'%link%' => $linkToPost,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// See https://symfony.com/doc/current/mailer.html
|
|
||||||
$email = (new Email())
|
|
||||||
->from($this->sender)
|
|
||||||
->to($post->getAuthor()->getEmail())
|
|
||||||
->subject($subject)
|
|
||||||
->html($body)
|
|
||||||
;
|
|
||||||
|
|
||||||
// In config/packages/dev/mailer.yaml the delivery of messages is disabled.
|
|
||||||
// That's why in the development environment you won't actually receive any email.
|
|
||||||
// However, you can inspect the contents of those unsent emails using the debug toolbar.
|
|
||||||
$this->mailer->send($email);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\EventSubscriber;
|
|
||||||
|
|
||||||
use App\Twig\SourceCodeExtension;
|
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
|
||||||
use Symfony\Component\HttpKernel\Event\ControllerEvent;
|
|
||||||
use Symfony\Component\HttpKernel\KernelEvents;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the method that 'listens' to the 'kernel.controller' event, which is
|
|
||||||
* triggered whenever a controller is executed in the application.
|
|
||||||
*
|
|
||||||
* @author Ryan Weaver <weaverryan@gmail.com>
|
|
||||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
|
||||||
*/
|
|
||||||
class ControllerSubscriber implements EventSubscriberInterface
|
|
||||||
{
|
|
||||||
private $twigExtension;
|
|
||||||
|
|
||||||
public function __construct(SourceCodeExtension $twigExtension)
|
|
||||||
{
|
|
||||||
$this->twigExtension = $twigExtension;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getSubscribedEvents(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
KernelEvents::CONTROLLER => 'registerCurrentController',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function registerCurrentController(ControllerEvent $event): void
|
|
||||||
{
|
|
||||||
// this check is needed because in Symfony a request can perform any
|
|
||||||
// number of sub-requests. See
|
|
||||||
// https://symfony.com/doc/current/components/http_kernel.html#sub-requests
|
|
||||||
if ($event->isMasterRequest()) {
|
|
||||||
$this->twigExtension->setController($event->getController());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,86 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\EventSubscriber;
|
|
||||||
|
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
|
||||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
|
||||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
|
||||||
use Symfony\Component\HttpKernel\KernelEvents;
|
|
||||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
|
||||||
use function Symfony\Component\String\u;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When visiting the homepage, this listener redirects the user to the most
|
|
||||||
* appropriate localized version according to the browser settings.
|
|
||||||
*
|
|
||||||
* See https://symfony.com/doc/current/components/http_kernel.html#the-kernel-request-event
|
|
||||||
*
|
|
||||||
* @author Oleg Voronkovich <oleg-voronkovich@yandex.ru>
|
|
||||||
*/
|
|
||||||
class RedirectToPreferredLocaleSubscriber implements EventSubscriberInterface
|
|
||||||
{
|
|
||||||
private $urlGenerator;
|
|
||||||
private $locales;
|
|
||||||
private $defaultLocale;
|
|
||||||
|
|
||||||
public function __construct(UrlGeneratorInterface $urlGenerator, string $locales, string $defaultLocale = null)
|
|
||||||
{
|
|
||||||
$this->urlGenerator = $urlGenerator;
|
|
||||||
|
|
||||||
$this->locales = explode('|', trim($locales));
|
|
||||||
if (empty($this->locales)) {
|
|
||||||
throw new \UnexpectedValueException('The list of supported locales must not be empty.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->defaultLocale = $defaultLocale ?: $this->locales[0];
|
|
||||||
|
|
||||||
if (!\in_array($this->defaultLocale, $this->locales, true)) {
|
|
||||||
throw new \UnexpectedValueException(sprintf('The default locale ("%s") must be one of "%s".', $this->defaultLocale, $locales));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the default locale at the first position of the array,
|
|
||||||
// because Symfony\HttpFoundation\Request::getPreferredLanguage
|
|
||||||
// returns the first element when no an appropriate language is found
|
|
||||||
array_unshift($this->locales, $this->defaultLocale);
|
|
||||||
$this->locales = array_unique($this->locales);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getSubscribedEvents(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
KernelEvents::REQUEST => 'onKernelRequest',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onKernelRequest(RequestEvent $event): void
|
|
||||||
{
|
|
||||||
$request = $event->getRequest();
|
|
||||||
|
|
||||||
// Ignore sub-requests and all URLs but the homepage
|
|
||||||
if (!$event->isMasterRequest() || '/' !== $request->getPathInfo()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Ignore requests from referrers with the same HTTP host in order to prevent
|
|
||||||
// changing language for users who possibly already selected it for this application.
|
|
||||||
$referrer = $request->headers->get('referer');
|
|
||||||
if (null !== $referrer && u($referrer)->ignoreCase()->startsWith($request->getSchemeAndHttpHost())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$preferredLanguage = $request->getPreferredLanguage($this->locales);
|
|
||||||
|
|
||||||
if ($preferredLanguage !== $this->defaultLocale) {
|
|
||||||
$response = new RedirectResponse($this->urlGenerator->generate('homepage', ['_locale' => $preferredLanguage]));
|
|
||||||
$event->setResponse($response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
26
app/src/Form/BeerType.php
Normal file
26
app/src/Form/BeerType.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Entity\Beer;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class BeerType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('name')
|
||||||
|
->add('alcool')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => Beer::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -1,59 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Form;
|
|
||||||
|
|
||||||
use App\Entity\Comment;
|
|
||||||
use Symfony\Component\Form\AbstractType;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the form used to create and manipulate blog comments. Although in this
|
|
||||||
* case the form is trivial and we could build it inside the controller, a good
|
|
||||||
* practice is to always define your forms as classes.
|
|
||||||
*
|
|
||||||
* See https://symfony.com/doc/current/forms.html#creating-form-classes
|
|
||||||
*
|
|
||||||
* @author Ryan Weaver <weaverryan@gmail.com>
|
|
||||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
|
||||||
*/
|
|
||||||
class CommentType extends AbstractType
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
|
||||||
{
|
|
||||||
// By default, form fields include the 'required' attribute, which enables
|
|
||||||
// the client-side form validation. This means that you can't test the
|
|
||||||
// server-side validation errors from the browser. To temporarily disable
|
|
||||||
// this validation, set the 'required' attribute to 'false':
|
|
||||||
// $builder->add('content', null, ['required' => false]);
|
|
||||||
|
|
||||||
$builder
|
|
||||||
->add('content', TextareaType::class, [
|
|
||||||
'help' => 'help.comment_content',
|
|
||||||
])
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function configureOptions(OptionsResolver $resolver): void
|
|
||||||
{
|
|
||||||
$resolver->setDefaults([
|
|
||||||
'data_class' => Comment::class,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Form\DataTransformer;
|
|
||||||
|
|
||||||
use App\Entity\Tag;
|
|
||||||
use App\Repository\TagRepository;
|
|
||||||
use Symfony\Component\Form\DataTransformerInterface;
|
|
||||||
use function Symfony\Component\String\u;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This data transformer is used to translate the array of tags into a comma separated format
|
|
||||||
* that can be displayed and managed by Bootstrap-tagsinput js plugin (and back on submit).
|
|
||||||
*
|
|
||||||
* See https://symfony.com/doc/current/form/data_transformers.html
|
|
||||||
*
|
|
||||||
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
|
||||||
* @author Jonathan Boyer <contact@grafikart.fr>
|
|
||||||
*/
|
|
||||||
class TagArrayToStringTransformer implements DataTransformerInterface
|
|
||||||
{
|
|
||||||
private $tags;
|
|
||||||
|
|
||||||
public function __construct(TagRepository $tags)
|
|
||||||
{
|
|
||||||
$this->tags = $tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function transform($tags): string
|
|
||||||
{
|
|
||||||
// The value received is an array of Tag objects generated with
|
|
||||||
// Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer::transform()
|
|
||||||
// The value returned is a string that concatenates the string representation of those objects
|
|
||||||
|
|
||||||
/* @var Tag[] $tags */
|
|
||||||
return implode(',', $tags);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function reverseTransform($string): array
|
|
||||||
{
|
|
||||||
if (null === $string || u($string)->isEmpty()) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$names = array_filter(array_unique(array_map('trim', u($string)->split(','))));
|
|
||||||
|
|
||||||
// Get the current tags and find the new ones that should be created.
|
|
||||||
$tags = $this->tags->findBy([
|
|
||||||
'name' => $names,
|
|
||||||
]);
|
|
||||||
$newNames = array_diff($names, $tags);
|
|
||||||
foreach ($newNames as $name) {
|
|
||||||
$tag = new Tag();
|
|
||||||
$tag->setName($name);
|
|
||||||
$tags[] = $tag;
|
|
||||||
|
|
||||||
// There's no need to persist these new tags because Doctrine does that automatically
|
|
||||||
// thanks to the cascade={"persist"} option in the App\Entity\Post::$tags property.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return an array of tags to transform them back into a Doctrine Collection.
|
|
||||||
// See Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer::reverseTransform()
|
|
||||||
return $tags;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,100 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Form;
|
|
||||||
|
|
||||||
use App\Entity\Post;
|
|
||||||
use App\Form\Type\DateTimePickerType;
|
|
||||||
use App\Form\Type\TagsInputType;
|
|
||||||
use Symfony\Component\Form\AbstractType;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
|
||||||
use Symfony\Component\Form\FormEvent;
|
|
||||||
use Symfony\Component\Form\FormEvents;
|
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
|
||||||
use Symfony\Component\String\Slugger\SluggerInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the form used to create and manipulate blog posts.
|
|
||||||
*
|
|
||||||
* @author Ryan Weaver <weaverryan@gmail.com>
|
|
||||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
|
||||||
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
|
||||||
*/
|
|
||||||
class PostType extends AbstractType
|
|
||||||
{
|
|
||||||
private $slugger;
|
|
||||||
|
|
||||||
// Form types are services, so you can inject other services in them if needed
|
|
||||||
public function __construct(SluggerInterface $slugger)
|
|
||||||
{
|
|
||||||
$this->slugger = $slugger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
|
||||||
{
|
|
||||||
// For the full reference of options defined by each form field type
|
|
||||||
// see https://symfony.com/doc/current/reference/forms/types.html
|
|
||||||
|
|
||||||
// By default, form fields include the 'required' attribute, which enables
|
|
||||||
// the client-side form validation. This means that you can't test the
|
|
||||||
// server-side validation errors from the browser. To temporarily disable
|
|
||||||
// this validation, set the 'required' attribute to 'false':
|
|
||||||
// $builder->add('title', null, ['required' => false, ...]);
|
|
||||||
|
|
||||||
$builder
|
|
||||||
->add('title', null, [
|
|
||||||
'attr' => ['autofocus' => true],
|
|
||||||
'label' => 'label.title',
|
|
||||||
])
|
|
||||||
->add('summary', TextareaType::class, [
|
|
||||||
'help' => 'help.post_summary',
|
|
||||||
'label' => 'label.summary',
|
|
||||||
])
|
|
||||||
->add('content', null, [
|
|
||||||
'attr' => ['rows' => 20],
|
|
||||||
'help' => 'help.post_content',
|
|
||||||
'label' => 'label.content',
|
|
||||||
])
|
|
||||||
->add('publishedAt', DateTimePickerType::class, [
|
|
||||||
'label' => 'label.published_at',
|
|
||||||
'help' => 'help.post_publication',
|
|
||||||
])
|
|
||||||
->add('tags', TagsInputType::class, [
|
|
||||||
'label' => 'label.tags',
|
|
||||||
'required' => false,
|
|
||||||
])
|
|
||||||
// form events let you modify information or fields at different steps
|
|
||||||
// of the form handling process.
|
|
||||||
// See https://symfony.com/doc/current/form/events.html
|
|
||||||
->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) {
|
|
||||||
/** @var Post */
|
|
||||||
$post = $event->getData();
|
|
||||||
if (null !== $postTitle = $post->getTitle()) {
|
|
||||||
$post->setSlug($this->slugger->slug($postTitle)->lower());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function configureOptions(OptionsResolver $resolver): void
|
|
||||||
{
|
|
||||||
$resolver->setDefaults([
|
|
||||||
'data_class' => Post::class,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Form\Type;
|
|
||||||
|
|
||||||
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\Security\Core\Validator\Constraints\UserPassword;
|
|
||||||
use Symfony\Component\Validator\Constraints\Length;
|
|
||||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the custom form field type used to change user's password.
|
|
||||||
*
|
|
||||||
* @author Romain Monteil <monteil.romain@gmail.com>
|
|
||||||
*/
|
|
||||||
class ChangePasswordType extends AbstractType
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
|
||||||
{
|
|
||||||
$builder
|
|
||||||
->add('currentPassword', PasswordType::class, [
|
|
||||||
'constraints' => [
|
|
||||||
new UserPassword(),
|
|
||||||
],
|
|
||||||
'label' => 'label.current_password',
|
|
||||||
'attr' => [
|
|
||||||
'autocomplete' => 'off',
|
|
||||||
],
|
|
||||||
])
|
|
||||||
->add('newPassword', RepeatedType::class, [
|
|
||||||
'type' => PasswordType::class,
|
|
||||||
'constraints' => [
|
|
||||||
new NotBlank(),
|
|
||||||
new Length([
|
|
||||||
'min' => 5,
|
|
||||||
'max' => 128,
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
'first_options' => [
|
|
||||||
'label' => 'label.new_password',
|
|
||||||
],
|
|
||||||
'second_options' => [
|
|
||||||
'label' => 'label.new_password_confirm',
|
|
||||||
],
|
|
||||||
])
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Form\Type;
|
|
||||||
|
|
||||||
use App\Utils\MomentFormatConverter;
|
|
||||||
use Symfony\Component\Form\AbstractType;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
|
|
||||||
use Symfony\Component\Form\FormInterface;
|
|
||||||
use Symfony\Component\Form\FormView;
|
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
|
||||||
use function Symfony\Component\String\u;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the custom form field type used to manipulate datetime values across
|
|
||||||
* Bootstrap Date\Time Picker javascript plugin.
|
|
||||||
*
|
|
||||||
* See https://symfony.com/doc/current/form/create_custom_field_type.html
|
|
||||||
*
|
|
||||||
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
|
||||||
*/
|
|
||||||
class DateTimePickerType extends AbstractType
|
|
||||||
{
|
|
||||||
private $formatConverter;
|
|
||||||
|
|
||||||
public function __construct(MomentFormatConverter $converter)
|
|
||||||
{
|
|
||||||
$this->formatConverter = $converter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
|
||||||
{
|
|
||||||
$view->vars['attr']['data-date-format'] = $this->formatConverter->convert($options['format']);
|
|
||||||
$view->vars['attr']['data-date-locale'] = u(\Locale::getDefault())->replace('_', '-')->lower();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function configureOptions(OptionsResolver $resolver): void
|
|
||||||
{
|
|
||||||
$resolver->setDefaults([
|
|
||||||
'widget' => 'single_text',
|
|
||||||
// if true, the browser will display the native date picker widget
|
|
||||||
// however, this app uses a custom JavaScript widget, so it must be set to false
|
|
||||||
'html5' => false,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getParent(): ?string
|
|
||||||
{
|
|
||||||
return DateTimeType::class;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Form\Type;
|
|
||||||
|
|
||||||
use App\Form\DataTransformer\TagArrayToStringTransformer;
|
|
||||||
use App\Repository\TagRepository;
|
|
||||||
use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer;
|
|
||||||
use Symfony\Component\Form\AbstractType;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
|
||||||
use Symfony\Component\Form\FormInterface;
|
|
||||||
use Symfony\Component\Form\FormView;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the custom form field type used to manipulate tags values across
|
|
||||||
* Bootstrap-tagsinput javascript plugin.
|
|
||||||
*
|
|
||||||
* See https://symfony.com/doc/current/form/create_custom_field_type.html
|
|
||||||
*
|
|
||||||
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
|
||||||
*/
|
|
||||||
class TagsInputType extends AbstractType
|
|
||||||
{
|
|
||||||
private $tags;
|
|
||||||
|
|
||||||
public function __construct(TagRepository $tags)
|
|
||||||
{
|
|
||||||
$this->tags = $tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
|
||||||
{
|
|
||||||
$builder
|
|
||||||
// The Tag collection must be transformed into a comma separated string.
|
|
||||||
// We could create a custom transformer to do Collection <-> string in one step,
|
|
||||||
// but here we're doing the transformation in two steps (Collection <-> array <-> string)
|
|
||||||
// and reuse the existing CollectionToArrayTransformer.
|
|
||||||
->addModelTransformer(new CollectionToArrayTransformer(), true)
|
|
||||||
->addModelTransformer(new TagArrayToStringTransformer($this->tags), true)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
|
||||||
{
|
|
||||||
$view->vars['tags'] = $this->tags->findAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getParent(): ?string
|
|
||||||
{
|
|
||||||
return TextType::class;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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\TextType;
|
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the form used to edit an user.
|
|
||||||
*
|
|
||||||
* @author Romain Monteil <monteil.romain@gmail.com>
|
|
||||||
*/
|
|
||||||
class UserType extends AbstractType
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
|
||||||
{
|
|
||||||
// For the full reference of options defined by each form field type
|
|
||||||
// see https://symfony.com/doc/current/reference/forms/types.html
|
|
||||||
|
|
||||||
// By default, form fields include the 'required' attribute, which enables
|
|
||||||
// the client-side form validation. This means that you can't test the
|
|
||||||
// server-side validation errors from the browser. To temporarily disable
|
|
||||||
// this validation, set the 'required' attribute to 'false':
|
|
||||||
// $builder->add('title', null, ['required' => false, ...]);
|
|
||||||
|
|
||||||
$builder
|
|
||||||
->add('username', TextType::class, [
|
|
||||||
'label' => 'label.username',
|
|
||||||
'disabled' => true,
|
|
||||||
])
|
|
||||||
->add('fullName', TextType::class, [
|
|
||||||
'label' => 'label.fullname',
|
|
||||||
])
|
|
||||||
->add('email', EmailType::class, [
|
|
||||||
'label' => 'label.email',
|
|
||||||
])
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function configureOptions(OptionsResolver $resolver): void
|
|
||||||
{
|
|
||||||
$resolver->setDefaults([
|
|
||||||
'data_class' => User::class,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App;
|
namespace App;
|
||||||
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
||||||
|
@ -1,117 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Pagination;
|
|
||||||
|
|
||||||
use Doctrine\ORM\QueryBuilder as DoctrineQueryBuilder;
|
|
||||||
use Doctrine\ORM\Tools\Pagination\CountWalker;
|
|
||||||
use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
|
||||||
*/
|
|
||||||
class Paginator
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Use constants to define configuration options that rarely change instead
|
|
||||||
* of specifying them under parameters section in config/services.yaml file.
|
|
||||||
*
|
|
||||||
* See https://symfony.com/doc/current/best_practices.html#use-constants-to-define-options-that-rarely-change
|
|
||||||
*/
|
|
||||||
public const PAGE_SIZE = 10;
|
|
||||||
|
|
||||||
private $queryBuilder;
|
|
||||||
private $currentPage;
|
|
||||||
private $pageSize;
|
|
||||||
private $results;
|
|
||||||
private $numResults;
|
|
||||||
|
|
||||||
public function __construct(DoctrineQueryBuilder $queryBuilder, int $pageSize = self::PAGE_SIZE)
|
|
||||||
{
|
|
||||||
$this->queryBuilder = $queryBuilder;
|
|
||||||
$this->pageSize = $pageSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function paginate(int $page = 1): self
|
|
||||||
{
|
|
||||||
$this->currentPage = max(1, $page);
|
|
||||||
$firstResult = ($this->currentPage - 1) * $this->pageSize;
|
|
||||||
|
|
||||||
$query = $this->queryBuilder
|
|
||||||
->setFirstResult($firstResult)
|
|
||||||
->setMaxResults($this->pageSize)
|
|
||||||
->getQuery();
|
|
||||||
|
|
||||||
if (0 === \count($this->queryBuilder->getDQLPart('join'))) {
|
|
||||||
$query->setHint(CountWalker::HINT_DISTINCT, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
$paginator = new DoctrinePaginator($query, true);
|
|
||||||
|
|
||||||
$useOutputWalkers = \count($this->queryBuilder->getDQLPart('having') ?: []) > 0;
|
|
||||||
$paginator->setUseOutputWalkers($useOutputWalkers);
|
|
||||||
|
|
||||||
$this->results = $paginator->getIterator();
|
|
||||||
$this->numResults = $paginator->count();
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCurrentPage(): int
|
|
||||||
{
|
|
||||||
return $this->currentPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLastPage(): int
|
|
||||||
{
|
|
||||||
return (int) ceil($this->numResults / $this->pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPageSize(): int
|
|
||||||
{
|
|
||||||
return $this->pageSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasPreviousPage(): bool
|
|
||||||
{
|
|
||||||
return $this->currentPage > 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPreviousPage(): int
|
|
||||||
{
|
|
||||||
return max(1, $this->currentPage - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasNextPage(): bool
|
|
||||||
{
|
|
||||||
return $this->currentPage < $this->getLastPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getNextPage(): int
|
|
||||||
{
|
|
||||||
return min($this->getLastPage(), $this->currentPage + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasToPaginate(): bool
|
|
||||||
{
|
|
||||||
return $this->numResults > $this->pageSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getNumResults(): int
|
|
||||||
{
|
|
||||||
return $this->numResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getResults(): \Traversable
|
|
||||||
{
|
|
||||||
return $this->results;
|
|
||||||
}
|
|
||||||
}
|
|
0
app/src/Repository/.gitignore
vendored
Normal file
0
app/src/Repository/.gitignore
vendored
Normal file
50
app/src/Repository/BeerRepository.php
Normal file
50
app/src/Repository/BeerRepository.php
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\Beer;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method Beer|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
|
* @method Beer|null findOneBy(array $criteria, array $orderBy = null)
|
||||||
|
* @method Beer[] findAll()
|
||||||
|
* @method Beer[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
*/
|
||||||
|
class BeerRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, Beer::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @return Beer[] Returns an array of Beer objects
|
||||||
|
// */
|
||||||
|
/*
|
||||||
|
public function findByExampleField($value)
|
||||||
|
{
|
||||||
|
return $this->createQueryBuilder('b')
|
||||||
|
->andWhere('b.exampleField = :val')
|
||||||
|
->setParameter('val', $value)
|
||||||
|
->orderBy('b.id', 'ASC')
|
||||||
|
->setMaxResults(10)
|
||||||
|
->getQuery()
|
||||||
|
->getResult()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
public function findOneBySomeField($value): ?Beer
|
||||||
|
{
|
||||||
|
return $this->createQueryBuilder('b')
|
||||||
|
->andWhere('b.exampleField = :val')
|
||||||
|
->setParameter('val', $value)
|
||||||
|
->getQuery()
|
||||||
|
->getOneOrNullResult()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
@ -1,97 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Repository;
|
|
||||||
|
|
||||||
use App\Entity\Post;
|
|
||||||
use App\Entity\Tag;
|
|
||||||
use App\Pagination\Paginator;
|
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
|
||||||
use function Symfony\Component\String\u;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This custom Doctrine repository contains some methods which are useful when
|
|
||||||
* querying for blog post information.
|
|
||||||
*
|
|
||||||
* See https://symfony.com/doc/current/doctrine.html#querying-for-objects-the-repository
|
|
||||||
*
|
|
||||||
* @author Ryan Weaver <weaverryan@gmail.com>
|
|
||||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
|
||||||
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
|
||||||
*/
|
|
||||||
class PostRepository extends ServiceEntityRepository
|
|
||||||
{
|
|
||||||
public function __construct(ManagerRegistry $registry)
|
|
||||||
{
|
|
||||||
parent::__construct($registry, Post::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function findLatest(int $page = 1, Tag $tag = null): Paginator
|
|
||||||
{
|
|
||||||
$qb = $this->createQueryBuilder('p')
|
|
||||||
->addSelect('a', 't')
|
|
||||||
->innerJoin('p.author', 'a')
|
|
||||||
->leftJoin('p.tags', 't')
|
|
||||||
->where('p.publishedAt <= :now')
|
|
||||||
->orderBy('p.publishedAt', 'DESC')
|
|
||||||
->setParameter('now', new \DateTime())
|
|
||||||
;
|
|
||||||
|
|
||||||
if (null !== $tag) {
|
|
||||||
$qb->andWhere(':tag MEMBER OF p.tags')
|
|
||||||
->setParameter('tag', $tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (new Paginator($qb))->paginate($page);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Post[]
|
|
||||||
*/
|
|
||||||
public function findBySearchQuery(string $query, int $limit = Paginator::PAGE_SIZE): array
|
|
||||||
{
|
|
||||||
$searchTerms = $this->extractSearchTerms($query);
|
|
||||||
|
|
||||||
if (0 === \count($searchTerms)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$queryBuilder = $this->createQueryBuilder('p');
|
|
||||||
|
|
||||||
foreach ($searchTerms as $key => $term) {
|
|
||||||
$queryBuilder
|
|
||||||
->orWhere('p.title LIKE :t_'.$key)
|
|
||||||
->setParameter('t_'.$key, '%'.$term.'%')
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $queryBuilder
|
|
||||||
->orderBy('p.publishedAt', 'DESC')
|
|
||||||
->setMaxResults($limit)
|
|
||||||
->getQuery()
|
|
||||||
->getResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms the search string into an array of search terms.
|
|
||||||
*/
|
|
||||||
private function extractSearchTerms(string $searchQuery): array
|
|
||||||
{
|
|
||||||
$searchQuery = u($searchQuery)->replaceMatches('/[[:space:]]+/', ' ')->trim();
|
|
||||||
$terms = array_unique($searchQuery->split(' '));
|
|
||||||
|
|
||||||
// ignore the search terms that are too short
|
|
||||||
return array_filter($terms, function ($term) {
|
|
||||||
return 2 <= $term->length();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Repository;
|
|
||||||
|
|
||||||
use App\Entity\Tag;
|
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This custom Doctrine repository is empty because so far we don't need any custom
|
|
||||||
* method to query for application user information. But it's always a good practice
|
|
||||||
* to define a custom repository that will be used when the application grows.
|
|
||||||
*
|
|
||||||
* See https://symfony.com/doc/current/doctrine.html#querying-for-objects-the-repository
|
|
||||||
*
|
|
||||||
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
|
||||||
*/
|
|
||||||
class TagRepository extends ServiceEntityRepository
|
|
||||||
{
|
|
||||||
public function __construct(ManagerRegistry $registry)
|
|
||||||
{
|
|
||||||
parent::__construct($registry, Tag::class);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Repository;
|
|
||||||
|
|
||||||
use App\Entity\User;
|
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This custom Doctrine repository is empty because so far we don't need any custom
|
|
||||||
* method to query for application user information. But it's always a good practice
|
|
||||||
* to define a custom repository that will be used when the application grows.
|
|
||||||
*
|
|
||||||
* See https://symfony.com/doc/current/doctrine.html#querying-for-objects-the-repository
|
|
||||||
*
|
|
||||||
* @author Ryan Weaver <weaverryan@gmail.com>
|
|
||||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
|
||||||
*/
|
|
||||||
class UserRepository extends ServiceEntityRepository
|
|
||||||
{
|
|
||||||
public function __construct(ManagerRegistry $registry)
|
|
||||||
{
|
|
||||||
parent::__construct($registry, User::class);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Security;
|
|
||||||
|
|
||||||
use App\Entity\Post;
|
|
||||||
use App\Entity\User;
|
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
|
||||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* It grants or denies permissions for actions related to blog posts (such as
|
|
||||||
* showing, editing and deleting posts).
|
|
||||||
*
|
|
||||||
* See https://symfony.com/doc/current/security/voters.html
|
|
||||||
*
|
|
||||||
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
|
||||||
*/
|
|
||||||
class PostVoter extends Voter
|
|
||||||
{
|
|
||||||
// Defining these constants is overkill for this simple application, but for real
|
|
||||||
// applications, it's a recommended practice to avoid relying on "magic strings"
|
|
||||||
public const DELETE = 'delete';
|
|
||||||
public const EDIT = 'edit';
|
|
||||||
public const SHOW = 'show';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function supports(string $attribute, $subject): bool
|
|
||||||
{
|
|
||||||
// this voter is only executed for three specific permissions on Post objects
|
|
||||||
return $subject instanceof Post && \in_array($attribute, [self::SHOW, self::EDIT, self::DELETE], true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @param Post $post
|
|
||||||
*/
|
|
||||||
protected function voteOnAttribute(string $attribute, $post, TokenInterface $token): bool
|
|
||||||
{
|
|
||||||
$user = $token->getUser();
|
|
||||||
|
|
||||||
// the user must be logged in; if not, deny permission
|
|
||||||
if (!$user instanceof User) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the logic of this voter is pretty simple: if the logged user is the
|
|
||||||
// author of the given blog post, grant permission; otherwise, deny it.
|
|
||||||
// (the supports() method guarantees that $post is a Post object)
|
|
||||||
return $user === $post->getAuthor();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Twig;
|
|
||||||
|
|
||||||
use Symfony\Component\Intl\Locales;
|
|
||||||
use Twig\Extension\AbstractExtension;
|
|
||||||
use Twig\TwigFunction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See https://symfony.com/doc/current/templating/twig_extension.html.
|
|
||||||
*
|
|
||||||
* @author Ryan Weaver <weaverryan@gmail.com>
|
|
||||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
|
||||||
* @author Julien ITARD <julienitard@gmail.com>
|
|
||||||
*/
|
|
||||||
class AppExtension extends AbstractExtension
|
|
||||||
{
|
|
||||||
private $localeCodes;
|
|
||||||
private $locales;
|
|
||||||
|
|
||||||
public function __construct(string $locales)
|
|
||||||
{
|
|
||||||
$localeCodes = explode('|', $locales);
|
|
||||||
sort($localeCodes);
|
|
||||||
$this->localeCodes = $localeCodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getFunctions(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
new TwigFunction('locales', [$this, 'getLocales']),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes the list of codes of the locales (languages) enabled in the
|
|
||||||
* application and returns an array with the name of each locale written
|
|
||||||
* in its own language (e.g. English, Français, Español, etc.).
|
|
||||||
*/
|
|
||||||
public function getLocales(): array
|
|
||||||
{
|
|
||||||
if (null !== $this->locales) {
|
|
||||||
return $this->locales;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->locales = [];
|
|
||||||
foreach ($this->localeCodes as $localeCode) {
|
|
||||||
$this->locales[] = ['code' => $localeCode, 'name' => Locales::getName($localeCode, $localeCode)];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->locales;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,137 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Twig;
|
|
||||||
|
|
||||||
use function Symfony\Component\String\u;
|
|
||||||
use Twig\Environment;
|
|
||||||
use Twig\Extension\AbstractExtension;
|
|
||||||
use Twig\Template;
|
|
||||||
use Twig\TemplateWrapper;
|
|
||||||
use Twig\TwigFunction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CAUTION: this is an extremely advanced Twig extension. It's used to get the
|
|
||||||
* source code of the controller and the template used to render the current
|
|
||||||
* page. If you are starting with Symfony, don't look at this code and consider
|
|
||||||
* studying instead the code of the src/App/Twig/AppExtension.php extension.
|
|
||||||
*
|
|
||||||
* @author Ryan Weaver <weaverryan@gmail.com>
|
|
||||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
|
||||||
*/
|
|
||||||
class SourceCodeExtension extends AbstractExtension
|
|
||||||
{
|
|
||||||
private $controller;
|
|
||||||
|
|
||||||
public function setController(?callable $controller)
|
|
||||||
{
|
|
||||||
$this->controller = $controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getFunctions(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
new TwigFunction('show_source_code', [$this, 'showSourceCode'], ['is_safe' => ['html'], 'needs_environment' => true]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|TemplateWrapper|array $template
|
|
||||||
*/
|
|
||||||
public function showSourceCode(Environment $twig, $template): string
|
|
||||||
{
|
|
||||||
return $twig->render('debug/source_code.html.twig', [
|
|
||||||
'controller' => $this->getController(),
|
|
||||||
'template' => $this->getTemplateSource($twig->resolveTemplate($template)),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getController(): ?array
|
|
||||||
{
|
|
||||||
// this happens for example for exceptions (404 errors, etc.)
|
|
||||||
if (null === $this->controller) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$method = $this->getCallableReflector($this->controller);
|
|
||||||
|
|
||||||
$classCode = file($method->getFileName());
|
|
||||||
$methodCode = \array_slice($classCode, $method->getStartLine() - 1, $method->getEndLine() - $method->getStartLine() + 1);
|
|
||||||
$controllerCode = ' '.$method->getDocComment()."\n".implode('', $methodCode);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'file_path' => $method->getFileName(),
|
|
||||||
'starting_line' => $method->getStartLine(),
|
|
||||||
'source_code' => $this->unindentCode($controllerCode),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a reflector for a callable.
|
|
||||||
*
|
|
||||||
* This logic is copied from Symfony\Component\HttpKernel\Controller\ControllerResolver::getArguments
|
|
||||||
*/
|
|
||||||
private function getCallableReflector(callable $callable): \ReflectionFunctionAbstract
|
|
||||||
{
|
|
||||||
if (\is_array($callable)) {
|
|
||||||
return new \ReflectionMethod($callable[0], $callable[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (\is_object($callable) && !$callable instanceof \Closure) {
|
|
||||||
$r = new \ReflectionObject($callable);
|
|
||||||
|
|
||||||
return $r->getMethod('__invoke');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new \ReflectionFunction($callable);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getTemplateSource(TemplateWrapper $template): array
|
|
||||||
{
|
|
||||||
$templateSource = $template->getSourceContext();
|
|
||||||
|
|
||||||
return [
|
|
||||||
// Twig templates are not always stored in files (they can be stored
|
|
||||||
// in a database for example). However, for the needs of the Symfony
|
|
||||||
// Demo app, we consider that all templates are stored in files and
|
|
||||||
// that their file paths can be obtained through the source context.
|
|
||||||
'file_path' => $templateSource->getPath(),
|
|
||||||
'starting_line' => 1,
|
|
||||||
'source_code' => $templateSource->getCode(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility method that "unindents" the given $code when all its lines start
|
|
||||||
* with a tabulation of four white spaces.
|
|
||||||
*/
|
|
||||||
private function unindentCode(string $code): string
|
|
||||||
{
|
|
||||||
$codeLines = u($code)->split("\n");
|
|
||||||
|
|
||||||
$indentedOrBlankLines = array_filter($codeLines, function ($lineOfCode) {
|
|
||||||
return u($lineOfCode)->isEmpty() || u($lineOfCode)->startsWith(' ');
|
|
||||||
});
|
|
||||||
|
|
||||||
$codeIsIndented = \count($indentedOrBlankLines) === \count($codeLines);
|
|
||||||
if ($codeIsIndented) {
|
|
||||||
$unindentedLines = array_map(function ($lineOfCode) {
|
|
||||||
return u($lineOfCode)->after(' ');
|
|
||||||
}, $codeLines);
|
|
||||||
$code = u("\n")->join($unindentedLines);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $code;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Utils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is used to convert PHP date format to moment.js format.
|
|
||||||
*
|
|
||||||
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
|
||||||
*/
|
|
||||||
class MomentFormatConverter
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This defines the mapping between PHP ICU date format (key) and moment.js date format (value)
|
|
||||||
* For ICU formats see http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax
|
|
||||||
* For Moment formats see https://momentjs.com/docs/#/displaying/format/.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private static $formatConvertRules = [
|
|
||||||
// year
|
|
||||||
'yyyy' => 'YYYY', 'yy' => 'YY', 'y' => 'YYYY',
|
|
||||||
// day
|
|
||||||
'dd' => 'DD', 'd' => 'D',
|
|
||||||
// day of week
|
|
||||||
'EE' => 'ddd', 'EEEEEE' => 'dd',
|
|
||||||
// timezone
|
|
||||||
'ZZZZZ' => 'Z', 'ZZZ' => 'ZZ',
|
|
||||||
// letter 'T'
|
|
||||||
'\'T\'' => 'T',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns associated moment.js format.
|
|
||||||
*/
|
|
||||||
public function convert(string $format): string
|
|
||||||
{
|
|
||||||
return strtr($format, self::$formatConvertRules);
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user