Full Code of the-fast-track/book-5.0-1 for AI

master 26f6458f88ca cached
131 files
11.5 MB
33.2k tokens
158 symbols
1 requests
Download .txt
Repository: the-fast-track/book-5.0-1
Branch: master
Commit: 26f6458f88ca
Files: 131
Total size: 11.5 MB

Directory structure:
gitextract_iisn5zjd/

├── .blackfire.yaml
├── .gitignore
├── .symfony/
│   ├── config.vcl
│   ├── routes.yaml
│   └── services.yaml
├── .symfony.cloud.yaml
├── Makefile
├── assets/
│   ├── css/
│   │   ├── _variables.scss
│   │   └── app.scss
│   └── js/
│       └── app.js
├── bin/
│   ├── console
│   └── phpunit
├── blackfire-player.phar
├── composer.json
├── config/
│   ├── bootstrap.php
│   ├── bundles.php
│   ├── packages/
│   │   ├── api_platform.yaml
│   │   ├── assets.yaml
│   │   ├── cache.yaml
│   │   ├── dev/
│   │   │   ├── debug.yaml
│   │   │   ├── easy_log_handler.yaml
│   │   │   ├── monolog.yaml
│   │   │   └── web_profiler.yaml
│   │   ├── doctrine.yaml
│   │   ├── doctrine_migrations.yaml
│   │   ├── easy_admin.yaml
│   │   ├── framework.yaml
│   │   ├── mailer.yaml
│   │   ├── messenger.yaml
│   │   ├── nelmio_cors.yaml
│   │   ├── notifier.yaml
│   │   ├── prod/
│   │   │   ├── doctrine.yaml
│   │   │   ├── monolog.yaml
│   │   │   ├── routing.yaml
│   │   │   └── webpack_encore.yaml
│   │   ├── routing.yaml
│   │   ├── security.yaml
│   │   ├── sensio_framework_extra.yaml
│   │   ├── test/
│   │   │   ├── dama_doctrine_test_bundle.yaml
│   │   │   ├── framework.yaml
│   │   │   ├── monolog.yaml
│   │   │   ├── twig.yaml
│   │   │   ├── validator.yaml
│   │   │   ├── web_profiler.yaml
│   │   │   └── webpack_encore.yaml
│   │   ├── translation.yaml
│   │   ├── twig.yaml
│   │   ├── validator.yaml
│   │   ├── webpack_encore.yaml
│   │   └── workflow.yaml
│   ├── routes/
│   │   ├── annotations.yaml
│   │   ├── api_platform.yaml
│   │   ├── dev/
│   │   │   ├── framework.yaml
│   │   │   └── web_profiler.yaml
│   │   └── easy_admin.yaml
│   ├── routes.yaml
│   ├── secrets/
│   │   ├── dev/
│   │   │   ├── dev.AKISMET_KEY.ca01fb.php
│   │   │   ├── dev.SLACK_DSN.b2b579.php
│   │   │   ├── dev.decrypt.private.php
│   │   │   ├── dev.encrypt.public.php
│   │   │   └── dev.list.php
│   │   ├── prod/
│   │   │   ├── prod.AKISMET_KEY.ca01fb.php
│   │   │   ├── prod.SLACK_DSN.b2b579.php
│   │   │   ├── prod.encrypt.public.php
│   │   │   └── prod.list.php
│   │   └── test/
│   │       ├── test.AKISMET_KEY.ca01fb.php
│   │       ├── test.decrypt.private.php
│   │       ├── test.encrypt.public.php
│   │       └── test.list.php
│   └── services.yaml
├── docker-compose.yaml
├── package.json
├── php.ini
├── phpunit.xml.dist
├── public/
│   └── index.php
├── spa/
│   ├── .gitignore
│   ├── .symfony.cloud.yaml
│   ├── assets/
│   │   └── css/
│   │       ├── _variables.scss
│   │       └── app.scss
│   ├── package.json
│   ├── src/
│   │   ├── api/
│   │   │   └── api.js
│   │   ├── app.js
│   │   ├── index.ejs
│   │   └── pages/
│   │       ├── conference.js
│   │       └── home.js
│   └── webpack.config.js
├── src/
│   ├── Api/
│   │   └── FilterPublishedCommentQueryExtension.php
│   ├── Command/
│   │   ├── CommentCleanupCommand.php
│   │   └── StepInfoCommand.php
│   ├── Controller/
│   │   ├── .gitignore
│   │   ├── AdminController.php
│   │   ├── ConferenceController.php
│   │   └── SecurityController.php
│   ├── DataFixtures/
│   │   └── AppFixtures.php
│   ├── Entity/
│   │   ├── .gitignore
│   │   ├── Admin.php
│   │   ├── Comment.php
│   │   └── Conference.php
│   ├── EntityListener/
│   │   └── ConferenceEntityListener.php
│   ├── Form/
│   │   └── CommentFormType.php
│   ├── ImageOptimizer.php
│   ├── Kernel.php
│   ├── Message/
│   │   └── CommentMessage.php
│   ├── MessageHandler/
│   │   └── CommentMessageHandler.php
│   ├── Migrations/
│   │   ├── .gitignore
│   │   ├── Version20200107080917.php
│   │   ├── Version20200107081222.php
│   │   ├── Version20200107081238.php
│   │   ├── Version20200107081419.php
│   │   └── Version20200107081708.php
│   ├── Notification/
│   │   └── CommentReviewNotification.php
│   ├── Repository/
│   │   ├── .gitignore
│   │   ├── AdminRepository.php
│   │   ├── CommentRepository.php
│   │   └── ConferenceRepository.php
│   ├── Security/
│   │   └── AppAuthenticator.php
│   └── SpamChecker.php
├── templates/
│   ├── admin/
│   │   └── review.html.twig
│   ├── base.html.twig
│   ├── conference/
│   │   ├── header.html.twig
│   │   ├── index.html.twig
│   │   └── show.html.twig
│   ├── emails/
│   │   └── comment_notification.html.twig
│   └── security/
│       └── login.html.twig
├── tests/
│   ├── .gitignore
│   ├── Controller/
│   │   └── ConferenceControllerTest.php
│   └── SpamCheckerTest.php
├── translations/
│   ├── .gitignore
│   ├── messages+intl-icu.en.xlf
│   └── messages+intl-icu.fr.xlf
└── webpack.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .blackfire.yaml
================================================
scenarios: |
    #!blackfire-player

    group login
        visit url('/login')
        submit button("Sign in")
            param username "admin"
            param password "admin"
            expect status_code() == 302

    scenario
        name "Submit a comment on the Amsterdam conference page"
        include login
        visit url('/fr/conference/amsterdam-2019')
            expect status_code() == 200
        submit button("Submit")
            param comment_form[author] 'Fabien'
            param comment_form[email] 'me@example.com'
            param comment_form[text] 'Such a good conference!'
            param comment_form[photo] file(fake('image', '/tmp', 400, 300, 'cats'), 'awesome-cat.jpg')
            expect status_code() == 302
        follow
            expect status_code() == 200
            expect not(body() matches "/Such a good conference/")
            # Wait for the workflow to validate the submissions
            wait 5000
        when env != "prod"
            visit url(webmail_url ~ '/messages')
                expect status_code() == 200
                set message_ids json("[*].id")
            with message_id in message_ids
                visit url(webmail_url ~ '/messages/' ~ message_id ~ '.html')
                    expect status_code() == 200
                    set accept_url css("table a").first().attr("href")
                visit url(accept_url)
                    # we don't check the status code as we can deal
                    # with "old" messages which do not exist anymore
                    # in the DB (would be a 404 then)
        when env == "prod"
            visit url('/admin/?entity=Comment&action=list')
                expect status_code() == 200
                set comment_ids css('table.table tbody tr').extract('data-id')
            with id in comment_ids
                visit url('/admin/comment/review/' ~ id)
                    # we don't check the status code as we scan all comments,
                    # including the ones already reviewed
        visit url('/fr/')
            wait 5000
        visit url('/fr/conference/amsterdam-2019')
            expect body() matches "/Such a good conference/"


================================================
FILE: .gitignore
================================================
/public/uploads

###> symfony/framework-bundle ###
/.env.local
/.env.local.php
/.env.*.local
/config/secrets/prod/prod.decrypt.private.php
/public/bundles/
/var/
/vendor/
###< symfony/framework-bundle ###

###> symfony/phpunit-bridge ###
.phpunit
.phpunit.result.cache
/phpunit.xml
###< symfony/phpunit-bridge ###

###> symfony/webpack-encore-bundle ###
/node_modules/
/public/build/
npm-debug.log
yarn-error.log
###< symfony/webpack-encore-bundle ###


================================================
FILE: .symfony/config.vcl
================================================
acl profile {
   # Authorize the local IP address (replace with the IP found above)
   "a.b.c.d";
   # Authorize Blackfire servers
   "46.51.168.2";
   "54.75.240.245";
}

sub vcl_recv {
    set req.backend_hint = application.backend();
    set req.http.Surrogate-Capability = "abc=ESI/1.0";

    if (req.method == "PURGE") {
        if (req.http.x-purge-token != "PURGE_NOW") {
            return(synth(405));
        }
        return (purge);
    }

    # Don't profile ESI requests
    if (req.esi_level > 0) {
        unset req.http.X-Blackfire-Query;
    }

    # Bypass Varnish when the profile request comes from a known IP
    if (req.http.X-Blackfire-Query && client.ip ~ profile) {
        return (pass);
    }
}

sub vcl_backend_response {
    if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
        unset beresp.http.Surrogate-Control;
        set beresp.do_esi = true;
    }
}


================================================
FILE: .symfony/routes.yaml
================================================
"https://spa.{all}/": { type: upstream, upstream: "spa:http" }
"http://spa.{all}/": { type: redirect, to: "https://spa.{all}/" }

"https://{all}/": { type: upstream, upstream: "varnish:http", cache: { enabled: false } }
"http://{all}/": { type: redirect, to: "https://{all}/" }


================================================
FILE: .symfony/services.yaml
================================================
db:
    type: postgresql:11
    disk: 1024
    size: S

rediscache:
    type: redis:5.0

queue:
    type: rabbitmq:3.7
    disk: 1024
    size: S

varnish:
    type: varnish:6.0
    relationships:
        application: 'app:http'
    configuration:
        vcl: !include
            type: string
            path: config.vcl

files:
    type: network-storage:1.0
    disk: 256


================================================
FILE: .symfony.cloud.yaml
================================================
name: app

type: php:7.3

runtime:
    extensions:
        - blackfire
        - xsl
        - amqp
        - redis
        - pdo_pgsql
        - apcu
        - mbstring
        - sodium
        - ctype
        - iconv
        

build:
    flavor: none

relationships:
    database: "db:postgresql"
    redis: "rediscache:redis"
    rabbitmq: "queue:rabbitmq"

web:
    locations:
        "/":
            root: "public"
            expires: 1h
            passthru: "/index.php"

disk: 512

mounts:
    "/var": { source: local, source_path: var }
    "/public/uploads": { source: service, service: files, source_path: uploads }

hooks:
    build: |
        set -x -e

        curl -s https://get.symfony.com/cloud/configurator | (>&2 bash)
        (>&2 symfony-build)

    deploy: |
        set -x -e

        (>&2 symfony-deploy)

crons:
    comment_cleanup:
        # Cleanup every night at 11.50 pm (UTC).
        spec: '50 23 * * *'
        cmd: |
            if [ "$SYMFONY_BRANCH" = "master" ]; then
                croncape symfony console app:comment:cleanup
            fi

workers:
    messages:
        commands:
            start: |
                set -x -e

                (>&2 symfony-deploy)
                php bin/console messenger:consume async -vv --time-limit 3600 --memory-limit=128M


================================================
FILE: Makefile
================================================
SHELL := /bin/bash

tests:
	symfony console doctrine:fixtures:load -n
	symfony run bin/phpunit
.PHONY: tests


================================================
FILE: assets/css/_variables.scss
================================================

// Colors

$white: #fff;
$gray-100: #f5f5f5;
$gray-200: #e9ecef;
$gray-300: #ddd;
$gray-400: #ced4da;
$gray-500: #adb5bd;
$gray-600: #868e96;
$gray-700: #495057;
$gray-800: #343a40;
$gray-900: #212529;
$black: #000;

$blue: #175fc9;
$pink: #ff737c;
$red: #d9534f;
$yellow: #fcee60;
$orange: #ffae73;
$green: #02B875;
$teal: #55e7cc;
$cyan: #8ce6ff;

$primary: $blue;
$secondary: #666;
$success: $green;
$info: $cyan;
$warning: $yellow;
$danger: $red;
$light: $gray-100;
$dark: $gray-800;

$theme-colors: (
    "blue": $blue,
    "soft-blue": rgba(23, 95, 201, 0.15),
    "pink": $pink,
    "yellow": $yellow,
    "orange": $orange,
    "soft-orange": rgba(255, 174, 115, 0.15),
    "teal": $teal,
    "cyan": $cyan,
);

$yiq-contrasted-threshold: 190;

$text-muted: #999;

// Shadow

$box-shadow: 0 15px 30px 0 rgba(0, 0, 0, 0.1);

// Grid

$spacer: 1rem;
$spacers: (
    0: 0,
    1: ($spacer * .25),
    2: ($spacer * .5),
    3: $spacer,
    4: ($spacer * 1.5),
    5: ($spacer * 3),
    6: ($spacer * 4.5),
    7: ($spacer * 6)
);

// Body

$body-bg: $white;
$body-color: $black;

// Fonts

$font-family-base: 'Open Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
$font-family-title: Georgia, Times, 'New Roman', serif;

$font-size-base: 1rem;

$font-weight-normal: 400;
$font-weight-medium: 500;
$font-weight-bold: 600;

// Headings

$headings-font-family: $font-family-title;
$headings-font-weight: $font-weight-normal;
$headings-color: $gray-900;

// Navbar

$navbar-light-brand-color: $black;
$navbar-light-brand-hover-color: $primary;
$navbar-light-hover-color: $primary;

// Dropdown

$dropdown-link-hover-bg: $gray-200;

// Tables

$table-border-color: rgba(0, 0, 0, 0.1);

// Forms and buttons

$input-border-color: rgba(0, 0, 0, .1);
$input-group-addon-bg: $gray-200;
$btn-font-weight: $font-weight-bold;
$custom-control-indicator-size: 1.3rem;

// Tooltips

$tooltip-font-size: 11px;

// Badges

$badge-font-weight: normal;
$badge-padding-y: 0.6em;
$badge-padding-x: 1.2em;

// Alerts

$alert-border-width: 0;

// Cards

$card-border-width: 0;


================================================
FILE: assets/css/app.scss
================================================
@import './variables';
@import '~bootstrap/scss/bootstrap';

body {
    display: flex;
    flex-direction: column;
    height: 100vh;
}
main {
    flex: 1;
}

// Darken buttons
@each $color, $value in $theme-colors {
    .btn-#{$color} {
        @include button-variant($value, $value, darken($value, 10%));
    }
}

.navbar-brand {
    font-family: $font-family-title;
    font-weight: $font-weight-normal;
    font-size: 1.8rem;
}

.navbar-brand, .nav-link {
    transition: all .15s;
}

.nav-conference {
    display: inline-block;
    padding: 5px 10px;
    color: #666;
    text-transform: uppercase;
    font-weight: bold;
    font-size: 0.8rem;
}

.lift {
    transition: box-shadow .25s ease,transform .25s ease;

    &:focus, &:hover {
        box-shadow: 0 1rem 2.5rem rgba(22,28,45,.1),0 .5rem 1rem -.75rem rgba(22,28,45,.1)!important;
        transform: translate3d(0, -4px, 0);
    }
}

.comment-img {
    width: 250px;
    height: 150px;

    img {
        max-width: 250px;
        max-height: 150px;
    }
}

.comment-text {
    font-size: 12px;
    line-height: 15px;
}

footer {
    background: #18171b;
}


================================================
FILE: assets/js/app.js
================================================
/*
 * 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 require will output into a single css file (app.css in this case)
import '../css/app.scss';
import 'bootstrap';
import bsCustomFileInput from 'bs-custom-file-input';

bsCustomFileInput.init();


================================================
FILE: bin/console
================================================
#!/usr/bin/env php
<?php

use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\ErrorHandler\Debug;

if (false === in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
    echo 'Warning: The console should be invoked via the CLI version of PHP, not the '.\PHP_SAPI.' SAPI'.\PHP_EOL;
}

set_time_limit(0);

require dirname(__DIR__).'/vendor/autoload.php';

if (!class_exists(Application::class)) {
    throw new RuntimeException('You need to add "symfony/framework-bundle" as a Composer dependency.');
}

$input = new ArgvInput();
if (null !== $env = $input->getParameterOption(['--env', '-e'], null, true)) {
    putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env);
}

if ($input->hasParameterOption('--no-debug', true)) {
    putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0');
}

require dirname(__DIR__).'/config/bootstrap.php';

if ($_SERVER['APP_DEBUG']) {
    umask(0000);

    if (class_exists(Debug::class)) {
        Debug::enable();
    }
}

$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$application = new Application($kernel);
$application->run($input);


================================================
FILE: bin/phpunit
================================================
#!/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';


================================================
FILE: blackfire-player.phar
================================================
[File too large to display: 11.3 MB]

================================================
FILE: composer.json
================================================
{
    "type": "project",
    "license": "proprietary",
    "require": {
        "php": "^7.2.5",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/api-pack": "^1.2",
        "easycorp/easyadmin-bundle": "^2.3",
        "imagine/imagine": "^1.2",
        "sensio/framework-extra-bundle": "^5.5",
        "symfony/cache": "5.0.*",
        "symfony/console": "5.0.*",
        "symfony/dotenv": "5.0.*",
        "symfony/flex": "^1.3.1",
        "symfony/framework-bundle": "5.0.*",
        "symfony/http-client": "5.0.*",
        "symfony/mailer": "5.0.*",
        "symfony/messenger": "5.0.*",
        "symfony/monolog-bundle": "^3.5",
        "symfony/notifier": "5.0.*",
        "symfony/orm-pack": "^1.0",
        "symfony/process": "5.0.*",
        "symfony/security-bundle": "5.0.*",
        "symfony/slack-notifier": "5.0.*",
        "symfony/string": "5.0.*",
        "symfony/test-pack": "^1.0",
        "symfony/translation": "5.0.*",
        "symfony/twig-pack": "^1.0",
        "symfony/webpack-encore-bundle": "^1.7",
        "symfony/workflow": "5.0.*",
        "symfony/yaml": "5.0.*",
        "twig/cssinliner-extra": "^3.0",
        "twig/inky-extra": "^3.0",
        "twig/intl-extra": "^3.0",
        "twig/string-extra": "^3.0"
    },
    "require-dev": {
        "dama/doctrine-test-bundle": "^6.3",
        "doctrine/doctrine-fixtures-bundle": "^3.3",
        "symfony/browser-kit": "5.0.*",
        "symfony/debug-pack": "^1.0",
        "symfony/maker-bundle": "^1.14",
        "symfony/profiler-pack": "^1.0"
    },
    "config": {
        "preferred-install": {
            "*": "dist"
        },
        "sort-packages": true
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "App\\Tests\\": "tests/"
        }
    },
    "replace": {
        "paragonie/random_compat": "2.*",
        "symfony/polyfill-ctype": "*",
        "symfony/polyfill-iconv": "*",
        "symfony/polyfill-php72": "*",
        "symfony/polyfill-php71": "*",
        "symfony/polyfill-php70": "*",
        "symfony/polyfill-php56": "*"
    },
    "scripts": {
        "auto-scripts": {
            "cache:clear": "symfony-cmd",
            "assets:install %PUBLIC_DIR%": "symfony-cmd"
        },
        "post-install-cmd": [
            "@auto-scripts"
        ],
        "post-update-cmd": [
            "@auto-scripts"
        ]
    },
    "conflict": {
        "symfony/symfony": "*"
    },
    "extra": {
        "symfony": {
            "allow-contrib": true,
            "require": "5.0.*"
        }
    }
}


================================================
FILE: config/bootstrap.php
================================================
<?php

use Symfony\Component\Dotenv\Dotenv;

require dirname(__DIR__).'/vendor/autoload.php';

// Load cached env vars if the .env.local.php file exists
// Run "composer dump-env prod" to create it (requires symfony/flex >=1.2)
if (is_array($env = @include dirname(__DIR__).'/.env.local.php') && ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? $env['APP_ENV']) === $env['APP_ENV']) {
    foreach ($env as $k => $v) {
        $_ENV[$k] = $_ENV[$k] ?? (isset($_SERVER[$k]) && 0 !== strpos($k, 'HTTP_') ? $_SERVER[$k] : $v);
    }
} elseif (!class_exists(Dotenv::class)) {
    throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.');
} else {
    // load all the .env files
    (new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env');
}

$_SERVER += $_ENV;
$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev';
$_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV'];
$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0';


================================================
FILE: config/bundles.php
================================================
<?php

return [
    Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
    Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
    Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
    Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
    Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true],
    Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
    Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
    Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
    Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
    Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
    EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle::class => ['all' => true],
    Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
    Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
    DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true],
    Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
    Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
    ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
];


================================================
FILE: config/packages/api_platform.yaml
================================================
api_platform:
    mapping:
        paths: ['%kernel.project_dir%/src/Entity']
    patch_formats:
        json: ['application/merge-patch+json']
    swagger:
        versions: [3]


================================================
FILE: config/packages/assets.yaml
================================================
framework:
    assets:
        json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'


================================================
FILE: config/packages/cache.yaml
================================================
framework:
    cache:
        # Unique name of your app: used to compute stable namespaces for cache keys.
        #prefix_seed: your_vendor_name/app_name

        # The "app" cache stores to the filesystem by default.
        # The data in this cache should persist between deploys.
        # Other options include:

        # Redis
        #app: cache.adapter.redis
        #default_redis_provider: redis://localhost

        # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
        #app: cache.adapter.apcu

        # Namespaced pools use the above "app" backend by default
        #pools:
            #my.dedicated.cache: null


================================================
FILE: config/packages/dev/debug.yaml
================================================
debug:
    # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
    # See the "server:dump" command to start a new server.
    dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"


================================================
FILE: config/packages/dev/easy_log_handler.yaml
================================================
services:
    EasyCorp\EasyLog\EasyLogHandler:
        public: false
        arguments: ['%kernel.logs_dir%/%kernel.environment%.log']

#// FIXME: How to add this configuration automatically without messing up with the monolog configuration?
#monolog:
#    handlers:
#        buffered:
#            type:     buffer
#            handler:  easylog
#            channels: ['!event']
#            level:    debug
#        easylog:
#            type: service
#            id:   EasyCorp\EasyLog\EasyLogHandler


================================================
FILE: config/packages/dev/monolog.yaml
================================================
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"]


================================================
FILE: config/packages/dev/web_profiler.yaml
================================================
web_profiler:
    toolbar: true
    intercept_redirects: false

framework:
    profiler: { only_exceptions: false }


================================================
FILE: config/packages/doctrine.yaml
================================================
doctrine:
    dbal:
        url: '%env(resolve:DATABASE_URL)%'

        # IMPORTANT: You MUST configure your server version,
        # either here or in the DATABASE_URL env var (see .env file)
        #server_version: '5.7'
    orm:
        auto_generate_proxy_classes: true
        naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
        auto_mapping: true
        mappings:
            App:
                is_bundle: false
                type: annotation
                dir: '%kernel.project_dir%/src/Entity'
                prefix: 'App\Entity'
                alias: App


================================================
FILE: config/packages/doctrine_migrations.yaml
================================================
doctrine_migrations:
    dir_name: '%kernel.project_dir%/src/Migrations'
    # namespace is arbitrary but should be different from App\Migrations
    # as migrations classes should NOT be autoloaded
    namespace: DoctrineMigrations


================================================
FILE: config/packages/easy_admin.yaml
================================================
easy_admin:
    site_name: Conference Guestbook

    design:
        menu:
            - { route: 'homepage', label: 'Back to the website', icon: 'home' }
            - { entity: 'Conference', label: 'Conferences', icon: 'map-marker' }
            - { entity: 'Comment', label: 'Comments', icon: 'comments' }

    entities:
        Conference:
            class: App\Entity\Conference

        Comment:
            class: App\Entity\Comment
            list:
                fields:
                    - author
                    - { property: 'email', type: 'email' }
                    - { property: 'photoFilename', type: 'image', 'base_path': "/uploads/photos", label: 'Photo' }
                    - state
                    - { property: 'createdAt', type: 'datetime' }
                sort: ['createdAt', 'ASC']
                filters: ['conference']
            edit:
                fields:
                    - { property: 'conference' }
                    - { property: 'createdAt', type: datetime, type_options: { attr: { readonly: true } } }
                    - 'author'
                    - { property: 'state' }
                    - { property: 'email', type: 'email' }
                    - text


================================================
FILE: config/packages/framework.yaml
================================================
framework:
    secret: '%env(APP_SECRET)%'
    #csrf_protection: true
    #http_method_override: true

    # 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.
    session:
        handler_id: '%env(REDIS_URL)%'
        cookie_secure: auto
        cookie_samesite: lax

    esi: true
    #fragments: true
    php_errors:
        log: true

    ide: vscode


================================================
FILE: config/packages/mailer.yaml
================================================
framework:
    mailer:
        dsn: '%env(MAILER_DSN)%'
        envelope:
            sender: "%env(string:default:default_admin_email:ADMIN_EMAIL)%"


================================================
FILE: config/packages/messenger.yaml
================================================
framework:
    messenger:
        # Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
        # failure_transport: failed

        transports:
            # https://symfony.com/doc/current/messenger.html#transport-configuration
            async:
                dsn: '%env(RABBITMQ_DSN)%'
                retry_strategy:
                    max_retries: 3
                    multiplier: 2

            failed: 'doctrine://default?queue_name=failed'
            # sync: 'sync://'

        failure_transport: failed

        routing:
            # Route your messages to the transports
            App\Message\CommentMessage: async
            Symfony\Component\Mailer\Messenger\SendEmailMessage: async
            Symfony\Component\Notifier\Message\ChatMessage: async
            Symfony\Component\Notifier\Message\SmsMessage: async


================================================
FILE: config/packages/nelmio_cors.yaml
================================================
nelmio_cors:
    defaults:
        origin_regex: true
        allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
        allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
        allow_headers: ['Content-Type', 'Authorization']
        expose_headers: ['Link']
        max_age: 3600
    paths:
        '^/': null


================================================
FILE: config/packages/notifier.yaml
================================================
framework:
    notifier:
        chatter_transports:
            slack: '%env(SLACK_DSN)%'
        #    telegram: '%env(TELEGRAM_DSN)%'
        #texter_transports:
        #    twilio: '%env(TWILIO_DSN)%'
        #    nexmo: '%env(NEXMO_DSN)%'
        channel_policy:
            # use chat/slack, chat/telegram, sms/twilio or sms/nexmo
            urgent: ['email']
            high: ['email']
            medium: ['email']
            low: ['email']
        admin_recipients:
            - { email: "%env(string:default:default_admin_email:ADMIN_EMAIL)%" }


================================================
FILE: config/packages/prod/doctrine.yaml
================================================
doctrine:
    orm:
        auto_generate_proxy_classes: false
        metadata_cache_driver:
            type: pool
            pool: doctrine.system_cache_pool
        query_cache_driver:
            type: pool
            pool: doctrine.system_cache_pool
        result_cache_driver:
            type: pool
            pool: doctrine.result_cache_pool

framework:
    cache:
        pools:
            doctrine.result_cache_pool:
                adapter: cache.app
            doctrine.system_cache_pool:
                adapter: cache.system


================================================
FILE: config/packages/prod/monolog.yaml
================================================
monolog:
    handlers:
        main:
            type: fingers_crossed
            action_level: error
            handler: nested
            excluded_http_codes: [404, 405]
        nested:
            type: stream
            path: "%kernel.logs_dir%/%kernel.environment%.log"
            level: debug
        console:
            type: console
            process_psr_3_messages: false
            channels: ["!event", "!doctrine"]
        deprecation:
            type: stream
            path: "%kernel.logs_dir%/%kernel.environment%.deprecations.log"
        deprecation_filter:
            type: filter
            handler: deprecation
            max_level: info
            channels: ["php"]


================================================
FILE: config/packages/prod/routing.yaml
================================================
framework:
    router:
        strict_requirements: null


================================================
FILE: config/packages/prod/webpack_encore.yaml
================================================
#webpack_encore:
    # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
    # Available in version 1.2
    #cache: true


================================================
FILE: config/packages/routing.yaml
================================================
framework:
    router:
        utf8: true


================================================
FILE: config/packages/security.yaml
================================================
security:
    encoders:
        App\Entity\Admin:
            algorithm: auto

    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        # used to reload user from session & other features (e.g. switch_user)
        app_user_provider:
            entity:
                class: App\Entity\Admin
                property: username
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            anonymous: lazy
            guard:
                authenticators:
                    - App\Security\AppAuthenticator
            logout:
                path: app_logout
                # where to redirect after logout
                # target: app_any_route

            # activate different ways to authenticate
            # https://symfony.com/doc/current/security.html#firewalls-authentication

            # https://symfony.com/doc/current/security/impersonating_user.html
            # switch_user: true

    # Easy way to control access for large sections of your site
    # Note: Only the *first* access control that matches will be used
    access_control:
        - { path: ^/admin, roles: ROLE_ADMIN }
        # - { path: ^/profile, roles: ROLE_USER }


================================================
FILE: config/packages/sensio_framework_extra.yaml
================================================
sensio_framework_extra:
    router:
        annotations: false


================================================
FILE: config/packages/test/dama_doctrine_test_bundle.yaml
================================================
dama_doctrine_test:
    enable_static_connection: true
    enable_static_meta_data_cache: true
    enable_static_query_cache: true


================================================
FILE: config/packages/test/framework.yaml
================================================
framework:
    test: true
    session:
        storage_id: session.storage.mock_file


================================================
FILE: config/packages/test/monolog.yaml
================================================
monolog:
    handlers:
        main:
            type: stream
            path: "%kernel.logs_dir%/%kernel.environment%.log"
            level: debug
            channels: ["!event"]


================================================
FILE: config/packages/test/twig.yaml
================================================
twig:
    strict_variables: true


================================================
FILE: config/packages/test/validator.yaml
================================================
framework:
    validation:
        not_compromised_password: false


================================================
FILE: config/packages/test/web_profiler.yaml
================================================
web_profiler:
    toolbar: false
    intercept_redirects: false

framework:
    profiler: { collect: false }


================================================
FILE: config/packages/test/webpack_encore.yaml
================================================
#webpack_encore:
#    strict_mode: false


================================================
FILE: config/packages/translation.yaml
================================================
framework:
    default_locale: en
    translator:
        default_path: '%kernel.project_dir%/translations'
        fallbacks:
            - en


================================================
FILE: config/packages/twig.yaml
================================================
twig:
    form_themes: ['bootstrap_4_layout.html.twig']


================================================
FILE: config/packages/validator.yaml
================================================
framework:
    validation:
        email_validation_mode: html5

        # Enables validator auto-mapping support.
        # For instance, basic validation constraints will be inferred from Doctrine's metadata.
        #auto_mapping:
        #    App\Entity\: []


================================================
FILE: config/packages/webpack_encore.yaml
================================================
webpack_encore:
    # The path where Encore is building the assets - i.e. Encore.setOutputPath()
    output_path: '%kernel.project_dir%/public/build'
    # If multiple builds are defined (as shown below), you can disable the default build:
    # output_path: false

    # if using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials')
    # crossorigin: 'anonymous'

    # preload all rendered script and link tags automatically via the http2 Link header
    # preload: true

    # Throw an exception if the entrypoints.json file is missing or an entry is missing from the data
    # strict_mode: false

    # if you have multiple builds:
    # builds:
        # pass "frontend" as the 3rg arg to the Twig functions
        # {{ encore_entry_script_tags('entry1', null, 'frontend') }}

        # frontend: '%kernel.project_dir%/public/frontend/build'

    # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
    # Put in config/packages/prod/webpack_encore.yaml
    # cache: true


================================================
FILE: config/packages/workflow.yaml
================================================
framework:
    workflows:
        comment:
            type: state_machine
            audit_trail:
                enabled: "%kernel.debug%"
            marking_store:
                type: 'method'
                property: 'state'
            supports:
                - App\Entity\Comment
            initial_marking: submitted
            places:
                - submitted
                - ham
                - potential_spam
                - spam
                - rejected
                - ready
                - published
            transitions:
                accept:
                    from: submitted
                    to:   ham
                might_be_spam:
                    from: submitted
                    to:   potential_spam
                reject_spam:
                    from: submitted
                    to:   spam
                publish:
                    from: potential_spam
                    to:   ready
                reject:
                    from: potential_spam
                    to:   rejected
                publish_ham:
                    from: ham
                    to:   ready
                reject_ham:
                    from: ham
                    to:   rejected
                optimize:
                    from: ready
                    to:   published


================================================
FILE: config/routes/annotations.yaml
================================================
controllers:
    resource: ../../src/Controller/
    type: annotation


================================================
FILE: config/routes/api_platform.yaml
================================================
api_platform:
    resource: .
    type: api_platform
    prefix: /api


================================================
FILE: config/routes/dev/framework.yaml
================================================
_errors:
    resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
    prefix: /_error


================================================
FILE: config/routes/dev/web_profiler.yaml
================================================
web_profiler_wdt:
    resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
    prefix: /_wdt

web_profiler_profiler:
    resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
    prefix: /_profiler


================================================
FILE: config/routes/easy_admin.yaml
================================================
easy_admin_bundle:
    resource: '@EasyAdminBundle/Controller/EasyAdminController.php'
    prefix: /admin
    type: annotation


================================================
FILE: config/routes.yaml
================================================
#index:
#    path: /
#    controller: App\Controller\DefaultController::index


================================================
FILE: config/secrets/dev/dev.AKISMET_KEY.ca01fb.php
================================================
<?php // dev.AKISMET_KEY.ca01fb on Tue, 07 Jan 2020 09:15:06 +0100

return "\x9B\xAA\xDB\xF7\x9E\x1C\x8D\x9F\x7B\xDBm8d\xA3q\x0F\x81-\x9C\x25\x0C5y\x3F\x8D\xC8\xF9\xD2\x8BjU5\xF61\xEB\xC8\x40i\x28\xA2\x11\x14\x07U\xBF8\x0A\xB9\x19\x1F\xB6\x0876\xFF\x7B\xE4\xA0Eko\xA0\xB8X\x7D";


================================================
FILE: config/secrets/dev/dev.SLACK_DSN.b2b579.php
================================================
<?php // dev.SLACK_DSN.b2b579 on Tue, 07 Jan 2020 09:22:47 +0100

return "\x97\x10E\x88\x0Fc\xCA\x850\x9F\xB6\x18Q\x1E\x82\xAB\xDE\xA9\xA9\x17t\xB6\xCB\xB6\xBA.m\xC0\x82\x3C\x24i\x07\xCA\xE2\x95\x0C\xB6\x2B\xD2qZR\x7C\x87\xB5\x9C\xD5\x3B\x91\x8B\x3C\xDF\x8C\x8B\x10\x9F\x8E\x06\x5D\x19\xF3\x93M\xE8\x80\xDBx\x5E\xF5\x24\x18\xD3x\x40\xBCt\xFD\xDA\x60\xF2I\x99\x8A\xEEe\x83\x23D\xA7y\x0F";


================================================
FILE: config/secrets/dev/dev.decrypt.private.php
================================================
<?php // dev.decrypt.private on Tue, 07 Jan 2020 09:15:06 +0100

return "z7\x80\x9E\x0E\x22\xD2\xBE-\xD9\x3Co\x08B\x0D\xE5\xF2\xD5\xB9D\xEB\x8D\x04p\x97\xE2\x11\xF1\x0F\x24\xC8\x40\x15\x2A\x18\xCC\x11\x04\x60\x16\x8D\x26\xC1\xA2\x3A\x9CB\xD9\x9C\xD9\xDD\xC8\xD2TIp\x9A\xC5\x80\xF5\x80\x8BB~";


================================================
FILE: config/secrets/dev/dev.encrypt.public.php
================================================
<?php // dev.encrypt.public on Tue, 07 Jan 2020 09:15:06 +0100

return "\x15\x2A\x18\xCC\x11\x04\x60\x16\x8D\x26\xC1\xA2\x3A\x9CB\xD9\x9C\xD9\xDD\xC8\xD2TIp\x9A\xC5\x80\xF5\x80\x8BB~";


================================================
FILE: config/secrets/dev/dev.list.php
================================================
<?php

return array (
  'AKISMET_KEY' => NULL,
  'SLACK_DSN' => NULL,
);


================================================
FILE: config/secrets/prod/prod.AKISMET_KEY.ca01fb.php
================================================
<?php // prod.AKISMET_KEY.ca01fb on Tue, 07 Jan 2020 09:15:07 +0100

return "\x89\x8E\x7C\x94\xE7\x90\xDE77\xEB\xFB\xE0\x9C\xDC\x19\xF7\xBA\x7D\x81\xF1\x24\xD8\x07\xDF\x03\xB0\x7Cj\xCEd\xCF\x1C\x13\x01\xBB\x0A\x8F\x5C-\x80C\x16\xC0\xAA\xBBG\xB7\xE1\x91\xB9F\xCDt\xB2";


================================================
FILE: config/secrets/prod/prod.SLACK_DSN.b2b579.php
================================================
<?php // prod.SLACK_DSN.b2b579 on Tue, 07 Jan 2020 09:22:47 +0100

return "\x0D_U\xDBV\xC4\xF7\x0Az\xB2\xBC\xDB\x5BXMd\x17\xE4\x17Z\x8E\xE4\x06V\xDFUY\x3A\xE2v\xE7R\xCA\xD3\x23\xBFzDj\x9F\x0A\x10\xF1\x0F\x90G\x0CBf\x04\x5D\x8Er\x20\x1C\xF1\xA9WJ\xFD\x9C\x98j\x60\xD9_\xEB\x8A_\xA6\xAEaf\xC6\xAA\x133k\x3B\xFB\xA6\x9E\x25\xD6\x84\xB3\x85A\xC9\x11O\xF5";


================================================
FILE: config/secrets/prod/prod.encrypt.public.php
================================================
<?php // prod.encrypt.public on Tue, 07 Jan 2020 09:15:06 +0100

return "\xA7\xFDO\xCB\xA8Y\x85\xCE\xA8E\xCBT\xAEK\xEF\xAD\x91\x5E\x06\xF1\xC22i\x9E\x04\x14\x99\x02O\x20\x07E";


================================================
FILE: config/secrets/prod/prod.list.php
================================================
<?php

return array (
  'AKISMET_KEY' => NULL,
  'SLACK_DSN' => NULL,
);


================================================
FILE: config/secrets/test/test.AKISMET_KEY.ca01fb.php
================================================
<?php // test.AKISMET_KEY.ca01fb on Tue, 07 Jan 2020 09:15:56 +0100

return "\xE1\x85\x91\x878\xAD\xBAu\x84\x7F\xC8\xAA2\x29\xE7\xAE\xBB\x05a-\x29\xF9uZ\x80\x904\x96\xE7\x0C\x40\x19\xB1\xE6b\xAB\xD6Z\xC8\xB0\x7C\xFDGf\x9DH\x8A\xC3\x08\x3D\x11\xE8vO\x7CZGz\xF5\x0F\xF9\xEEw\xDAd";


================================================
FILE: config/secrets/test/test.decrypt.private.php
================================================
<?php // test.decrypt.private on Tue, 07 Jan 2020 09:15:56 +0100

return "\x8DV1\x26\x29o\xD6\xF1\xA8\xF0\x3Dz\xE8\xB1\x82\xE8\x10\xF5\x09\xE8\x16\xB5c\xCBn\xD8R~\x3B\xC3\xCA\x25\x80\xD2\xF5\x1A7j\xE4w\xDF\x7Bw\xA4\x60\x5CUO\x5Bv\xE9j\x1B\xAD\x0D\x13q\x19\xC7\x9E\xAB\xFA\x9F\x7B";


================================================
FILE: config/secrets/test/test.encrypt.public.php
================================================
<?php // test.encrypt.public on Tue, 07 Jan 2020 09:15:56 +0100

return "\x80\xD2\xF5\x1A7j\xE4w\xDF\x7Bw\xA4\x60\x5CUO\x5Bv\xE9j\x1B\xAD\x0D\x13q\x19\xC7\x9E\xAB\xFA\x9F\x7B";


================================================
FILE: config/secrets/test/test.list.php
================================================
<?php

return array (
  'AKISMET_KEY' => NULL,
);


================================================
FILE: config/services.yaml
================================================
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# 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
parameters:
    default_admin_email: admin@example.com
    default_domain: '127.0.0.1'
    default_scheme: 'http'
    app.supported_locales: 'en|fr'

    router.request_context.host: '%env(default:default_domain:SYMFONY_DEFAULT_ROUTE_HOST)%'
    router.request_context.scheme: '%env(default:default_scheme:SYMFONY_DEFAULT_ROUTE_SCHEME)%'

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
        bind:
            $photoDir: "%kernel.project_dir%/public/uploads/photos"
            $akismetKey: "%env(AKISMET_KEY)%"
            $adminEmail: "%env(string:default:default_admin_email:ADMIN_EMAIL)%"

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/*'
        exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'

    # controllers are imported separately to make sure services can be injected
    # as action arguments even if you don't extend any base controller class
    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']

    # add more service definitions when explicit configuration is needed
    # please note that last definitions always *replace* previous ones
    App\EntityListener\ConferenceEntityListener:
        tags:
            - { name: 'doctrine.orm.entity_listener', event: 'prePersist', entity: 'App\Entity\Conference'}
            - { name: 'doctrine.orm.entity_listener', event: 'preUpdate', entity: 'App\Entity\Conference'}


================================================
FILE: docker-compose.yaml
================================================
version: '3'

services:
    database:
        image: postgres:11-alpine
        environment:
            POSTGRES_USER: main
            POSTGRES_PASSWORD: main
            POSTGRES_DB: main
        ports: [5432]

    redis:
        image: redis:5-alpine
        ports: [6379]

    rabbitmq:
        image: rabbitmq:3.7-management
        ports: [5672, 15672]

    mailcatcher:
        image: schickling/mailcatcher
        ports: [1025, 1080]

    blackfire:
        image: blackfire/blackfire
        env_file: .env.local
        ports: [8707]


================================================
FILE: package.json
================================================
{
    "devDependencies": {
        "@symfony/webpack-encore": "^0.28.2",
        "bootstrap": "^4.4.1",
        "bs-custom-file-input": "^1.3.2",
        "core-js": "^3.0.0",
        "jquery": "^3.4.1",
        "node-sass": "^4.13.0",
        "popper.js": "^1.16.0",
        "regenerator-runtime": "^0.13.2",
        "sass-loader": "^7.0.1",
        "webpack-notifier": "^1.6.0"
    },
    "license": "UNLICENSED",
    "private": true,
    "scripts": {
        "dev-server": "encore dev-server",
        "dev": "encore dev",
        "watch": "encore dev --watch",
        "build": "encore production --progress"
    }
}


================================================
FILE: php.ini
================================================
allow_url_include=off
assert.active=off
display_errors=off
display_startup_errors=off
max_execution_time=30
session.use_strict_mode=On
realpath_cache_ttl=3600
zend.detect_unicode=Off

[blackfire]
# use php_blackfire.dll on Windows
extension=blackfire.so


================================================
FILE: phpunit.xml.dist
================================================
<?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="config/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="7.5" />
    </php>

    <testsuites>
        <testsuite name="Project Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>

    <filter>
        <whitelist>
            <directory>src</directory>
        </whitelist>
    </filter>

    <extensions>
        <extension class="DAMA\DoctrineTestBundle\PHPUnit\PHPUnitExtension" />
    </extensions>

    <listeners>
        <listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
    </listeners>
</phpunit>


================================================
FILE: public/index.php
================================================
<?php

use App\Kernel;
use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;
use Symfony\Component\ErrorHandler\Debug;
use Symfony\Component\HttpFoundation\Request;

require dirname(__DIR__).'/config/bootstrap.php';

if ($_SERVER['APP_DEBUG']) {
    umask(0000);

    Debug::enable();
}

if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? $_ENV['TRUSTED_PROXIES'] ?? false) {
    Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST);
}

if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? $_ENV['TRUSTED_HOSTS'] ?? false) {
    Request::setTrustedHosts([$trustedHosts]);
}

$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);

if ('dev' === $kernel->getEnvironment()) {
    $kernel = new HttpCache($kernel);
}

$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);


================================================
FILE: spa/.gitignore
================================================
/node_modules
/public
/yarn-error.log
# used later by Cordova
/app


================================================
FILE: spa/.symfony.cloud.yaml
================================================
name: spa

type: php:7.3
size: S
disk: 256

build:
    flavor: none

dependencies:
    nodejs:
        yarn: "*"

web:
    commands:
        start: sleep
    locations:
        "/":
            root: "public"
            index:
                - "index.html"
            scripts: false
            expires: 10m

hooks:
    build: |
        set -x -e

        curl -s https://get.symfony.com/cloud/configurator | (>&2 bash)
        yarn-install
        npm rebuild node-sass
        yarn encore prod


================================================
FILE: spa/assets/css/_variables.scss
================================================

// Colors

$white: #fff;
$gray-100: #f5f5f5;
$gray-200: #e9ecef;
$gray-300: #ddd;
$gray-400: #ced4da;
$gray-500: #adb5bd;
$gray-600: #868e96;
$gray-700: #495057;
$gray-800: #343a40;
$gray-900: #212529;
$black: #000;

$blue: #175fc9;
$pink: #ff737c;
$red: #d9534f;
$yellow: #fcee60;
$orange: #ffae73;
$green: #02B875;
$teal: #55e7cc;
$cyan: #8ce6ff;

$primary: $blue;
$secondary: #666;
$success: $green;
$info: $cyan;
$warning: $yellow;
$danger: $red;
$light: $gray-100;
$dark: $gray-800;

$theme-colors: (
    "blue": $blue,
    "soft-blue": rgba(23, 95, 201, 0.15),
    "pink": $pink,
    "yellow": $yellow,
    "orange": $orange,
    "soft-orange": rgba(255, 174, 115, 0.15),
    "teal": $teal,
    "cyan": $cyan,
);

$yiq-contrasted-threshold: 190;

$text-muted: #999;

// Shadow

$box-shadow: 0 15px 30px 0 rgba(0, 0, 0, 0.1);

// Grid

$spacer: 1rem;
$spacers: (
    0: 0,
    1: ($spacer * .25),
    2: ($spacer * .5),
    3: $spacer,
    4: ($spacer * 1.5),
    5: ($spacer * 3),
    6: ($spacer * 4.5),
    7: ($spacer * 6)
);

// Body

$body-bg: $white;
$body-color: $black;

// Fonts

$font-family-base: 'Open Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
$font-family-title: Georgia, Times, 'New Roman', serif;

$font-size-base: 1rem;

$font-weight-normal: 400;
$font-weight-medium: 500;
$font-weight-bold: 600;

// Headings

$headings-font-family: $font-family-title;
$headings-font-weight: $font-weight-normal;
$headings-color: $gray-900;

// Navbar

$navbar-light-brand-color: $black;
$navbar-light-brand-hover-color: $primary;
$navbar-light-hover-color: $primary;

// Dropdown

$dropdown-link-hover-bg: $gray-200;

// Tables

$table-border-color: rgba(0, 0, 0, 0.1);

// Forms and buttons

$input-border-color: rgba(0, 0, 0, .1);
$input-group-addon-bg: $gray-200;
$btn-font-weight: $font-weight-bold;
$custom-control-indicator-size: 1.3rem;

// Tooltips

$tooltip-font-size: 11px;

// Badges

$badge-font-weight: normal;
$badge-padding-y: 0.6em;
$badge-padding-x: 1.2em;

// Alerts

$alert-border-width: 0;

// Cards

$card-border-width: 0;


================================================
FILE: spa/assets/css/app.scss
================================================
@import './variables';
@import '~bootstrap/scss/bootstrap';

body {
    display: flex;
    flex-direction: column;
    height: 100vh;
}
main {
    flex: 1;
}

// Darken buttons
@each $color, $value in $theme-colors {
    .btn-#{$color} {
        @include button-variant($value, $value, darken($value, 10%));
    }
}

.navbar-brand {
    font-family: $font-family-title;
    font-weight: $font-weight-normal;
    font-size: 1.8rem;
}

.navbar-brand, .nav-link {
    transition: all .15s;
}

.nav-conference {
    display: inline-block;
    padding: 5px 10px;
    color: #666;
    text-transform: uppercase;
    font-weight: bold;
    font-size: 0.8rem;
}

.lift {
    transition: box-shadow .25s ease,transform .25s ease;

    &:focus, &:hover {
        box-shadow: 0 1rem 2.5rem rgba(22,28,45,.1),0 .5rem 1rem -.75rem rgba(22,28,45,.1)!important;
        transform: translate3d(0, -4px, 0);
    }
}

.comment-img {
    width: 250px;
    height: 150px;

    img {
        max-width: 250px;
        max-height: 150px;
    }
}

.comment-text {
    font-size: 12px;
    line-height: 15px;
}

footer {
    background: #18171b;
}


================================================
FILE: spa/package.json
================================================
{
  "name": "spa",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "@babel/core": "^7.7.7",
    "@babel/preset-env": "^7.7.7",
    "@symfony/webpack-encore": "^0.28.2",
    "babel-preset-preact": "^2.0.0",
    "bootstrap": "^4.4.1",
    "html-webpack-plugin": "^3.2.0",
    "node-sass": "^4.13.0",
    "preact": "^10.1.1",
    "preact-router": "^3.1.0",
    "sass-loader": "^7.0"
  }
}


================================================
FILE: spa/src/api/api.js
================================================
function fetchCollection(path) {
    return fetch(ENV_API_ENDPOINT + path).then(resp => resp.json()).then(json => json['hydra:member']);
}

export function findConferences() {
    return fetchCollection('api/conferences');
}

export function findComments(conference) {
    return fetchCollection('api/comments?conference='+conference.id);
}


================================================
FILE: spa/src/app.js
================================================
import '../assets/css/app.scss';

import {h, render} from 'preact';
import {Router, Link} from 'preact-router';
import {useState, useEffect} from 'preact/hooks';

import {findConferences} from './api/api';
import Home from './pages/home';
import Conference from './pages/conference';

function App() {
    const [conferences, setConferences] = useState(null);

    useEffect(() => {
        findConferences().then((conferences) => setConferences(conferences));
    }, []);

    if (conferences === null) {
        return <div className="text-center pt-5">Loading...</div>;
    }

    return (
        <div>
            <header className="header">
                <nav className="navbar navbar-light bg-light">
                    <div className="container">
                        <Link className="navbar-brand mr-4 pr-2" href="/">
                            &#128217; Guestbook
                        </Link>
                    </div>
                </nav>

                <nav className="bg-light border-bottom text-center">
                    {conferences.map((conference) => (
                        <Link className="nav-conference" href={'/conference/'+conference.slug}>
                            {conference.city} {conference.year}
                        </Link>
                    ))}
                </nav>
            </header>

            <Router>
                <Home path="/" conferences={conferences} />
                <Conference path="/conference/:slug" conferences={conferences} />
            </Router>
        </div>
    )
}

render(<App />, document.getElementById('app'));


================================================
FILE: spa/src/index.ejs
================================================
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="msapplication-tap-highlight" content="no" />
    <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width" />

    <title>Conference Guestbook application</title>
</head>
<body>
    <div id="app"></div>
</body>
</html>


================================================
FILE: spa/src/pages/conference.js
================================================
import {h} from 'preact';
import {findComments} from '../api/api';
import {useState, useEffect} from 'preact/hooks';

function Comment({comments}) {
    if (comments !== null && comments.length === 0) {
        return <div className="text-center pt-4">No comments yet</div>;
    }

    if (!comments) {
        return <div className="text-center pt-4">Loading...</div>;
    }

    return (
        <div className="pt-4">
            {comments.map(comment => (
                <div className="shadow border rounded-lg p-3 mb-4">
                    <div className="comment-img mr-3">
                        {!comment.photoFilename ? '' : (
                            <a href={ENV_API_ENDPOINT+'uploads/photos/'+comment.photoFilename} target="_blank">
                                <img src={ENV_API_ENDPOINT+'uploads/photos/'+comment.photoFilename} />
                            </a>
                        )}
                    </div>

                    <h5 className="font-weight-light mt-3 mb-0">{comment.author}</h5>
                    <div className="comment-text">{comment.text}</div>
                </div>
            ))}
        </div>
    );
}

export default function Conference({conferences, slug}) {
    const conference = conferences.find(conference => conference.slug === slug);
    const [comments, setComments] = useState(null);

    useEffect(() => {
        findComments(conference).then(comments => setComments(comments));
    }, [slug]);

    return (
        <div className="p-3">
            <h4>{conference.city} {conference.year}</h4>
            <Comment comments={comments} />
        </div>
    );
}


================================================
FILE: spa/src/pages/home.js
================================================
import {h} from 'preact';
import {Link} from 'preact-router';

export default function Home({conferences}) {
    if (!conferences) {
        return <div className="p-3 text-center">No conferences yet</div>;
    }

    return (
        <div className="p-3">
            {conferences.map((conference)=> (
                <div className="card border shadow-sm lift mb-3">
                    <div className="card-body">
                        <div className="card-title">
                            <h4 className="font-weight-light">
                                {conference.city} {conference.year}
                            </h4>
                        </div>

                        <Link className="btn btn-sm btn-blue stretched-link" href={'/conference/'+conference.slug}>
                            View
                        </Link>
                    </div>
                </div>
            ))}
        </div>
    );
}


================================================
FILE: spa/webpack.config.js
================================================
const webpack = require('webpack');
const Encore = require('@symfony/webpack-encore');
const HtmlWebpackPlugin = require('html-webpack-plugin');

Encore
    .setOutputPath('public/')
    .setPublicPath('/')
    .cleanupOutputBeforeBuild()
    .addEntry('app', './src/app.js')
    .enablePreactPreset()
    .enableSassLoader()
    .enableSingleRuntimeChunk()
    .addPlugin(new HtmlWebpackPlugin({ template: 'src/index.ejs', alwaysWriteToDisk: true }))
    .addPlugin(new webpack.DefinePlugin({
        'ENV_API_ENDPOINT': JSON.stringify(process.env.API_ENDPOINT),
    }))
;

module.exports = Encore.getWebpackConfig();


================================================
FILE: src/Api/FilterPublishedCommentQueryExtension.php
================================================
<?php

namespace App\Api;

use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use App\Entity\Comment;
use Doctrine\ORM\QueryBuilder;

class FilterPublishedCommentQueryExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
{
    public function applyToCollection(QueryBuilder $qb, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
    {
        if (Comment::class === $resourceClass) {
            $qb->andWhere(sprintf("%s.state = 'published'", $qb->getRootAliases()[0]));
        }
    }

    public function applyToItem(QueryBuilder $qb, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = [])
    {
        if (Comment::class === $resourceClass) {
            $qb->andWhere(sprintf("%s.state = 'published'", $qb->getRootAliases()[0]));
        }
    }
}


================================================
FILE: src/Command/CommentCleanupCommand.php
================================================
<?php

namespace App\Command;

use App\Repository\CommentRepository;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class CommentCleanupCommand extends Command
{
    private $commentRepository;

    protected static $defaultName = 'app:comment:cleanup';

    public function __construct(CommentRepository $commentRepository)
    {
        $this->commentRepository = $commentRepository;

        parent::__construct();
    }

    protected function configure()
    {
        $this
            ->setDescription('Deletes rejected and spam comments from the database')
            ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Dry run')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);

        if ($input->getOption('dry-run')) {
            $io->note('Dry mode enabled');

            $count = $this->commentRepository->countOldRejected();
        } else {
            $count = $this->commentRepository->deleteOldRejected();
        }

        $io->success(sprintf('Deleted "%d" old rejected/spam comments.', $count));

        return 0;
    }
}


================================================
FILE: src/Command/StepInfoCommand.php
================================================
<?php

namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Process;
use Symfony\Contracts\Cache\CacheInterface;

class StepInfoCommand extends Command
{
    protected static $defaultName = 'app:step:info';

    private $cache;

    public function __construct(CacheInterface $cache)
    {
        $this->cache = $cache;

        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $step = $this->cache->get('app.current_step', function ($item) {
            $process = new Process(['git', 'tag', '-l', '--points-at', 'HEAD']);
            $process->mustRun();
            $item->expiresAfter(30);

            return $process->getOutput();
        });
        $output->writeln($step);

        return 0;
    }
}


================================================
FILE: src/Controller/.gitignore
================================================


================================================
FILE: src/Controller/AdminController.php
================================================
<?php

namespace App\Controller;

use App\Entity\Comment;
use App\Message\CommentMessage;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Workflow\Registry;
use Twig\Environment;

/**
 * @Route("/admin")
 */
class AdminController extends AbstractController
{
    private $twig;
    private $entityManager;
    private $bus;

    public function __construct(Environment $twig, EntityManagerInterface $entityManager, MessageBusInterface $bus)
    {
        $this->twig = $twig;
        $this->entityManager = $entityManager;
        $this->bus = $bus;
    }

    /**
     * @Route("/comment/review/{id}", name="review_comment")
     */
    public function reviewComment(Request $request, Comment $comment, Registry $registry)
    {
        $accepted = !$request->query->get('reject');

        $machine = $registry->get($comment);
        if ($machine->can($comment, 'publish')) {
            $transition = $accepted ? 'publish' : 'reject';
        } elseif ($machine->can($comment, 'publish_ham')) {
            $transition = $accepted ? 'publish_ham' : 'reject_ham';
        } else {
            return new Response('Comment already reviewed or not in the right state.');
        }

        $machine->apply($comment, $transition);
        $this->entityManager->flush();

        if ($accepted) {
            $reviewUrl = $this->generateUrl('review_comment', ['id' => $comment->getId()], UrlGeneratorInterface::ABSOLUTE_URL);
            $this->bus->dispatch(new CommentMessage($comment->getId(), $reviewUrl));
        }

        return $this->render('admin/review.html.twig', [
            'transition' => $transition,
            'comment' => $comment,
        ]);
    }

    /**
     * @Route("/http-cache/{uri<.*>}", methods={"PURGE"})
     */
    public function purgeHttpCache(KernelInterface $kernel, Request $request, string $uri)
    {
        if ('prod' === $kernel->getEnvironment()) {
            return new Response('KO', 400);
        }

        $store = (new class($kernel) extends HttpCache {})->getStore();
        $store->purge($request->getSchemeAndHttpHost().'/'.$uri);

        return new Response('Done');
    }
}


================================================
FILE: src/Controller/ConferenceController.php
================================================
<?php

namespace App\Controller;

use App\Entity\Comment;
use App\Entity\Conference;
use App\Form\CommentFormType;
use App\Message\CommentMessage;
use App\Repository\CommentRepository;
use App\Repository\ConferenceRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Notifier\Notification\Notification;
use Symfony\Component\Notifier\NotifierInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Twig\Environment;

class ConferenceController extends AbstractController
{
    private $twig;
    private $entityManager;
    private $bus;

    public function __construct(Environment $twig, EntityManagerInterface $entityManager, MessageBusInterface $bus)
    {
        $this->twig = $twig;
        $this->entityManager = $entityManager;
        $this->bus = $bus;
    }

    /**
     * @Route("/")
     */
    public function indexNoLocale()
    {
        return $this->redirectToRoute('homepage', ['_locale' => 'en']);
    }

    /**
     * @Route("/{_locale<%app.supported_locales%>}/", name="homepage")
     */
    public function index(ConferenceRepository $conferenceRepository)
    {
        $response = new Response($this->twig->render('conference/index.html.twig', [
            'conferences' => $conferenceRepository->findAll(),
        ]));
        $response->setSharedMaxAge(3600);

        return $response;
    }

    /**
     * @Route("/{_locale<%app.supported_locales%>}/conference_header", name="conference_header")
     */
    public function conferenceHeader(ConferenceRepository $conferenceRepository)
    {
        $response = new Response($this->twig->render('conference/header.html.twig', [
            'conferences' => $conferenceRepository->findAll(),
        ]));
        $response->setSharedMaxAge(3600);

        return $response;
    }

    /**
     * @Route("/{_locale<%app.supported_locales%>}/conference/{slug}", name="conference")
     */
    public function show(Request $request, Conference $conference, CommentRepository $commentRepository, NotifierInterface $notifier, string $photoDir)
    {
        $comment = new Comment();
        $form = $this->createForm(CommentFormType::class, $comment);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $comment->setConference($conference);
            if ($photo = $form['photo']->getData()) {
                $filename = bin2hex(random_bytes(6)).'.'.$photo->guessExtension();
                try {
                    $photo->move($photoDir, $filename);
                } catch (FileException $e) {
                    // unable to upload the photo, give up
                }
                $comment->setPhotoFilename($filename);
            }

            $this->entityManager->persist($comment);
            $this->entityManager->flush();

            $context = [
                'user_ip' => $request->getClientIp(),
                'user_agent' => $request->headers->get('user-agent'),
                'referrer' => $request->headers->get('referer'),
                'permalink' => $request->getUri(),
            ];

            $reviewUrl = $this->generateUrl('review_comment', ['id' => $comment->getId()], UrlGeneratorInterface::ABSOLUTE_URL);
            $this->bus->dispatch(new CommentMessage($comment->getId(), $reviewUrl, $context));

            $notifier->send(new Notification('Thank you for the feedback; your comment will be posted after moderation.', ['browser']));

            return $this->redirectToRoute('conference', ['slug' => $conference->getSlug()]);
        }

        if ($form->isSubmitted()) {
            $notifier->send(new Notification('Can you check your submission? There are some problems with it.', ['browser']));
        }

        $offset = max(0, $request->query->getInt('offset', 0));
        $paginator = $commentRepository->getCommentPaginator($conference, $offset);

        return new Response($this->twig->render('conference/show.html.twig', [
            'conference' => $conference,
            'comments' => $paginator,
            'previous' => $offset - CommentRepository::PAGINATOR_PER_PAGE,
            'next' => min(count($paginator), $offset + CommentRepository::PAGINATOR_PER_PAGE),
            'comment_form' => $form->createView(),
        ]));
    }
}


================================================
FILE: src/Controller/SecurityController.php
================================================
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;

class SecurityController extends AbstractController
{
    /**
     * @Route("/login", name="app_login")
     */
    public function login(AuthenticationUtils $authenticationUtils): Response
    {
        // if ($this->getUser()) {
        //     return $this->redirectToRoute('target_path');
        // }

        // get the login error if there is one
        $error = $authenticationUtils->getLastAuthenticationError();
        // last username entered by the user
        $lastUsername = $authenticationUtils->getLastUsername();

        return $this->render('security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]);
    }

    /**
     * @Route("/logout", name="app_logout")
     */
    public function logout()
    {
        throw new \Exception('This method can be blank - it will be intercepted by the logout key on your firewall');
    }
}


================================================
FILE: src/DataFixtures/AppFixtures.php
================================================
<?php

namespace App\DataFixtures;

use App\Entity\Admin;
use App\Entity\Comment;
use App\Entity\Conference;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;

class AppFixtures extends Fixture
{
    private $encoderFactory;

    public function __construct(EncoderFactoryInterface $encoderFactory)
    {
        $this->encoderFactory = $encoderFactory;
    }

    public function load(ObjectManager $manager)
    {
        $amsterdam = new Conference();
        $amsterdam->setCity('Amsterdam');
        $amsterdam->setYear('2019');
        $amsterdam->setIsInternational(true);
        $manager->persist($amsterdam);

        $paris = new Conference();
        $paris->setCity('Paris');
        $paris->setYear('2020');
        $paris->setIsInternational(false);
        $manager->persist($paris);

        $comment1 = new Comment();
        $comment1->setConference($amsterdam);
        $comment1->setAuthor('Fabien');
        $comment1->setEmail('fabien@example.com');
        $comment1->setText('This was a great conference.');
        $comment1->setState('published');
        $manager->persist($comment1);

        $comment2 = new Comment();
        $comment2->setConference($amsterdam);
        $comment2->setAuthor('Lucas');
        $comment2->setEmail('lucas@example.com');
        $comment2->setText('I think this one is going to be moderated.');
        $manager->persist($comment2);

        $admin = new Admin();
        $admin->setRoles(['ROLE_ADMIN']);
        $admin->setUsername('admin');
        $admin->setPassword($this->encoderFactory->getEncoder(Admin::class)->encodePassword('admin', null));
        $manager->persist($admin);

        $manager->flush();
    }
}


================================================
FILE: src/Entity/.gitignore
================================================


================================================
FILE: src/Entity/Admin.php
================================================
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @ORM\Entity(repositoryClass="App\Repository\AdminRepository")
 */
class Admin implements UserInterface
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=180, unique=true)
     */
    private $username;

    /**
     * @ORM\Column(type="json")
     */
    private $roles = [];

    /**
     * @var string The hashed password
     * @ORM\Column(type="string")
     */
    private $password;

    public function getId(): ?int
    {
        return $this->id;
    }

    /**
     * A visual identifier that represents this user.
     *
     * @see UserInterface
     */
    public function getUsername(): string
    {
        return (string) $this->username;
    }

    public function setUsername(string $username): self
    {
        $this->username = $username;

        return $this;
    }

    /**
     * @see UserInterface
     */
    public function getRoles(): array
    {
        $roles = $this->roles;
        // guarantee every user at least has ROLE_USER
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
    }

    public function setRoles(array $roles): self
    {
        $this->roles = $roles;

        return $this;
    }

    public function __toString(): string
    {
        return $this->username;
    }

    /**
     * @see UserInterface
     */
    public function getPassword(): string
    {
        return (string) $this->password;
    }

    public function setPassword(string $password): self
    {
        $this->password = $password;

        return $this;
    }

    /**
     * @see UserInterface
     */
    public function getSalt()
    {
        // not needed when using the "bcrypt" algorithm in security.yaml
    }

    /**
     * @see UserInterface
     */
    public function eraseCredentials()
    {
        // If you store any temporary, sensitive data on the user, clear it here
        // $this->plainPassword = null;
    }
}


================================================
FILE: src/Entity/Comment.php
================================================
<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass="App\Repository\CommentRepository")
 * @ORM\HasLifecycleCallbacks()
 *
 * @ApiResource(
 *     collectionOperations={"get"={"normalization_context"={"groups"="comment:list"}}},
 *     itemOperations={"get"={"normalization_context"={"groups"="comment:item"}}},
 *     order={"createdAt"="DESC"},
 *     paginationEnabled=false
 * )
 *
 * @ApiFilter(SearchFilter::class, properties={"conference": "exact"})
 */
class Comment
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     *
     * @Groups({"comment:list", "comment:item"})
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank
     *
     * @Groups({"comment:list", "comment:item"})
     */
    private $author;

    /**
     * @ORM\Column(type="text")
     * @Assert\NotBlank
     *
     * @Groups({"comment:list", "comment:item"})
     */
    private $text;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank
     * @Assert\Email
     *
     * @Groups({"comment:list", "comment:item"})
     */
    private $email;

    /**
     * @ORM\Column(type="datetime")
     *
     * @Groups({"comment:list", "comment:item"})
     */
    private $createdAt;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Conference", inversedBy="comments")
     * @ORM\JoinColumn(nullable=false)
     *
     * @Groups({"comment:list", "comment:item"})
     */
    private $conference;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     *
     * @Groups({"comment:list", "comment:item"})
     */
    private $photoFilename;

    /**
     * @ORM\Column(type="string", length=255, options={"default": "submitted"})
     */
    private $state = 'submitted';

    public function __toString(): string
    {
        return (string) $this->getEmail();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getAuthor(): ?string
    {
        return $this->author;
    }

    public function setAuthor(string $author): self
    {
        $this->author = $author;

        return $this;
    }

    public function getText(): ?string
    {
        return $this->text;
    }

    public function setText(string $text): self
    {
        $this->text = $text;

        return $this;
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function setEmail(string $email): self
    {
        $this->email = $email;

        return $this;
    }

    public function getCreatedAt(): ?\DateTimeInterface
    {
        return $this->createdAt;
    }

    public function setCreatedAt(\DateTimeInterface $createdAt): self
    {
        $this->createdAt = $createdAt;

        return $this;
    }

    /**
     * @ORM\PrePersist
     */
    public function setCreatedAtValue()
    {
        $this->createdAt = new \DateTime();
    }

    public function getConference(): ?Conference
    {
        return $this->conference;
    }

    public function setConference(?Conference $conference): self
    {
        $this->conference = $conference;

        return $this;
    }

    public function getPhotoFilename(): ?string
    {
        return $this->photoFilename;
    }

    public function setPhotoFilename(?string $photoFilename): self
    {
        $this->photoFilename = $photoFilename;

        return $this;
    }

    public function getState(): ?string
    {
        return $this->state;
    }

    public function setState(string $state): self
    {
        $this->state = $state;

        return $this;
    }
}


================================================
FILE: src/Entity/Conference.php
================================================
<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
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\Serializer\Annotation\Groups;
use Symfony\Component\String\Slugger\SluggerInterface;

/**
 * @ORM\Entity(repositoryClass="App\Repository\ConferenceRepository")
 * @UniqueEntity("slug")
 *
 * @ApiResource(
 *     collectionOperations={"get"={"normalization_context"={"groups"="conference:list"}}},
 *     itemOperations={"get"={"normalization_context"={"groups"="conference:item"}}},
 *     order={"year"="DESC", "city"="ASC"},
 *     paginationEnabled=false
 * )
 */
class Conference
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     *
     * @Groups({"conference:list", "conference:item"})
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     *
     * @Groups({"conference:list", "conference:item"})
     */
    private $city;

    /**
     * @ORM\Column(type="string", length=4)
     *
     * @Groups({"conference:list", "conference:item"})
     */
    private $year;

    /**
     * @ORM\Column(type="boolean")
     *
     * @Groups({"conference:list", "conference:item"})
     */
    private $isInternational;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Comment", mappedBy="conference", orphanRemoval=true)
     */
    private $comments;

    /**
     * @ORM\Column(type="string", length=255, unique=true)
     *
     * @Groups({"conference:list", "conference:item"})
     */
    private $slug;

    public function __construct()
    {
        $this->comments = new ArrayCollection();
    }

    public function __toString(): string
    {
        return $this->city.' '.$this->year;
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function computeSlug(SluggerInterface $slugger)
    {
        if (!$this->slug || '-' === $this->slug) {
            $this->slug = (string) $slugger->slug((string) $this)->lower();
        }
    }

    public function getCity(): ?string
    {
        return $this->city;
    }

    public function setCity(string $city): self
    {
        $this->city = $city;

        return $this;
    }

    public function getYear(): ?string
    {
        return $this->year;
    }

    public function setYear(string $year): self
    {
        $this->year = $year;

        return $this;
    }

    public function getIsInternational(): ?bool
    {
        return $this->isInternational;
    }

    public function setIsInternational(bool $isInternational): self
    {
        $this->isInternational = $isInternational;

        return $this;
    }

    /**
     * @return Collection|Comment[]
     */
    public function getComments(): Collection
    {
        return $this->comments;
    }

    public function addComment(Comment $comment): self
    {
        if (!$this->comments->contains($comment)) {
            $this->comments[] = $comment;
            $comment->setConference($this);
        }

        return $this;
    }

    public function removeComment(Comment $comment): self
    {
        if ($this->comments->contains($comment)) {
            $this->comments->removeElement($comment);
            // set the owning side to null (unless already changed)
            if ($comment->getConference() === $this) {
                $comment->setConference(null);
            }
        }

        return $this;
    }

    public function getSlug(): ?string
    {
        return $this->slug;
    }

    public function setSlug(string $slug): self
    {
        $this->slug = $slug;

        return $this;
    }
}


================================================
FILE: src/EntityListener/ConferenceEntityListener.php
================================================
<?php

namespace App\EntityListener;

use App\Entity\Conference;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\String\Slugger\SluggerInterface;

class ConferenceEntityListener
{
    private $slugger;

    public function __construct(SluggerInterface $slugger)
    {
        $this->slugger = $slugger;
    }

    public function prePersist(Conference $conference, LifecycleEventArgs $event)
    {
        $conference->computeSlug($this->slugger);
    }

    public function preUpdate(Conference $conference, LifecycleEventArgs $event)
    {
        $conference->computeSlug($this->slugger);
    }
}


================================================
FILE: src/Form/CommentFormType.php
================================================
<?php

namespace App\Form;

use App\Entity\Comment;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Image;

class CommentFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('author', null, [
                'label' => 'Your name',
            ])
            ->add('text')
            ->add('email', EmailType::class)
            ->add('photo', FileType::class, [
                'required' => false,
                'mapped' => false,
                'constraints' => [
                    new Image(['maxSize' => '1024k'])
                ],
            ])
            ->add('submit', SubmitType::class)
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Comment::class,
        ]);
    }
}


================================================
FILE: src/ImageOptimizer.php
================================================
<?php

namespace App;

use Imagine\Gd\Imagine;
use Imagine\Image\Box;

class ImageOptimizer
{
    private const MAX_WIDTH = 200;
    private const MAX_HEIGHT = 150;

    private $imagine;

    public function __construct()
    {
        $this->imagine = new Imagine();
    }

    public function resize(string $filename): void
    {
        list($iwidth, $iheight) = getimagesize($filename);
        $ratio = $iwidth / $iheight;
        $width = self::MAX_WIDTH;
        $height = self::MAX_HEIGHT;
        if ($width / $height > $ratio) {
            $width = $height * $ratio;
        } else {
            $height = $width / $ratio;
        }

        $photo = $this->imagine->open($filename);
        $photo->resize(new Box($width, $height))->save($filename);
    }
}


================================================
FILE: src/Kernel.php
================================================
<?php

namespace App;

use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\RouteCollectionBuilder;

class Kernel extends BaseKernel
{
    use MicroKernelTrait;

    private const CONFIG_EXTS = '.{php,xml,yaml,yml}';

    public function registerBundles(): iterable
    {
        $contents = require $this->getProjectDir().'/config/bundles.php';
        foreach ($contents as $class => $envs) {
            if ($envs[$this->environment] ?? $envs['all'] ?? false) {
                yield new $class();
            }
        }
    }

    public function getProjectDir(): string
    {
        return \dirname(__DIR__);
    }

    protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
    {
        $container->addResource(new FileResource($this->getProjectDir().'/config/bundles.php'));
        $container->setParameter('container.dumper.inline_class_loader', \PHP_VERSION_ID < 70400 || $this->debug);
        $container->setParameter('container.dumper.inline_factories', true);
        $confDir = $this->getProjectDir().'/config';

        $loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob');
        $loader->load($confDir.'/{packages}/'.$this->environment.'/*'.self::CONFIG_EXTS, 'glob');
        $loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob');
        $loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob');
    }

    protected function configureRoutes(RouteCollectionBuilder $routes): void
    {
        $confDir = $this->getProjectDir().'/config';

        $routes->import($confDir.'/{routes}/'.$this->environment.'/*'.self::CONFIG_EXTS, '/', 'glob');
        $routes->import($confDir.'/{routes}/*'.self::CONFIG_EXTS, '/', 'glob');
        $routes->import($confDir.'/{routes}'.self::CONFIG_EXTS, '/', 'glob');
    }
}


================================================
FILE: src/Message/CommentMessage.php
================================================
<?php

namespace App\Message;

class CommentMessage
{
    private $id;
    private $reviewUrl;
    private $context;

    public function __construct(int $id, string $reviewUrl, array $context = [])
    {
        $this->id = $id;
        $this->reviewUrl = $reviewUrl;
        $this->context = $context;
    }

    public function getReviewUrl(): string
    {
        return $this->reviewUrl;
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function getContext(): array
    {
        return $this->context;
    }
}


================================================
FILE: src/MessageHandler/CommentMessageHandler.php
================================================
<?php

namespace App\MessageHandler;

use App\ImageOptimizer;
use App\Message\CommentMessage;
use App\Notification\CommentReviewNotification;
use App\Repository\CommentRepository;
use App\SpamChecker;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Notifier\NotifierInterface;
use Symfony\Component\Workflow\WorkflowInterface;

class CommentMessageHandler implements MessageHandlerInterface
{
    private $spamChecker;
    private $entityManager;
    private $commentRepository;
    private $bus;
    private $workflow;
    private $notifier;
    private $imageOptimizer;
    private $photoDir;
    private $logger;

    public function __construct(EntityManagerInterface $entityManager, SpamChecker $spamChecker, CommentRepository $commentRepository, MessageBusInterface $bus, WorkflowInterface $commentStateMachine, NotifierInterface $notifier, ImageOptimizer $imageOptimizer, string $photoDir, LoggerInterface $logger = null)
    {
        $this->entityManager = $entityManager;
        $this->spamChecker = $spamChecker;
        $this->commentRepository = $commentRepository;
        $this->bus = $bus;
        $this->workflow = $commentStateMachine;
        $this->notifier = $notifier;
        $this->imageOptimizer = $imageOptimizer;
        $this->photoDir = $photoDir;
        $this->logger = $logger;
    }

    public function __invoke(CommentMessage $message)
    {
        $comment = $this->commentRepository->find($message->getId());
        if (!$comment) {
            return;
        }


        if ($this->workflow->can($comment, 'accept')) {
            $score = $this->spamChecker->getSpamScore($comment, $message->getContext());
            $transition = 'accept';
            if (2 === $score) {
                $transition = 'reject_spam';
            } elseif (1 === $score) {
                $transition = 'might_be_spam';
            }
            $this->workflow->apply($comment, $transition);
            $this->entityManager->flush();

            $this->bus->dispatch($message);
        } elseif ($this->workflow->can($comment, 'publish') || $this->workflow->can($comment, 'publish_ham')) {
            $notification = new CommentReviewNotification($comment, $message->getReviewUrl());
            $this->notifier->send($notification, ...$this->notifier->getAdminRecipients());
        } elseif ($this->workflow->can($comment, 'optimize')) {
            if ($comment->getPhotoFilename()) {
                $this->imageOptimizer->resize($this->photoDir.'/'.$comment->getPhotoFilename());
            }
            $this->workflow->apply($comment, 'optimize');
            $this->entityManager->flush();
        } elseif ($this->logger) {
            $this->logger->debug('Dropping comment message', ['comment' => $comment->getId(), 'state' => $comment->getState()]);
        }
    }
}


================================================
FILE: src/Migrations/.gitignore
================================================


================================================
FILE: src/Migrations/Version20200107080917.php
================================================
<?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 Version20200107080917 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->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');

        $this->addSql('CREATE SEQUENCE comment_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
        $this->addSql('CREATE SEQUENCE conference_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
        $this->addSql('CREATE TABLE comment (id INT NOT NULL, conference_id INT NOT NULL, author VARCHAR(255) NOT NULL, text TEXT NOT NULL, email VARCHAR(255) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, photo_filename VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
        $this->addSql('CREATE INDEX IDX_9474526C604B8382 ON comment (conference_id)');
        $this->addSql('CREATE TABLE conference (id INT NOT NULL, city VARCHAR(255) NOT NULL, year VARCHAR(4) NOT NULL, is_international BOOLEAN NOT NULL, PRIMARY KEY(id))');
        $this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526C604B8382 FOREIGN KEY (conference_id) REFERENCES conference (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
    }

    public function down(Schema $schema) : void
    {
        // this down() migration is auto-generated, please modify it to your needs
        $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');

        $this->addSql('CREATE SCHEMA public');
        $this->addSql('ALTER TABLE comment DROP CONSTRAINT FK_9474526C604B8382');
        $this->addSql('DROP SEQUENCE comment_id_seq CASCADE');
        $this->addSql('DROP SEQUENCE conference_id_seq CASCADE');
        $this->addSql('DROP TABLE comment');
        $this->addSql('DROP TABLE conference');
    }
}


================================================
FILE: src/Migrations/Version20200107081222.php
================================================
<?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 Version20200107081222 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->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');

        $this->addSql('ALTER TABLE conference ADD slug VARCHAR(255)');
        $this->addSql("UPDATE conference SET slug=CONCAT(LOWER(city), '-', year)");
        $this->addSql('ALTER TABLE conference ALTER COLUMN slug SET NOT NULL');
    }

    public function down(Schema $schema) : void
    {
        // this down() migration is auto-generated, please modify it to your needs
        $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');

        $this->addSql('CREATE SCHEMA public');
        $this->addSql('ALTER TABLE conference DROP slug');
    }
}


================================================
FILE: src/Migrations/Version20200107081238.php
================================================
<?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 Version20200107081238 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->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');

        $this->addSql('CREATE UNIQUE INDEX UNIQ_911533C8989D9B62 ON conference (slug)');
    }

    public function down(Schema $schema) : void
    {
        // this down() migration is auto-generated, please modify it to your needs
        $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');

        $this->addSql('CREATE SCHEMA public');
        $this->addSql('DROP INDEX UNIQ_911533C8989D9B62');
    }
}


================================================
FILE: src/Migrations/Version20200107081419.php
================================================
<?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 Version20200107081419 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->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');

        $this->addSql('CREATE SEQUENCE admin_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
        $this->addSql('CREATE TABLE admin (id INT NOT NULL, username VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
        $this->addSql('CREATE UNIQUE INDEX UNIQ_880E0D76F85E0677 ON admin (username)');
    }

    public function down(Schema $schema) : void
    {
        // this down() migration is auto-generated, please modify it to your needs
        $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');

        $this->addSql('CREATE SCHEMA public');
        $this->addSql('DROP SEQUENCE admin_id_seq CASCADE');
        $this->addSql('DROP TABLE admin');
    }
}


================================================
FILE: src/Migrations/Version20200107081708.php
================================================
<?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 Version20200107081708 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->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');

        $this->addSql('ALTER TABLE comment ADD state VARCHAR(255)');
        $this->addSql("UPDATE comment SET state='published'");
        $this->addSql('ALTER TABLE comment ALTER COLUMN state SET NOT NULL');
    }

    public function down(Schema $schema) : void
    {
        // this down() migration is auto-generated, please modify it to your needs
        $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');

        $this->addSql('CREATE SCHEMA public');
        $this->addSql('ALTER TABLE comment DROP state');
    }
}


================================================
FILE: src/Notification/CommentReviewNotification.php
================================================
<?php

namespace App\Notification;

use App\Entity\Comment;
use Symfony\Component\Notifier\Bridge\Slack\Block\SlackActionsBlock;
use Symfony\Component\Notifier\Bridge\Slack\Block\SlackDividerBlock;
use Symfony\Component\Notifier\Bridge\Slack\Block\SlackSectionBlock;
use Symfony\Component\Notifier\Bridge\Slack\SlackOptions;
use Symfony\Component\Notifier\Message\ChatMessage;
use Symfony\Component\Notifier\Message\EmailMessage;
use Symfony\Component\Notifier\Notification\ChatNotificationInterface;
use Symfony\Component\Notifier\Notification\EmailNotificationInterface;
use Symfony\Component\Notifier\Notification\Notification;
use Symfony\Component\Notifier\Recipient\Recipient;

class CommentReviewNotification extends Notification implements EmailNotificationInterface, ChatNotificationInterface
{
    private $comment;
    private $reviewUrl;

    public function __construct(Comment $comment, string $reviewUrl)
    {
        $this->comment = $comment;
        $this->reviewUrl = $reviewUrl;

        parent::__construct('New comment posted');
    }

    public function asEmailMessage(Recipient $recipient, string $transport = null): ?EmailMessage
    {
        $message = EmailMessage::fromNotification($this, $recipient, $transport);
        $message->getMessage()
            ->htmlTemplate('emails/comment_notification.html.twig')
            ->context(['comment' => $this->comment])
        ;

        return $message;
    }

    public function asChatMessage(Recipient $recipient, string $transport = null): ?ChatMessage
    {
        if ('slack' !== $transport) {
            return null;
        }

        $message = ChatMessage::fromNotification($this, $recipient, $transport);
        $message->subject($this->getSubject());
        $message->options((new SlackOptions())
            ->iconEmoji('tada')
            ->iconUrl('https://guestbook.example.com')
            ->username('Guestbook')
            ->block((new SlackSectionBlock())->text($this->getSubject()))
            ->block(new SlackDividerBlock())
            ->block((new SlackSectionBlock())
                ->text(sprintf('%s (%s) says: %s', $this->comment->getAuthor(), $this->comment->getEmail(), $this->comment->getText()))
            )
            ->block((new SlackActionsBlock())
                ->button('Accept', $this->reviewUrl, 'primary')
                ->button('Reject', $this->reviewUrl.'?reject=1', 'danger')
            )
        );

        return $message;
    }

    public function getChannels(Recipient $recipient): array
    {
        if (preg_match('{\b(great|awesome)\b}i', $this->comment->getText())) {
            return ['email', 'chat/slack'];
        }

        $this->importance(Notification::IMPORTANCE_LOW);

        return ['email'];
    }
}


================================================
FILE: src/Repository/.gitignore
================================================


================================================
FILE: src/Repository/AdminRepository.php
================================================
<?php

namespace App\Repository;

use App\Entity\Admin;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @method Admin|null find($id, $lockMode = null, $lockVersion = null)
 * @method Admin|null findOneBy(array $criteria, array $orderBy = null)
 * @method Admin[]    findAll()
 * @method Admin[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 */
class AdminRepository extends ServiceEntityRepository implements PasswordUpgraderInterface
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Admin::class);
    }

    /**
     * Used to upgrade (rehash) the user's password automatically over time.
     */
    public function upgradePassword(UserInterface $user, string $newEncodedPassword): void
    {
        if (!$user instanceof User) {
            throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', \get_class($user)));
        }

        $user->setPassword($newEncodedPassword);
        $this->_em->persist($user);
        $this->_em->flush();
    }

    // /**
    //  * @return Admin[] Returns an array of Admin objects
    //  */
    /*
    public function findByExampleField($value)
    {
        return $this->createQueryBuilder('a')
            ->andWhere('a.exampleField = :val')
            ->setParameter('val', $value)
            ->orderBy('a.id', 'ASC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult()
        ;
    }
    */

    /*
    public function findOneBySomeField($value): ?Admin
    {
        return $this->createQueryBuilder('a')
            ->andWhere('a.exampleField = :val')
            ->setParameter('val', $value)
            ->getQuery()
            ->getOneOrNullResult()
        ;
    }
    */
}


================================================
FILE: src/Repository/CommentRepository.php
================================================
<?php

namespace App\Repository;

use App\Entity\Comment;
use App\Entity\Conference;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Tools\Pagination\Paginator;

/**
 * @method Comment|null find($id, $lockMode = null, $lockVersion = null)
 * @method Comment|null findOneBy(array $criteria, array $orderBy = null)
 * @method Comment[]    findAll()
 * @method Comment[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 */
class CommentRepository extends ServiceEntityRepository
{
    private const DAYS_BEFORE_REJECTED_REMOVAL = 7;

    public const PAGINATOR_PER_PAGE = 2;

    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Comment::class);
    }

    public function countOldRejected(): int
    {
        return $this->getOldRejectedQueryBuilder()->select('COUNT(c.id)')->getQuery()->getSingleScalarResult();
    }

    public function deleteOldRejected(): int
    {
        return $this->getOldRejectedQueryBuilder()->delete()->getQuery()->execute();
    }

    private function getOldRejectedQueryBuilder(): QueryBuilder
    {
        return $this->createQueryBuilder('c')
            ->andWhere('c.state = :state_rejected or c.state = :state_spam')
            ->andWhere('c.createdAt < :date')
            ->setParameters([
                'state_rejected' => 'rejected',
                'state_spam' => 'spam',
                'date' => new \DateTime(-self::DAYS_BEFORE_REJECTED_REMOVAL.' days'),
            ])
        ;
    }

    public function getCommentPaginator(Conference $conference, int $offset): Paginator
    {
        $query = $this->createQueryBuilder('c')
            ->andWhere('c.conference = :conference')
            ->andWhere('c.state = :state')
            ->setParameter('conference', $conference)
            ->setParameter('state', 'published')
            ->orderBy('c.createdAt', 'DESC')
            ->setMaxResults(self::PAGINATOR_PER_PAGE)
            ->setFirstResult($offset)
            ->getQuery()
        ;

        return new Paginator($query);
    }

    // /**
    //  * @return Comment[] Returns an array of Comment objects
    //  */
    /*
    public function findByExampleField($value)
    {
        return $this->createQueryBuilder('c')
            ->andWhere('c.exampleField = :val')
            ->setParameter('val', $value)
            ->orderBy('c.id', 'ASC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult()
        ;
    }
    */

    /*
    public function findOneBySomeField($value): ?Comment
    {
        return $this->createQueryBuilder('c')
            ->andWhere('c.exampleField = :val')
            ->setParameter('val', $value)
            ->getQuery()
            ->getOneOrNullResult()
        ;
    }
    */
}


================================================
FILE: src/Repository/ConferenceRepository.php
================================================
<?php

namespace App\Repository;

use App\Entity\Conference;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;

/**
 * @method Conference|null find($id, $lockMode = null, $lockVersion = null)
 * @method Conference|null findOneBy(array $criteria, array $orderBy = null)
 * @method Conference[]    findAll()
 * @method Conference[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 */
class ConferenceRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Conference::class);
    }

    public function findAll()
    {
        return $this->findBy([], ['year' => 'ASC', 'city' => 'ASC']);
    }

    // /**
    //  * @return Conference[] Returns an array of Conference objects
    //  */
    /*
    public function findByExampleField($value)
    {
        return $this->createQueryBuilder('c')
            ->andWhere('c.exampleField = :val')
            ->setParameter('val', $value)
            ->orderBy('c.id', 'ASC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult()
        ;
    }
    */

    /*
    public function findOneBySomeField($value): ?Conference
    {
        return $this->createQueryBuilder('c')
            ->andWhere('c.exampleField = :val')
            ->setParameter('val', $value)
            ->getQuery()
            ->getOneOrNullResult()
        ;
    }
    */
}


================================================
FILE: src/Security/AppAuthenticator.php
================================================
<?php

namespace App\Security;

use App\Entity\Admin;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait;

class AppAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
    use TargetPathTrait;

    private $entityManager;
    private $urlGenerator;
    private $csrfTokenManager;
    private $passwordEncoder;

    public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
    {
        $this->entityManager = $entityManager;
        $this->urlGenerator = $urlGenerator;
        $this->csrfTokenManager = $csrfTokenManager;
        $this->passwordEncoder = $passwordEncoder;
    }

    public function supports(Request $request)
    {
        return 'app_login' === $request->attributes->get('_route')
            && $request->isMethod('POST');
    }

    public function getCredentials(Request $request)
    {
        $credentials = [
            'username' => $request->request->get('username'),
            'password' => $request->request->get('password'),
            'csrf_token' => $request->request->get('_csrf_token'),
        ];
        $request->getSession()->set(
            Security::LAST_USERNAME,
            $credentials['username']
        );

        return $credentials;
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        $token = new CsrfToken('authenticate', $credentials['csrf_token']);
        if (!$this->csrfTokenManager->isTokenValid($token)) {
            throw new InvalidCsrfTokenException();
        }

        $user = $this->entityManager->getRepository(Admin::class)->findOneBy(['username' => $credentials['username']]);

        if (!$user) {
            // fail authentication with a custom error
            throw new CustomUserMessageAuthenticationException('Username could not be found.');
        }

        return $user;
    }

    public function checkCredentials($credentials, UserInterface $user)
    {
        return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
    }

    /**
     * Used to upgrade (rehash) the user's password automatically over time.
     */
    public function getPassword($credentials): ?string
    {
        return $credentials['password'];
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
            return new RedirectResponse($targetPath);
        }

        return new RedirectResponse($this->urlGenerator->generate('easyadmin'));
    }

    protected function getLoginUrl()
    {
        return $this->urlGenerator->generate('app_login');
    }
}


================================================
FILE: src/SpamChecker.php
================================================
<?php

namespace App;

use App\Entity\Comment;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class SpamChecker
{
    private $client;
    private $endpoint;

    public function __construct(HttpClientInterface $client, string $akismetKey)
    {
        $this->client = $client;
        $this->endpoint = sprintf('https://%s.rest.akismet.com/1.1/comment-check', $akismetKey);
    }

    /**
     * @return int Spam score: 0: not spam, 1: maybe spam, 2: blatant spam
     *
     * @throws \RuntimeException if the call did not work
     */
    public function getSpamScore(Comment $comment, array $context): int
    {
        $response = $this->client->request('POST', $this->endpoint, [
            'body' => array_merge($context, [
                'blog' => 'https://guestbook.example.com',
                'comment_type' => 'comment',
                'comment_author' => $comment->getAuthor(),
                'comment_author_email' => $comment->getEmail(),
                'comment_content' => $comment->getText(),
                'comment_date_gmt' => $comment->getCreatedAt()->format('c'),
                'blog_lang' => 'en',
                'blog_charset' => 'UTF-8',
                'is_test' => true,
            ]),
        ]);

        $headers = $response->getHeaders();
        if ('discard' === ($headers['x-akismet-pro-tip'][0] ?? '')) {
            return 2;
        }

        $content = $response->getContent();
        if (isset($headers['x-akismet-debug-help'][0])) {
            throw new \RuntimeException(sprintf('Unable to check for spam: %s (%s).', $content, $headers['x-akismet-debug-help'][0]));
        }

        return 'true' === $content ? 1 : 0;
    }
}


================================================
FILE: templates/admin/review.html.twig
================================================
{% extends 'base.html.twig' %}

{% block body %}
    <h2>Comment reviewed, thank you!</h2>

    <p>Applied transition: <strong>{{ transition }}</strong></p>
    <p>New state: <strong>{{ comment.state }}</strong></p>
{% endblock %}


================================================
FILE: templates/base.html.twig
================================================
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

        <title>{% block title %}Welcome!{% endblock %}</title>

        {% block stylesheets %}
            <link href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,500,500i,600,600i&display=swap" rel="stylesheet" />
            {{ encore_entry_link_tags('app') }}
        {% endblock %}
    </head>
    <body>
        <header class="header">
            <h1 class="sr-only">
                Conference Guestbook
            </h1>

            <nav class="navbar navbar-expand-xl navbar-light bg-light">
                <div class="container mt-4 mb-3">
                    <a class="navbar-brand mr-4 pr-2" href="{{ path('homepage') }}">
                        &#128217; {{ 'Conference Guestbook'|trans }}
                    </a>

                    <button class="navbar-toggler border-0" type="button" data-toggle="collapse" data-target="#header-menu" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Afficher/Cacher la navigation">
                        <span class="navbar-toggler-icon"></span>
                    </button>

                    <div class="collapse navbar-collapse" id="header-menu">
                        <ul class="navbar-nav ml-auto">
                            <li class="nav-item mr-3">
                                <a class="nav-link" href="{{ path('easyadmin') }}">
                                    Admin
                                </a>
                            </li>
<li class="nav-item dropdown">
    <a class="nav-link dropdown-toggle" href="#" id="dropdown-language" role="button"
        data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
        {{ app.request.locale|locale_name(app.request.locale)|u.title }}
    </a>
    <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdown-language">
        <a class="dropdown-item" href="{{ path('homepage', {_locale: 'en'}) }}">English</a>
        <a class="dropdown-item" href="{{ path('homepage', {_locale: 'fr'}) }}">Français</a>
    </div>
</li>
                        </ul>
                    </div>
                </div>
            </nav>

            <nav class="bg-light border-bottom">
                <div class="container">
                    {{ render_esi(path('conference_header')) }}
                </div>
            </nav>
        </header>

        <main role="main" class="container mt-5">
            {% block body %}{% endblock %}
        </main>

        <footer class="mt-7 px-3 py-5 text-center text-muted">
            <p>
                Conference Guestbook
            </p>
            <p>
                <a href="#" class="text-white">Back to top</a>
            </p>
        </footer>

        {% block javascripts %}
            {{ encore_entry_script_tags('app') }}
        {% endblock %}
    </body>
</html>


================================================
FILE: templates/conference/header.html.twig
================================================
{% for conference in conferences %}
    <a class="nav-conference" href="{{ path('conference', { slug: conference.slug }) }}">
        {{ conference }}
    </a>
{% endfor %}


================================================
FILE: templates/conference/index.html.twig
================================================
{% extends 'base.html.twig' %}

{% block title %}Conference Guestbook{% endblock %}

{% block body %}
    <h2 class="mb-5">
        {{ 'Give your feedback!'|trans }}
    </h2>

    {% for row in conferences|batch(4) %}
        <div class="row">
            {% for conference in row %}
                <div class="col-12 col-md-6 col-lg-3 mb-4">
                    <div class="card border shadow lift">
                        <div class="card-body">
                            <div class="card-title">
                                <h4 class="font-weight-light">
                                    {{ conference }}
                                </h4>
                            </div>

                            <a href="{{ path('conference', { slug: conference.slug }) }}"
                               class="btn btn-sm btn-blue stretched-link">
                                {{ 'View'|trans }}
                            </a>
                        </div>
                    </div>
                </div>
            {% endfor %}
        </div>
    {% endfor %}
{% endblock %}


================================================
FILE: templates/conference/show.html.twig
================================================
{% extends 'base.html.twig' %}

{% block title %}Conference Guestbook - {{ conference }}{% endblock %}

{% block body %}
    {% for message in app.flashes('notification') %}
        <div class="alert alert-info alert-dismissible fade show">
            {{ message }}
            <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        </div>
    {% endfor %}

    <h2 class="mb-5">
        {{ conference }} Conference
    </h2>

    <div class="row">
        <div class="col-12 col-lg-8">
            {% if comments|length > 0 %}
                {% for comment in comments %}
                    <div class="media shadow border rounded-lg p-3 mb-4">
                        <div class="comment-img mr-3">
                            {% if comment.photofilename %}
                                <a href="{{ asset('uploads/photos/' ~ comment.photofilename) }}" target="_blank">
                                    <img src="{{ asset('uploads/photos/' ~ comment.photofilename) }}" />
                                </a>
                            {% endif %}
                        </div>

                        <div class="media-body">
                            <h4 class="font-weight-light mb-0">
                                {{ comment.author }}
                            </h4>

                            <div class="mb-2">
                                <small class="text-muted text-uppercase">
                                    {{ comment.createdAt|format_datetime('medium', 'short') }}
                                </small>
                            </div>

                            <div class="comment-text">
                                {{ comment.text|nl2br }}
                            </div>
                        </div>
                    </div>
                {% endfor %}
                <div>{{ 'nb_of_comments'|trans({count: comments|length}) }}</div>
                {% if previous >= 0 %}
                    <a href="{{ path('conference', { slug: conference.slug, offset: previous }) }}">Previous</a>
                {% endif %}
                {% if next < comments|length %}
                    <a href="{{ path('conference', { slug: conference.slug, offset: next }) }}">Next</a>
                {% endif %}
            {% else %}
                <div class="text-center">
                    No comments have been posted yet for this conference.
                </div>
            {% endif %}
        </div>
        <div class="col-12 col-lg-4">
            <div class="bg-light shadow border rounded-lg p-4">
                <h3 class="font-weight-light">
                    Add your own feedback
                </h3>

                {{ form(comment_form) }}
            </div>
        </div>
    </div>
{% endblock %}


================================================
FILE: templates/emails/comment_notification.html.twig
================================================
{% extends '@email/default/notification/body.html.twig' %}

{% block content %}
    Author: {{ comment.author }}<br />
    Email: {{ comment.email }}<br />
    State: {{ comment.state }}<br />

    <p>
        {{ comment.text }}
    </p>
{% endblock %}

{% block action %}
    <spacer size="16"></spacer>
    <button href="{{ url('review_comment', { id: comment.id }) }}">Accept</button>
    <button href="{{ url('review_comment', { id: comment.id, reject: true }) }}">Reject</button>
{% endblock %}


================================================
FILE: templates/security/login.html.twig
================================================
{% extends 'base.html.twig' %}

{% block title %}Log in!{% endblock %}

{% block body %}
<form method="post">
    {% if error %}
        <div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
    {% endif %}

    {% if app.user %}
        <div class="mb-3">
            You are logged in as {{ app.user.username }}, <a href="{{ path('app_logout') }}">Logout</a>
        </div>
    {% endif %}

    <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
    <label for="inputUsername">Username</label>
    <input type="text" value="{{ last_username }}" name="username" id="inputUsername" class="form-control" required autofocus>
    <label for="inputPassword">Password</label>
    <input type="password" name="password" id="inputPassword" class="form-control" required>

    <input type="hidden" name="_csrf_token"
           value="{{ csrf_token('authenticate') }}"
    >

    {#
        Uncomment this section and add a remember_me option below your firewall to activate remember me functionality.
        See https://symfony.com/doc/current/security/remember_me.html

        <div class="checkbox mb-3">
            <label>
                <input type="checkbox" name="_remember_me"> Remember me
            </label>
        </div>
    #}

    <button class="btn btn-lg btn-primary" type="submit">
        Sign in
    </button>
</form>
{% endblock %}


================================================
FILE: tests/.gitignore
================================================


================================================
FILE: tests/Controller/ConferenceControllerTest.php
================================================
<?php

namespace App\Tests\Controller;

use App\Repository\CommentRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class ConferenceControllerTest extends WebTestCase
{
    public function testIndex()
    {
        $client = static::createClient();
        $client->request('GET', '/en/');

        $this->assertResponseIsSuccessful();
        $this->assertSelectorTextContains('h2', 'Give your feedback');
    }

    public function testCommentSubmission()
    {
        $client = static::createClient();
        $client->request('GET', '/en/conference/amsterdam-2019');
        $client->submitForm('Submit', [
            'comment_form[author]' => 'Fabien',
            'comment_form[text]' => 'Some feedback from an automated functional test',
            'comment_form[email]' => $email = 'me@automat.ed',
            'comment_form[photo]' => dirname(__DIR__, 2).'/public/images/under-construction.gif',
        ]);
        $this->assertResponseRedirects();

        // simulate comment validation
        $comment = self::$container->get(CommentRepository::class)->findOneByEmail($email);
        $comment->setState('published');
        self::$container->get(EntityManagerInterface::class)->flush();

        $client->followRedirect();
        $this->assertSelectorExists('div:contains("There are 2 comments")');
    }

    public function testConferencePage()
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/en/');

        $this->assertCount(2, $crawler->filter('h4'));

        $client->clickLink('View');

        $this->assertPageTitleContains('Amsterdam');
        $this->assertResponseIsSuccessful();
        $this->assertSelectorTextContains('h2', 'Amsterdam 2019');
        $this->assertSelectorExists('div:contains("There is one comment")');
    }
}


================================================
FILE: tests/SpamCheckerTest.php
================================================
<?php

namespace App\Tests;

use App\Entity\Comment;
use App\SpamChecker;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
use Symfony\Contracts\HttpClient\ResponseInterface;

class SpamCheckerTest extends TestCase
{
    public function testSpamScoreWithInvalidRequest()
    {
        $comment = new Comment();
        $comment->setCreatedAtValue();
        $context = [];

        $client = new MockHttpClient([new MockResponse('invalid', ['response_headers' => ['x-akismet-debug-help: Invalid key']])]);
        $checker = new SpamChecker($client, 'abcde');

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Unable to check for spam: invalid (Invalid key).');
        $checker->getSpamScore($comment, $context);
    }

    /**
     * @dataProvider getComments
     */
    public function testSpamScore(int $expectedScore, ResponseInterface $response, Comment $comment, array $context)
    {
        $client = new MockHttpClient([$response]);
        $checker = new SpamChecker($client, 'abcde');

        $score = $checker->getSpamScore($comment, $context);
        $this->assertSame($expectedScore, $score);
    }

    public function getComments(): iterable
    {
        $comment = new Comment();
        $comment->setCreatedAtValue();
        $context = [];

        $response = new MockResponse('', ['response_headers' => ['x-akismet-pro-tip: discard']]);
        yield 'blatant_spam' => [2, $response, $comment, $context];

        $response = new MockResponse('true');
        yield 'spam' => [1, $response, $comment, $context];

        $response = new MockResponse('false');
        yield 'ham' => [0, $response, $comment, $context];
    }
}


================================================
FILE: translations/.gitignore
================================================


================================================
FILE: translations/messages+intl-icu.en.xlf
================================================
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
  <file source-language="en" target-language="en" datatype="plaintext" original="file.ext">
    <header>
      <tool tool-id="symfony" tool-name="Symfony"/>
    </header>
    <body>
      <trans-unit id="maMQz7W" resname="nb_of_comments">
        <source>nb_of_comments</source>
        <target>{count, plural, =0 {There are no comments.} one {There is one comment.} other {There are # comments.}}</target>
      </trans-unit>
    </body>
  </file>
</xliff>


================================================
FILE: translations/messages+intl-icu.fr.xlf
================================================
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
  <file source-language="en" target-language="fr" datatype="plaintext" original="file.ext">
    <header>
      <tool tool-id="symfony" tool-name="Symfony"/>
    </header>
    <body>
      <trans-unit id="LNAVleg" resname="Give your feedback!">
        <source>Give your feedback!</source>
        <target>Donnez votre avis !</target>
      </trans-unit>
      <trans-unit id="3Mg5pAF" resname="View">
        <source>View</source>
        <target>Sélectionner</target>
      </trans-unit>
      <trans-unit id="eOy4.6V" resname="Conference Guestbook">
        <source>Conference Guestbook</source>
        <target>Livre d'Or pour Conferences</target>
      </trans-unit>
      <trans-unit id="Dg2dPd6" resname="nb_of_comments">
        <source>nb_of_comments</source>
        <target>{count, plural, =0 {Aucun commentaire.} =1 {1 commentaire.} other {# commentaires.}}</target>
      </trans-unit>
    </body>
  </file>
</xliff>


================================================
FILE: webpack.config.js
================================================
var Encore = require('@symfony/webpack-encore');

// Manually configure the runtime environment if not already configured yet by the "encore" command.
// It's useful when you use tools that rely on webpack.config.js file.
if (!Encore.isRuntimeEnvironmentConfigured()) {
    Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
}

Encore
    // directory where compiled assets will be stored
    .setOutputPath('public/build/')
    // public path used by the web server to access the output path
    .setPublicPath('/build')
    // only needed for CDN's or sub-directory deploy
    //.setManifestKeyPrefix('build/')

    /*
     * ENTRY CONFIG
     *
     * Add 1 entry for each "page" of your app
     * (including one that's included on every page - e.g. "app")
     *
     * Each entry will result in one JavaScript file (e.g. app.js)
     * and one CSS file (e.g. app.css) if your JavaScript imports CSS.
     */
    .addEntry('app', './assets/js/app.js')
    //.addEntry('page1', './assets/js/page1.js')
    //.addEntry('page2', './assets/js/page2.js')

    // When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
    .splitEntryChunks()

    // will require an extra script tag for runtime.js
    // but, you probably want this, unless you're building a single-page app
    .enableSingleRuntimeChunk()

    /*
     * FEATURE CONFIG
     *
     * Enable & configure other features below. For a full
     * list of features, see:
     * https://symfony.com/doc/current/frontend.html#adding-more-features
     */
    .cleanupOutputBeforeBuild()
    .enableBuildNotifications()
    .enableSourceMaps(!Encore.isProduction())
    // enables hashed filenames (e.g. app.abc123.css)
    .enableVersioning(Encore.isProduction())

    // enables @babel/preset-env polyfills
    .configureBabelPresetEnv((config) => {
        config.useBuiltIns = 'usage';
        config.corejs = 3;
    })

    // enables Sass/SCSS support
    .enableSassLoader()

    // uncomment if you use TypeScript
    //.enableTypeScriptLoader()

    // uncomment to get integrity="..." attributes on your script & link tags
    // requires WebpackEncoreBundle 1.4 or higher
    //.enableIntegrityHashes(Encore.isProduction())

    // uncomment if you're having problems with a jQuery plugin
    //.autoProvidejQuery()

    // uncomment if you use API Platform Admin (composer req api-admin)
    //.enableReactPreset()
    //.addEntry('admin', './assets/js/admin.js')
;

module.exports = Encore.getWebpackConfig();
Download .txt
gitextract_iisn5zjd/

├── .blackfire.yaml
├── .gitignore
├── .symfony/
│   ├── config.vcl
│   ├── routes.yaml
│   └── services.yaml
├── .symfony.cloud.yaml
├── Makefile
├── assets/
│   ├── css/
│   │   ├── _variables.scss
│   │   └── app.scss
│   └── js/
│       └── app.js
├── bin/
│   ├── console
│   └── phpunit
├── blackfire-player.phar
├── composer.json
├── config/
│   ├── bootstrap.php
│   ├── bundles.php
│   ├── packages/
│   │   ├── api_platform.yaml
│   │   ├── assets.yaml
│   │   ├── cache.yaml
│   │   ├── dev/
│   │   │   ├── debug.yaml
│   │   │   ├── easy_log_handler.yaml
│   │   │   ├── monolog.yaml
│   │   │   └── web_profiler.yaml
│   │   ├── doctrine.yaml
│   │   ├── doctrine_migrations.yaml
│   │   ├── easy_admin.yaml
│   │   ├── framework.yaml
│   │   ├── mailer.yaml
│   │   ├── messenger.yaml
│   │   ├── nelmio_cors.yaml
│   │   ├── notifier.yaml
│   │   ├── prod/
│   │   │   ├── doctrine.yaml
│   │   │   ├── monolog.yaml
│   │   │   ├── routing.yaml
│   │   │   └── webpack_encore.yaml
│   │   ├── routing.yaml
│   │   ├── security.yaml
│   │   ├── sensio_framework_extra.yaml
│   │   ├── test/
│   │   │   ├── dama_doctrine_test_bundle.yaml
│   │   │   ├── framework.yaml
│   │   │   ├── monolog.yaml
│   │   │   ├── twig.yaml
│   │   │   ├── validator.yaml
│   │   │   ├── web_profiler.yaml
│   │   │   └── webpack_encore.yaml
│   │   ├── translation.yaml
│   │   ├── twig.yaml
│   │   ├── validator.yaml
│   │   ├── webpack_encore.yaml
│   │   └── workflow.yaml
│   ├── routes/
│   │   ├── annotations.yaml
│   │   ├── api_platform.yaml
│   │   ├── dev/
│   │   │   ├── framework.yaml
│   │   │   └── web_profiler.yaml
│   │   └── easy_admin.yaml
│   ├── routes.yaml
│   ├── secrets/
│   │   ├── dev/
│   │   │   ├── dev.AKISMET_KEY.ca01fb.php
│   │   │   ├── dev.SLACK_DSN.b2b579.php
│   │   │   ├── dev.decrypt.private.php
│   │   │   ├── dev.encrypt.public.php
│   │   │   └── dev.list.php
│   │   ├── prod/
│   │   │   ├── prod.AKISMET_KEY.ca01fb.php
│   │   │   ├── prod.SLACK_DSN.b2b579.php
│   │   │   ├── prod.encrypt.public.php
│   │   │   └── prod.list.php
│   │   └── test/
│   │       ├── test.AKISMET_KEY.ca01fb.php
│   │       ├── test.decrypt.private.php
│   │       ├── test.encrypt.public.php
│   │       └── test.list.php
│   └── services.yaml
├── docker-compose.yaml
├── package.json
├── php.ini
├── phpunit.xml.dist
├── public/
│   └── index.php
├── spa/
│   ├── .gitignore
│   ├── .symfony.cloud.yaml
│   ├── assets/
│   │   └── css/
│   │       ├── _variables.scss
│   │       └── app.scss
│   ├── package.json
│   ├── src/
│   │   ├── api/
│   │   │   └── api.js
│   │   ├── app.js
│   │   ├── index.ejs
│   │   └── pages/
│   │       ├── conference.js
│   │       └── home.js
│   └── webpack.config.js
├── src/
│   ├── Api/
│   │   └── FilterPublishedCommentQueryExtension.php
│   ├── Command/
│   │   ├── CommentCleanupCommand.php
│   │   └── StepInfoCommand.php
│   ├── Controller/
│   │   ├── .gitignore
│   │   ├── AdminController.php
│   │   ├── ConferenceController.php
│   │   └── SecurityController.php
│   ├── DataFixtures/
│   │   └── AppFixtures.php
│   ├── Entity/
│   │   ├── .gitignore
│   │   ├── Admin.php
│   │   ├── Comment.php
│   │   └── Conference.php
│   ├── EntityListener/
│   │   └── ConferenceEntityListener.php
│   ├── Form/
│   │   └── CommentFormType.php
│   ├── ImageOptimizer.php
│   ├── Kernel.php
│   ├── Message/
│   │   └── CommentMessage.php
│   ├── MessageHandler/
│   │   └── CommentMessageHandler.php
│   ├── Migrations/
│   │   ├── .gitignore
│   │   ├── Version20200107080917.php
│   │   ├── Version20200107081222.php
│   │   ├── Version20200107081238.php
│   │   ├── Version20200107081419.php
│   │   └── Version20200107081708.php
│   ├── Notification/
│   │   └── CommentReviewNotification.php
│   ├── Repository/
│   │   ├── .gitignore
│   │   ├── AdminRepository.php
│   │   ├── CommentRepository.php
│   │   └── ConferenceRepository.php
│   ├── Security/
│   │   └── AppAuthenticator.php
│   └── SpamChecker.php
├── templates/
│   ├── admin/
│   │   └── review.html.twig
│   ├── base.html.twig
│   ├── conference/
│   │   ├── header.html.twig
│   │   ├── index.html.twig
│   │   └── show.html.twig
│   ├── emails/
│   │   └── comment_notification.html.twig
│   └── security/
│       └── login.html.twig
├── tests/
│   ├── .gitignore
│   ├── Controller/
│   │   └── ConferenceControllerTest.php
│   └── SpamCheckerTest.php
├── translations/
│   ├── .gitignore
│   ├── messages+intl-icu.en.xlf
│   └── messages+intl-icu.fr.xlf
└── webpack.config.js
Download .txt
SYMBOL INDEX (158 symbols across 33 files)

FILE: spa/src/api/api.js
  function fetchCollection (line 1) | function fetchCollection(path) {
  function findConferences (line 5) | function findConferences() {
  function findComments (line 9) | function findComments(conference) {

FILE: spa/src/app.js
  function App (line 11) | function App() {

FILE: spa/src/pages/conference.js
  function Comment (line 5) | function Comment({comments}) {
  function Conference (line 34) | function Conference({conferences, slug}) {

FILE: spa/src/pages/home.js
  function Home (line 4) | function Home({conferences}) {

FILE: src/Api/FilterPublishedCommentQueryExtension.php
  class FilterPublishedCommentQueryExtension (line 11) | class FilterPublishedCommentQueryExtension implements QueryCollectionExt...
    method applyToCollection (line 13) | public function applyToCollection(QueryBuilder $qb, QueryNameGenerator...
    method applyToItem (line 20) | public function applyToItem(QueryBuilder $qb, QueryNameGeneratorInterf...

FILE: src/Command/CommentCleanupCommand.php
  class CommentCleanupCommand (line 12) | class CommentCleanupCommand extends Command
    method __construct (line 18) | public function __construct(CommentRepository $commentRepository)
    method configure (line 25) | protected function configure()
    method execute (line 33) | protected function execute(InputInterface $input, OutputInterface $out...

FILE: src/Command/StepInfoCommand.php
  class StepInfoCommand (line 11) | class StepInfoCommand extends Command
    method __construct (line 17) | public function __construct(CacheInterface $cache)
    method execute (line 24) | protected function execute(InputInterface $input, OutputInterface $out...

FILE: src/Controller/AdminController.php
  class AdminController (line 22) | class AdminController extends AbstractController
    method __construct (line 28) | public function __construct(Environment $twig, EntityManagerInterface ...
    method reviewComment (line 38) | public function reviewComment(Request $request, Comment $comment, Regi...
    method purgeHttpCache (line 68) | public function purgeHttpCache(KernelInterface $kernel, Request $reque...

FILE: src/Controller/ConferenceController.php
  class ConferenceController (line 23) | class ConferenceController extends AbstractController
    method __construct (line 29) | public function __construct(Environment $twig, EntityManagerInterface ...
    method indexNoLocale (line 39) | public function indexNoLocale()
    method index (line 47) | public function index(ConferenceRepository $conferenceRepository)
    method conferenceHeader (line 60) | public function conferenceHeader(ConferenceRepository $conferenceRepos...
    method show (line 73) | public function show(Request $request, Conference $conference, Comment...

FILE: src/Controller/SecurityController.php
  class SecurityController (line 10) | class SecurityController extends AbstractController
    method login (line 15) | public function login(AuthenticationUtils $authenticationUtils): Response
    method logout (line 32) | public function logout()

FILE: src/DataFixtures/AppFixtures.php
  class AppFixtures (line 12) | class AppFixtures extends Fixture
    method __construct (line 16) | public function __construct(EncoderFactoryInterface $encoderFactory)
    method load (line 21) | public function load(ObjectManager $manager)

FILE: src/Entity/Admin.php
  class Admin (line 11) | class Admin implements UserInterface
    method getId (line 36) | public function getId(): ?int
    method getUsername (line 46) | public function getUsername(): string
    method setUsername (line 51) | public function setUsername(string $username): self
    method getRoles (line 61) | public function getRoles(): array
    method setRoles (line 70) | public function setRoles(array $roles): self
    method __toString (line 77) | public function __toString(): string
    method getPassword (line 85) | public function getPassword(): string
    method setPassword (line 90) | public function setPassword(string $password): self
    method getSalt (line 100) | public function getSalt()
    method eraseCredentials (line 108) | public function eraseCredentials()

FILE: src/Entity/Comment.php
  class Comment (line 25) | class Comment
    method __toString (line 88) | public function __toString(): string
    method getId (line 93) | public function getId(): ?int
    method getAuthor (line 98) | public function getAuthor(): ?string
    method setAuthor (line 103) | public function setAuthor(string $author): self
    method getText (line 110) | public function getText(): ?string
    method setText (line 115) | public function setText(string $text): self
    method getEmail (line 122) | public function getEmail(): ?string
    method setEmail (line 127) | public function setEmail(string $email): self
    method getCreatedAt (line 134) | public function getCreatedAt(): ?\DateTimeInterface
    method setCreatedAt (line 139) | public function setCreatedAt(\DateTimeInterface $createdAt): self
    method setCreatedAtValue (line 149) | public function setCreatedAtValue()
    method getConference (line 154) | public function getConference(): ?Conference
    method setConference (line 159) | public function setConference(?Conference $conference): self
    method getPhotoFilename (line 166) | public function getPhotoFilename(): ?string
    method setPhotoFilename (line 171) | public function setPhotoFilename(?string $photoFilename): self
    method getState (line 178) | public function getState(): ?string
    method setState (line 183) | public function setState(string $state): self

FILE: src/Entity/Conference.php
  class Conference (line 24) | class Conference
    method __construct (line 68) | public function __construct()
    method __toString (line 73) | public function __toString(): string
    method getId (line 78) | public function getId(): ?int
    method computeSlug (line 83) | public function computeSlug(SluggerInterface $slugger)
    method getCity (line 90) | public function getCity(): ?string
    method setCity (line 95) | public function setCity(string $city): self
    method getYear (line 102) | public function getYear(): ?string
    method setYear (line 107) | public function setYear(string $year): self
    method getIsInternational (line 114) | public function getIsInternational(): ?bool
    method setIsInternational (line 119) | public function setIsInternational(bool $isInternational): self
    method getComments (line 129) | public function getComments(): Collection
    method addComment (line 134) | public function addComment(Comment $comment): self
    method removeComment (line 144) | public function removeComment(Comment $comment): self
    method getSlug (line 157) | public function getSlug(): ?string
    method setSlug (line 162) | public function setSlug(string $slug): self

FILE: src/EntityListener/ConferenceEntityListener.php
  class ConferenceEntityListener (line 9) | class ConferenceEntityListener
    method __construct (line 13) | public function __construct(SluggerInterface $slugger)
    method prePersist (line 18) | public function prePersist(Conference $conference, LifecycleEventArgs ...
    method preUpdate (line 23) | public function preUpdate(Conference $conference, LifecycleEventArgs $...

FILE: src/Form/CommentFormType.php
  class CommentFormType (line 14) | class CommentFormType extends AbstractType
    method buildForm (line 16) | public function buildForm(FormBuilderInterface $builder, array $options)
    method configureOptions (line 35) | public function configureOptions(OptionsResolver $resolver)

FILE: src/ImageOptimizer.php
  class ImageOptimizer (line 8) | class ImageOptimizer
    method __construct (line 15) | public function __construct()
    method resize (line 20) | public function resize(string $filename): void

FILE: src/Kernel.php
  class Kernel (line 12) | class Kernel extends BaseKernel
    method registerBundles (line 18) | public function registerBundles(): iterable
    method getProjectDir (line 28) | public function getProjectDir(): string
    method configureContainer (line 33) | protected function configureContainer(ContainerBuilder $container, Loa...
    method configureRoutes (line 46) | protected function configureRoutes(RouteCollectionBuilder $routes): void

FILE: src/Message/CommentMessage.php
  class CommentMessage (line 5) | class CommentMessage
    method __construct (line 11) | public function __construct(int $id, string $reviewUrl, array $context...
    method getReviewUrl (line 18) | public function getReviewUrl(): string
    method getId (line 23) | public function getId(): int
    method getContext (line 28) | public function getContext(): array

FILE: src/MessageHandler/CommentMessageHandler.php
  class CommentMessageHandler (line 17) | class CommentMessageHandler implements MessageHandlerInterface
    method __construct (line 29) | public function __construct(EntityManagerInterface $entityManager, Spa...
    method __invoke (line 42) | public function __invoke(CommentMessage $message)

FILE: src/Migrations/Version20200107080917.php
  class Version20200107080917 (line 13) | final class Version20200107080917 extends AbstractMigration
    method getDescription (line 15) | public function getDescription() : string
    method up (line 20) | public function up(Schema $schema) : void
    method down (line 33) | public function down(Schema $schema) : void

FILE: src/Migrations/Version20200107081222.php
  class Version20200107081222 (line 13) | final class Version20200107081222 extends AbstractMigration
    method getDescription (line 15) | public function getDescription() : string
    method up (line 20) | public function up(Schema $schema) : void
    method down (line 30) | public function down(Schema $schema) : void

FILE: src/Migrations/Version20200107081238.php
  class Version20200107081238 (line 13) | final class Version20200107081238 extends AbstractMigration
    method getDescription (line 15) | public function getDescription() : string
    method up (line 20) | public function up(Schema $schema) : void
    method down (line 28) | public function down(Schema $schema) : void

FILE: src/Migrations/Version20200107081419.php
  class Version20200107081419 (line 13) | final class Version20200107081419 extends AbstractMigration
    method getDescription (line 15) | public function getDescription() : string
    method up (line 20) | public function up(Schema $schema) : void
    method down (line 30) | public function down(Schema $schema) : void

FILE: src/Migrations/Version20200107081708.php
  class Version20200107081708 (line 13) | final class Version20200107081708 extends AbstractMigration
    method getDescription (line 15) | public function getDescription() : string
    method up (line 20) | public function up(Schema $schema) : void
    method down (line 30) | public function down(Schema $schema) : void

FILE: src/Notification/CommentReviewNotification.php
  class CommentReviewNotification (line 17) | class CommentReviewNotification extends Notification implements EmailNot...
    method __construct (line 22) | public function __construct(Comment $comment, string $reviewUrl)
    method asEmailMessage (line 30) | public function asEmailMessage(Recipient $recipient, string $transport...
    method asChatMessage (line 41) | public function asChatMessage(Recipient $recipient, string $transport ...
    method getChannels (line 67) | public function getChannels(Recipient $recipient): array

FILE: src/Repository/AdminRepository.php
  class AdminRepository (line 18) | class AdminRepository extends ServiceEntityRepository implements Passwor...
    method __construct (line 20) | public function __construct(ManagerRegistry $registry)
    method upgradePassword (line 28) | public function upgradePassword(UserInterface $user, string $newEncode...

FILE: src/Repository/CommentRepository.php
  class CommentRepository (line 18) | class CommentRepository extends ServiceEntityRepository
    method __construct (line 24) | public function __construct(ManagerRegistry $registry)
    method countOldRejected (line 29) | public function countOldRejected(): int
    method deleteOldRejected (line 34) | public function deleteOldRejected(): int
    method getOldRejectedQueryBuilder (line 39) | private function getOldRejectedQueryBuilder(): QueryBuilder
    method getCommentPaginator (line 52) | public function getCommentPaginator(Conference $conference, int $offse...

FILE: src/Repository/ConferenceRepository.php
  class ConferenceRepository (line 15) | class ConferenceRepository extends ServiceEntityRepository
    method __construct (line 17) | public function __construct(ManagerRegistry $registry)
    method findAll (line 22) | public function findAll()

FILE: src/Security/AppAuthenticator.php
  class AppAuthenticator (line 23) | class AppAuthenticator extends AbstractFormLoginAuthenticator implements...
    method __construct (line 32) | public function __construct(EntityManagerInterface $entityManager, Url...
    method supports (line 40) | public function supports(Request $request)
    method getCredentials (line 46) | public function getCredentials(Request $request)
    method getUser (line 61) | public function getUser($credentials, UserProviderInterface $userProvi...
    method checkCredentials (line 78) | public function checkCredentials($credentials, UserInterface $user)
    method getPassword (line 86) | public function getPassword($credentials): ?string
    method onAuthenticationSuccess (line 91) | public function onAuthenticationSuccess(Request $request, TokenInterfa...
    method getLoginUrl (line 100) | protected function getLoginUrl()

FILE: src/SpamChecker.php
  class SpamChecker (line 8) | class SpamChecker
    method __construct (line 13) | public function __construct(HttpClientInterface $client, string $akism...
    method getSpamScore (line 24) | public function getSpamScore(Comment $comment, array $context): int

FILE: tests/Controller/ConferenceControllerTest.php
  class ConferenceControllerTest (line 9) | class ConferenceControllerTest extends WebTestCase
    method testIndex (line 11) | public function testIndex()
    method testCommentSubmission (line 20) | public function testCommentSubmission()
    method testConferencePage (line 41) | public function testConferencePage()

FILE: tests/SpamCheckerTest.php
  class SpamCheckerTest (line 12) | class SpamCheckerTest extends TestCase
    method testSpamScoreWithInvalidRequest (line 14) | public function testSpamScoreWithInvalidRequest()
    method testSpamScore (line 31) | public function testSpamScore(int $expectedScore, ResponseInterface $r...
    method getComments (line 40) | public function getComments(): iterable
Condensed preview — 131 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (133K chars).
[
  {
    "path": ".blackfire.yaml",
    "chars": 2197,
    "preview": "scenarios: |\n    #!blackfire-player\n\n    group login\n        visit url('/login')\n        submit button(\"Sign in\")\n      "
  },
  {
    "path": ".gitignore",
    "chars": 452,
    "preview": "/public/uploads\n\n###> symfony/framework-bundle ###\n/.env.local\n/.env.local.php\n/.env.*.local\n/config/secrets/prod/prod.d"
  },
  {
    "path": ".symfony/config.vcl",
    "chars": 891,
    "preview": "acl profile {\n   # Authorize the local IP address (replace with the IP found above)\n   \"a.b.c.d\";\n   # Authorize Blackfi"
  },
  {
    "path": ".symfony/routes.yaml",
    "chars": 278,
    "preview": "\"https://spa.{all}/\": { type: upstream, upstream: \"spa:http\" }\n\"http://spa.{all}/\": { type: redirect, to: \"https://spa.{"
  },
  {
    "path": ".symfony/services.yaml",
    "chars": 376,
    "preview": "db:\n    type: postgresql:11\n    disk: 1024\n    size: S\n\nrediscache:\n    type: redis:5.0\n\nqueue:\n    type: rabbitmq:3.7\n "
  },
  {
    "path": ".symfony.cloud.yaml",
    "chars": 1308,
    "preview": "name: app\n\ntype: php:7.3\n\nruntime:\n    extensions:\n        - blackfire\n        - xsl\n        - amqp\n        - redis\n    "
  },
  {
    "path": "Makefile",
    "chars": 109,
    "preview": "SHELL := /bin/bash\n\ntests:\n\tsymfony console doctrine:fixtures:load -n\n\tsymfony run bin/phpunit\n.PHONY: tests\n"
  },
  {
    "path": "assets/css/_variables.scss",
    "chars": 2201,
    "preview": "\n// Colors\n\n$white: #fff;\n$gray-100: #f5f5f5;\n$gray-200: #e9ecef;\n$gray-300: #ddd;\n$gray-400: #ced4da;\n$gray-500: #adb5b"
  },
  {
    "path": "assets/css/app.scss",
    "chars": 1124,
    "preview": "@import './variables';\n@import '~bootstrap/scss/bootstrap';\n\nbody {\n    display: flex;\n    flex-direction: column;\n    h"
  },
  {
    "path": "assets/js/app.js",
    "chars": 394,
    "preview": "/*\n * Welcome to your app's main JavaScript file!\n *\n * We recommend including the built version of this JavaScript file"
  },
  {
    "path": "bin/console",
    "chars": 1216,
    "preview": "#!/usr/bin/env php\n<?php\n\nuse App\\Kernel;\nuse Symfony\\Bundle\\FrameworkBundle\\Console\\Application;\nuse Symfony\\Component\\"
  },
  {
    "path": "bin/phpunit",
    "chars": 426,
    "preview": "#!/usr/bin/env php\n<?php\n\nif (!file_exists(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {\n"
  },
  {
    "path": "composer.json",
    "chars": 2628,
    "preview": "{\n    \"type\": \"project\",\n    \"license\": \"proprietary\",\n    \"require\": {\n        \"php\": \"^7.2.5\",\n        \"ext-ctype\": \"*"
  },
  {
    "path": "config/bootstrap.php",
    "chars": 1174,
    "preview": "<?php\n\nuse Symfony\\Component\\Dotenv\\Dotenv;\n\nrequire dirname(__DIR__).'/vendor/autoload.php';\n\n// Load cached env vars i"
  },
  {
    "path": "config/bundles.php",
    "chars": 1394,
    "preview": "<?php\n\nreturn [\n    Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle::class => ['all' => true],\n    Symfony\\Bundle\\TwigBun"
  },
  {
    "path": "config/packages/api_platform.yaml",
    "chars": 179,
    "preview": "api_platform:\n    mapping:\n        paths: ['%kernel.project_dir%/src/Entity']\n    patch_formats:\n        json: ['applica"
  },
  {
    "path": "config/packages/assets.yaml",
    "chars": 101,
    "preview": "framework:\n    assets:\n        json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'\n"
  },
  {
    "path": "config/packages/cache.yaml",
    "chars": 687,
    "preview": "framework:\n    cache:\n        # Unique name of your app: used to compute stable namespaces for cache keys.\n        #pref"
  },
  {
    "path": "config/packages/dev/debug.yaml",
    "chars": 235,
    "preview": "debug:\n    # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser."
  },
  {
    "path": "config/packages/dev/easy_log_handler.yaml",
    "chars": 506,
    "preview": "services:\n    EasyCorp\\EasyLog\\EasyLogHandler:\n        public: false\n        arguments: ['%kernel.logs_dir%/%kernel.envi"
  },
  {
    "path": "config/packages/dev/monolog.yaml",
    "chars": 606,
    "preview": "monolog:\n    handlers:\n        main:\n            type: stream\n            path: \"%kernel.logs_dir%/%kernel.environment%."
  },
  {
    "path": "config/packages/dev/web_profiler.yaml",
    "chars": 116,
    "preview": "web_profiler:\n    toolbar: true\n    intercept_redirects: false\n\nframework:\n    profiler: { only_exceptions: false }\n"
  },
  {
    "path": "config/packages/doctrine.yaml",
    "chars": 601,
    "preview": "doctrine:\n    dbal:\n        url: '%env(resolve:DATABASE_URL)%'\n\n        # IMPORTANT: You MUST configure your server vers"
  },
  {
    "path": "config/packages/doctrine_migrations.yaml",
    "chars": 233,
    "preview": "doctrine_migrations:\n    dir_name: '%kernel.project_dir%/src/Migrations'\n    # namespace is arbitrary but should be diff"
  },
  {
    "path": "config/packages/easy_admin.yaml",
    "chars": 1223,
    "preview": "easy_admin:\n    site_name: Conference Guestbook\n\n    design:\n        menu:\n            - { route: 'homepage', label: 'Ba"
  },
  {
    "path": "config/packages/framework.yaml",
    "chars": 479,
    "preview": "framework:\n    secret: '%env(APP_SECRET)%'\n    #csrf_protection: true\n    #http_method_override: true\n\n    # Enables ses"
  },
  {
    "path": "config/packages/mailer.yaml",
    "chars": 150,
    "preview": "framework:\n    mailer:\n        dsn: '%env(MAILER_DSN)%'\n        envelope:\n            sender: \"%env(string:default:defau"
  },
  {
    "path": "config/packages/messenger.yaml",
    "chars": 894,
    "preview": "framework:\n    messenger:\n        # Uncomment this (and the failed transport below) to send failed messages to this tran"
  },
  {
    "path": "config/packages/nelmio_cors.yaml",
    "chars": 323,
    "preview": "nelmio_cors:\n    defaults:\n        origin_regex: true\n        allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']\n        allow_m"
  },
  {
    "path": "config/packages/notifier.yaml",
    "chars": 559,
    "preview": "framework:\n    notifier:\n        chatter_transports:\n            slack: '%env(SLACK_DSN)%'\n        #    telegram: '%env("
  },
  {
    "path": "config/packages/prod/doctrine.yaml",
    "chars": 545,
    "preview": "doctrine:\n    orm:\n        auto_generate_proxy_classes: false\n        metadata_cache_driver:\n            type: pool\n    "
  },
  {
    "path": "config/packages/prod/monolog.yaml",
    "chars": 701,
    "preview": "monolog:\n    handlers:\n        main:\n            type: fingers_crossed\n            action_level: error\n            handl"
  },
  {
    "path": "config/packages/prod/routing.yaml",
    "chars": 57,
    "preview": "framework:\n    router:\n        strict_requirements: null\n"
  },
  {
    "path": "config/packages/prod/webpack_encore.yaml",
    "chars": 154,
    "preview": "#webpack_encore:\n    # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)\n    # Availabl"
  },
  {
    "path": "config/packages/routing.yaml",
    "chars": 42,
    "preview": "framework:\n    router:\n        utf8: true\n"
  },
  {
    "path": "config/packages/security.yaml",
    "chars": 1291,
    "preview": "security:\n    encoders:\n        App\\Entity\\Admin:\n            algorithm: auto\n\n    # https://symfony.com/doc/current/sec"
  },
  {
    "path": "config/packages/sensio_framework_extra.yaml",
    "chars": 63,
    "preview": "sensio_framework_extra:\n    router:\n        annotations: false\n"
  },
  {
    "path": "config/packages/test/dama_doctrine_test_bundle.yaml",
    "chars": 131,
    "preview": "dama_doctrine_test:\n    enable_static_connection: true\n    enable_static_meta_data_cache: true\n    enable_static_query_c"
  },
  {
    "path": "config/packages/test/framework.yaml",
    "chars": 85,
    "preview": "framework:\n    test: true\n    session:\n        storage_id: session.storage.mock_file\n"
  },
  {
    "path": "config/packages/test/monolog.yaml",
    "chars": 183,
    "preview": "monolog:\n    handlers:\n        main:\n            type: stream\n            path: \"%kernel.logs_dir%/%kernel.environment%."
  },
  {
    "path": "config/packages/test/twig.yaml",
    "chars": 33,
    "preview": "twig:\n    strict_variables: true\n"
  },
  {
    "path": "config/packages/test/validator.yaml",
    "chars": 67,
    "preview": "framework:\n    validation:\n        not_compromised_password: false\n"
  },
  {
    "path": "config/packages/test/web_profiler.yaml",
    "chars": 109,
    "preview": "web_profiler:\n    toolbar: false\n    intercept_redirects: false\n\nframework:\n    profiler: { collect: false }\n"
  },
  {
    "path": "config/packages/test/webpack_encore.yaml",
    "chars": 41,
    "preview": "#webpack_encore:\n#    strict_mode: false\n"
  },
  {
    "path": "config/packages/translation.yaml",
    "chars": 144,
    "preview": "framework:\n    default_locale: en\n    translator:\n        default_path: '%kernel.project_dir%/translations'\n        fall"
  },
  {
    "path": "config/packages/twig.yaml",
    "chars": 56,
    "preview": "twig:\n    form_themes: ['bootstrap_4_layout.html.twig']\n"
  },
  {
    "path": "config/packages/validator.yaml",
    "chars": 263,
    "preview": "framework:\n    validation:\n        email_validation_mode: html5\n\n        # Enables validator auto-mapping support.\n     "
  },
  {
    "path": "config/packages/webpack_encore.yaml",
    "chars": 1083,
    "preview": "webpack_encore:\n    # The path where Encore is building the assets - i.e. Encore.setOutputPath()\n    output_path: '%kern"
  },
  {
    "path": "config/packages/workflow.yaml",
    "chars": 1332,
    "preview": "framework:\n    workflows:\n        comment:\n            type: state_machine\n            audit_trail:\n                enab"
  },
  {
    "path": "config/routes/annotations.yaml",
    "chars": 70,
    "preview": "controllers:\n    resource: ../../src/Controller/\n    type: annotation\n"
  },
  {
    "path": "config/routes/api_platform.yaml",
    "chars": 70,
    "preview": "api_platform:\n    resource: .\n    type: api_platform\n    prefix: /api\n"
  },
  {
    "path": "config/routes/dev/framework.yaml",
    "chars": 98,
    "preview": "_errors:\n    resource: '@FrameworkBundle/Resources/config/routing/errors.xml'\n    prefix: /_error\n"
  },
  {
    "path": "config/routes/dev/web_profiler.yaml",
    "chars": 224,
    "preview": "web_profiler_wdt:\n    resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'\n    prefix: /_wdt\n\nweb_profiler_pr"
  },
  {
    "path": "config/routes/easy_admin.yaml",
    "chars": 127,
    "preview": "easy_admin_bundle:\n    resource: '@EasyAdminBundle/Controller/EasyAdminController.php'\n    prefix: /admin\n    type: anno"
  },
  {
    "path": "config/routes.yaml",
    "chars": 78,
    "preview": "#index:\n#    path: /\n#    controller: App\\Controller\\DefaultController::index\n"
  },
  {
    "path": "config/secrets/dev/dev.AKISMET_KEY.ca01fb.php",
    "chars": 279,
    "preview": "<?php // dev.AKISMET_KEY.ca01fb on Tue, 07 Jan 2020 09:15:06 +0100\n\nreturn \"\\x9B\\xAA\\xDB\\xF7\\x9E\\x1C\\x8D\\x9F\\x7B\\xDBm8d\\"
  },
  {
    "path": "config/secrets/dev/dev.SLACK_DSN.b2b579.php",
    "chars": 388,
    "preview": "<?php // dev.SLACK_DSN.b2b579 on Tue, 07 Jan 2020 09:22:47 +0100\n\nreturn \"\\x97\\x10E\\x88\\x0Fc\\xCA\\x850\\x9F\\xB6\\x18Q\\x1E\\x"
  },
  {
    "path": "config/secrets/dev/dev.decrypt.private.php",
    "chars": 293,
    "preview": "<?php // dev.decrypt.private on Tue, 07 Jan 2020 09:15:06 +0100\n\nreturn \"z7\\x80\\x9E\\x0E\\x22\\xD2\\xBE-\\xD9\\x3Co\\x08B\\x0D\\x"
  },
  {
    "path": "config/secrets/dev/dev.encrypt.public.php",
    "chars": 185,
    "preview": "<?php // dev.encrypt.public on Tue, 07 Jan 2020 09:15:06 +0100\n\nreturn \"\\x15\\x2A\\x18\\xCC\\x11\\x04\\x60\\x16\\x8D\\x26\\xC1\\xA2"
  },
  {
    "path": "config/secrets/dev/dev.list.php",
    "chars": 73,
    "preview": "<?php\n\nreturn array (\n  'AKISMET_KEY' => NULL,\n  'SLACK_DSN' => NULL,\n);\n"
  },
  {
    "path": "config/secrets/prod/prod.AKISMET_KEY.ca01fb.php",
    "chars": 269,
    "preview": "<?php // prod.AKISMET_KEY.ca01fb on Tue, 07 Jan 2020 09:15:07 +0100\n\nreturn \"\\x89\\x8E\\x7C\\x94\\xE7\\x90\\xDE77\\xEB\\xFB\\xE0\\"
  },
  {
    "path": "config/secrets/prod/prod.SLACK_DSN.b2b579.php",
    "chars": 353,
    "preview": "<?php // prod.SLACK_DSN.b2b579 on Tue, 07 Jan 2020 09:22:47 +0100\n\nreturn \"\\x0D_U\\xDBV\\xC4\\xF7\\x0Az\\xB2\\xBC\\xDB\\x5BXMd\\x"
  },
  {
    "path": "config/secrets/prod/prod.encrypt.public.php",
    "chars": 177,
    "preview": "<?php // prod.encrypt.public on Tue, 07 Jan 2020 09:15:06 +0100\n\nreturn \"\\xA7\\xFDO\\xCB\\xA8Y\\x85\\xCE\\xA8E\\xCBT\\xAEK\\xEF\\x"
  },
  {
    "path": "config/secrets/prod/prod.list.php",
    "chars": 73,
    "preview": "<?php\n\nreturn array (\n  'AKISMET_KEY' => NULL,\n  'SLACK_DSN' => NULL,\n);\n"
  },
  {
    "path": "config/secrets/test/test.AKISMET_KEY.ca01fb.php",
    "chars": 280,
    "preview": "<?php // test.AKISMET_KEY.ca01fb on Tue, 07 Jan 2020 09:15:56 +0100\n\nreturn \"\\xE1\\x85\\x91\\x878\\xAD\\xBAu\\x84\\x7F\\xC8\\xAA2"
  },
  {
    "path": "config/secrets/test/test.decrypt.private.php",
    "chars": 282,
    "preview": "<?php // test.decrypt.private on Tue, 07 Jan 2020 09:15:56 +0100\n\nreturn \"\\x8DV1\\x26\\x29o\\xD6\\xF1\\xA8\\xF0\\x3Dz\\xE8\\xB1\\x"
  },
  {
    "path": "config/secrets/test/test.encrypt.public.php",
    "chars": 177,
    "preview": "<?php // test.encrypt.public on Tue, 07 Jan 2020 09:15:56 +0100\n\nreturn \"\\x80\\xD2\\xF5\\x1A7j\\xE4w\\xDF\\x7Bw\\xA4\\x60\\x5CUO\\"
  },
  {
    "path": "config/secrets/test/test.list.php",
    "chars": 50,
    "preview": "<?php\n\nreturn array (\n  'AKISMET_KEY' => NULL,\n);\n"
  },
  {
    "path": "config/services.yaml",
    "chars": 2093,
    "preview": "# This file is the entry point to configure your own services.\n# Files in the packages/ subdirectory configure your depe"
  },
  {
    "path": "docker-compose.yaml",
    "chars": 546,
    "preview": "version: '3'\n\nservices:\n    database:\n        image: postgres:11-alpine\n        environment:\n            POSTGRES_USER: "
  },
  {
    "path": "package.json",
    "chars": 620,
    "preview": "{\n    \"devDependencies\": {\n        \"@symfony/webpack-encore\": \"^0.28.2\",\n        \"bootstrap\": \"^4.4.1\",\n        \"bs-cust"
  },
  {
    "path": "php.ini",
    "chars": 254,
    "preview": "allow_url_include=off\nassert.active=off\ndisplay_errors=off\ndisplay_startup_errors=off\nmax_execution_time=30\nsession.use_"
  },
  {
    "path": "phpunit.xml.dist",
    "chars": 1125,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->\n<phpunit xm"
  },
  {
    "path": "public/index.php",
    "chars": 946,
    "preview": "<?php\n\nuse App\\Kernel;\nuse Symfony\\Bundle\\FrameworkBundle\\HttpCache\\HttpCache;\nuse Symfony\\Component\\ErrorHandler\\Debug;"
  },
  {
    "path": "spa/.gitignore",
    "chars": 67,
    "preview": "/node_modules\n/public\n/yarn-error.log\n# used later by Cordova\n/app\n"
  },
  {
    "path": "spa/.symfony.cloud.yaml",
    "chars": 499,
    "preview": "name: spa\n\ntype: php:7.3\nsize: S\ndisk: 256\n\nbuild:\n    flavor: none\n\ndependencies:\n    nodejs:\n        yarn: \"*\"\n\nweb:\n "
  },
  {
    "path": "spa/assets/css/_variables.scss",
    "chars": 2201,
    "preview": "\n// Colors\n\n$white: #fff;\n$gray-100: #f5f5f5;\n$gray-200: #e9ecef;\n$gray-300: #ddd;\n$gray-400: #ced4da;\n$gray-500: #adb5b"
  },
  {
    "path": "spa/assets/css/app.scss",
    "chars": 1124,
    "preview": "@import './variables';\n@import '~bootstrap/scss/bootstrap';\n\nbody {\n    display: flex;\n    flex-direction: column;\n    h"
  },
  {
    "path": "spa/package.json",
    "chars": 426,
    "preview": "{\n  \"name\": \"spa\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@babel/core\""
  },
  {
    "path": "spa/src/api/api.js",
    "chars": 341,
    "preview": "function fetchCollection(path) {\n    return fetch(ENV_API_ENDPOINT + path).then(resp => resp.json()).then(json => json['"
  },
  {
    "path": "spa/src/app.js",
    "chars": 1608,
    "preview": "import '../assets/css/app.scss';\n\nimport {h, render} from 'preact';\nimport {Router, Link} from 'preact-router';\nimport {"
  },
  {
    "path": "spa/src/index.ejs",
    "chars": 461,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n    <meta http-e"
  },
  {
    "path": "spa/src/pages/conference.js",
    "chars": 1637,
    "preview": "import {h} from 'preact';\nimport {findComments} from '../api/api';\nimport {useState, useEffect} from 'preact/hooks';\n\nfu"
  },
  {
    "path": "spa/src/pages/home.js",
    "chars": 938,
    "preview": "import {h} from 'preact';\nimport {Link} from 'preact-router';\n\nexport default function Home({conferences}) {\n    if (!co"
  },
  {
    "path": "spa/webpack.config.js",
    "chars": 619,
    "preview": "const webpack = require('webpack');\nconst Encore = require('@symfony/webpack-encore');\nconst HtmlWebpackPlugin = require"
  },
  {
    "path": "src/Api/FilterPublishedCommentQueryExtension.php",
    "chars": 1115,
    "preview": "<?php\n\nnamespace App\\Api;\n\nuse ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Extension\\QueryCollectionExtensionInterface;\nuse Api"
  },
  {
    "path": "src/Command/CommentCleanupCommand.php",
    "chars": 1366,
    "preview": "<?php\n\nnamespace App\\Command;\n\nuse App\\Repository\\CommentRepository;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse "
  },
  {
    "path": "src/Command/StepInfoCommand.php",
    "chars": 943,
    "preview": "<?php\n\nnamespace App\\Command;\n\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputI"
  },
  {
    "path": "src/Controller/.gitignore",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/Controller/AdminController.php",
    "chars": 2579,
    "preview": "<?php\n\nnamespace App\\Controller;\n\nuse App\\Entity\\Comment;\nuse App\\Message\\CommentMessage;\nuse Doctrine\\ORM\\EntityManager"
  },
  {
    "path": "src/Controller/ConferenceController.php",
    "chars": 4624,
    "preview": "<?php\n\nnamespace App\\Controller;\n\nuse App\\Entity\\Comment;\nuse App\\Entity\\Conference;\nuse App\\Form\\CommentFormType;\nuse A"
  },
  {
    "path": "src/Controller/SecurityController.php",
    "chars": 1142,
    "preview": "<?php\n\nnamespace App\\Controller;\n\nuse Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;\nuse Symfony\\Componen"
  },
  {
    "path": "src/DataFixtures/AppFixtures.php",
    "chars": 1797,
    "preview": "<?php\n\nnamespace App\\DataFixtures;\n\nuse App\\Entity\\Admin;\nuse App\\Entity\\Comment;\nuse App\\Entity\\Conference;\nuse Doctrin"
  },
  {
    "path": "src/Entity/.gitignore",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/Entity/Admin.php",
    "chars": 2126,
    "preview": "<?php\n\nnamespace App\\Entity;\n\nuse Doctrine\\ORM\\Mapping as ORM;\nuse Symfony\\Component\\Security\\Core\\User\\UserInterface;\n\n"
  },
  {
    "path": "src/Entity/Comment.php",
    "chars": 3930,
    "preview": "<?php\n\nnamespace App\\Entity;\n\nuse ApiPlatform\\Core\\Annotation\\ApiFilter;\nuse ApiPlatform\\Core\\Annotation\\ApiResource;\nus"
  },
  {
    "path": "src/Entity/Conference.php",
    "chars": 3763,
    "preview": "<?php\n\nnamespace App\\Entity;\n\nuse ApiPlatform\\Core\\Annotation\\ApiResource;\nuse Doctrine\\Common\\Collections\\ArrayCollecti"
  },
  {
    "path": "src/EntityListener/ConferenceEntityListener.php",
    "chars": 617,
    "preview": "<?php\n\nnamespace App\\EntityListener;\n\nuse App\\Entity\\Conference;\nuse Doctrine\\ORM\\Event\\LifecycleEventArgs;\nuse Symfony\\"
  },
  {
    "path": "src/Form/CommentFormType.php",
    "chars": 1202,
    "preview": "<?php\n\nnamespace App\\Form;\n\nuse App\\Entity\\Comment;\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\Form\\"
  },
  {
    "path": "src/ImageOptimizer.php",
    "chars": 771,
    "preview": "<?php\n\nnamespace App;\n\nuse Imagine\\Gd\\Imagine;\nuse Imagine\\Image\\Box;\n\nclass ImageOptimizer\n{\n    private const MAX_WIDT"
  },
  {
    "path": "src/Kernel.php",
    "chars": 2083,
    "preview": "<?php\n\nnamespace App;\n\nuse Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait;\nuse Symfony\\Component\\Config\\Loader\\L"
  },
  {
    "path": "src/Message/CommentMessage.php",
    "chars": 557,
    "preview": "<?php\n\nnamespace App\\Message;\n\nclass CommentMessage\n{\n    private $id;\n    private $reviewUrl;\n    private $context;\n\n  "
  },
  {
    "path": "src/MessageHandler/CommentMessageHandler.php",
    "chars": 2976,
    "preview": "<?php\n\nnamespace App\\MessageHandler;\n\nuse App\\ImageOptimizer;\nuse App\\Message\\CommentMessage;\nuse App\\Notification\\Comme"
  },
  {
    "path": "src/Migrations/.gitignore",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/Migrations/Version20200107080917.php",
    "chars": 2193,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "src/Migrations/Version20200107081222.php",
    "chars": 1283,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "src/Migrations/Version20200107081238.php",
    "chars": 1137,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "src/Migrations/Version20200107081419.php",
    "chars": 1435,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "src/Migrations/Version20200107081708.php",
    "chars": 1256,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "src/Notification/CommentReviewNotification.php",
    "chars": 2766,
    "preview": "<?php\n\nnamespace App\\Notification;\n\nuse App\\Entity\\Comment;\nuse Symfony\\Component\\Notifier\\Bridge\\Slack\\Block\\SlackActio"
  },
  {
    "path": "src/Repository/.gitignore",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/Repository/AdminRepository.php",
    "chars": 2062,
    "preview": "<?php\n\nnamespace App\\Repository;\n\nuse App\\Entity\\Admin;\nuse Doctrine\\Bundle\\DoctrineBundle\\Repository\\ServiceEntityRepos"
  },
  {
    "path": "src/Repository/CommentRepository.php",
    "chars": 2917,
    "preview": "<?php\n\nnamespace App\\Repository;\n\nuse App\\Entity\\Comment;\nuse App\\Entity\\Conference;\nuse Doctrine\\Bundle\\DoctrineBundle\\"
  },
  {
    "path": "src/Repository/ConferenceRepository.php",
    "chars": 1516,
    "preview": "<?php\n\nnamespace App\\Repository;\n\nuse App\\Entity\\Conference;\nuse Doctrine\\Bundle\\DoctrineBundle\\Repository\\ServiceEntity"
  },
  {
    "path": "src/Security/AppAuthenticator.php",
    "chars": 3778,
    "preview": "<?php\n\nnamespace App\\Security;\n\nuse App\\Entity\\Admin;\nuse Doctrine\\ORM\\EntityManagerInterface;\nuse Symfony\\Component\\Htt"
  },
  {
    "path": "src/SpamChecker.php",
    "chars": 1694,
    "preview": "<?php\n\nnamespace App;\n\nuse App\\Entity\\Comment;\nuse Symfony\\Contracts\\HttpClient\\HttpClientInterface;\n\nclass SpamChecker\n"
  },
  {
    "path": "templates/admin/review.html.twig",
    "chars": 231,
    "preview": "{% extends 'base.html.twig' %}\n\n{% block body %}\n    <h2>Comment reviewed, thank you!</h2>\n\n    <p>Applied transition: <"
  },
  {
    "path": "templates/base.html.twig",
    "chars": 2999,
    "preview": "<!doctype html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"utf-8\">\n        <meta name=\"viewport\" content=\"width="
  },
  {
    "path": "templates/conference/header.html.twig",
    "chars": 173,
    "preview": "{% for conference in conferences %}\n    <a class=\"nav-conference\" href=\"{{ path('conference', { slug: conference.slug })"
  },
  {
    "path": "templates/conference/index.html.twig",
    "chars": 1096,
    "preview": "{% extends 'base.html.twig' %}\n\n{% block title %}Conference Guestbook{% endblock %}\n\n{% block body %}\n    <h2 class=\"mb-"
  },
  {
    "path": "templates/conference/show.html.twig",
    "chars": 2856,
    "preview": "{% extends 'base.html.twig' %}\n\n{% block title %}Conference Guestbook - {{ conference }}{% endblock %}\n\n{% block body %}"
  },
  {
    "path": "templates/emails/comment_notification.html.twig",
    "chars": 500,
    "preview": "{% extends '@email/default/notification/body.html.twig' %}\n\n{% block content %}\n    Author: {{ comment.author }}<br />\n "
  },
  {
    "path": "templates/security/login.html.twig",
    "chars": 1400,
    "preview": "{% extends 'base.html.twig' %}\n\n{% block title %}Log in!{% endblock %}\n\n{% block body %}\n<form method=\"post\">\n    {% if "
  },
  {
    "path": "tests/.gitignore",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/Controller/ConferenceControllerTest.php",
    "chars": 1868,
    "preview": "<?php\n\nnamespace App\\Tests\\Controller;\n\nuse App\\Repository\\CommentRepository;\nuse Doctrine\\ORM\\EntityManagerInterface;\nu"
  },
  {
    "path": "tests/SpamCheckerTest.php",
    "chars": 1789,
    "preview": "<?php\n\nnamespace App\\Tests;\n\nuse App\\Entity\\Comment;\nuse App\\SpamChecker;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Co"
  },
  {
    "path": "translations/.gitignore",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "translations/messages+intl-icu.en.xlf",
    "chars": 565,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xliff xmlns=\"urn:oasis:names:tc:xliff:document:1.2\" version=\"1.2\">\n  <file sourc"
  },
  {
    "path": "translations/messages+intl-icu.fr.xlf",
    "chars": 1036,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xliff xmlns=\"urn:oasis:names:tc:xliff:document:1.2\" version=\"1.2\">\n  <file sourc"
  },
  {
    "path": "webpack.config.js",
    "chars": 2531,
    "preview": "var Encore = require('@symfony/webpack-encore');\n\n// Manually configure the runtime environment if not already configure"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the the-fast-track/book-5.0-1 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 131 files (11.5 MB), approximately 33.2k tokens, and a symbol index with 158 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!