From 4f47b83c966ff22e9a575504dc8f944ee5b11a9b Mon Sep 17 00:00:00 2001 From: Boyquotes Date: Mon, 27 Jul 2020 22:00:59 +0200 Subject: [PATCH] Send mail with mailgun --- .env | 17 +- composer.json | 2 + composer.lock | 188 ++++++++++++++++- config/packages/dev/mailer.yaml | 2 +- data/database.sqlite | Bin 274432 -> 274432 bytes migrations/Version20200727162131.php | 104 ---------- migrations/Version20200727191337.php | 55 +++++ src/Controller/Admin/GardenController.php | 193 ++++++++++++++++++ src/Controller/RegistrationController.php | 45 ++++ src/Entity/User.php | 2 + src/Form/GardenType.php | 105 ++++++++++ src/Form/RegistrationFormType.php | 57 ++++++ src/Repository/GardenRepository.php | 39 ++++ src/Security/GardenVoter.php | 61 ++++++ symfony.lock | 15 ++ templates/admin/garden/_delete_form.html.twig | 8 + templates/admin/garden/_form.html.twig | 26 +++ templates/admin/garden/edit.html.twig | 29 +++ templates/admin/garden/index.html.twig | 23 +++ templates/admin/garden/new.html.twig | 31 +++ templates/admin/garden/show.html.twig | 36 ++++ templates/admin/layout.html.twig | 5 + templates/garden/_comment_form.html.twig | 40 ++++ .../_delete_garden_confirmation.html.twig | 19 ++ templates/garden/_garden_tags.html.twig | 12 ++ templates/garden/_rss.html.twig | 5 + templates/garden/about.html.twig | 15 ++ templates/garden/comment_form_error.html.twig | 11 + templates/garden/garden_show.html.twig | 77 +++++++ templates/garden/index.html.twig | 57 ++++++ templates/garden/index.xml.twig | 25 +++ templates/garden/search.html.twig | 31 +++ templates/registration/register.html.twig | 20 ++ 33 files changed, 1247 insertions(+), 108 deletions(-) delete mode 100644 migrations/Version20200727162131.php create mode 100644 migrations/Version20200727191337.php create mode 100644 src/Controller/Admin/GardenController.php create mode 100644 src/Controller/RegistrationController.php create mode 100644 src/Form/GardenType.php create mode 100644 src/Form/RegistrationFormType.php create mode 100644 src/Repository/GardenRepository.php create mode 100644 src/Security/GardenVoter.php create mode 100644 templates/admin/garden/_delete_form.html.twig create mode 100644 templates/admin/garden/_form.html.twig create mode 100644 templates/admin/garden/edit.html.twig create mode 100644 templates/admin/garden/index.html.twig create mode 100644 templates/admin/garden/new.html.twig create mode 100644 templates/admin/garden/show.html.twig create mode 100644 templates/garden/_comment_form.html.twig create mode 100644 templates/garden/_delete_garden_confirmation.html.twig create mode 100644 templates/garden/_garden_tags.html.twig create mode 100644 templates/garden/_rss.html.twig create mode 100644 templates/garden/about.html.twig create mode 100644 templates/garden/comment_form_error.html.twig create mode 100644 templates/garden/garden_show.html.twig create mode 100644 templates/garden/index.html.twig create mode 100644 templates/garden/index.xml.twig create mode 100644 templates/garden/search.html.twig create mode 100644 templates/registration/register.html.twig diff --git a/.env b/.env index f2346c62..4bd0fe2e 100644 --- a/.env +++ b/.env @@ -25,13 +25,26 @@ APP_SECRET=2ca64f8d83b9e89f5f19d672841d6bb8 # For a MySQL database, use: "mysql://db_user:db_password@127.0.0.1:3306/db_name" # For a PostgreSQL database, use: "postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=11&charset=utf8" # IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml -DATABASE_URL=sqlite:///%kernel.project_dir%/data/database.sqlite +#DATABASE_URL=sqlite:///%kernel.project_dir%/data/database.sqlite +DATABASE_URL=mysql://root:live@127.0.0.1:3306/ge3 ###< doctrine/doctrine-bundle ### ###> symfony/mailer ### -# MAILER_DSN=smtp://localhost +#MAILGUN_DOMAIN=sandbox3a45137aa4b14102a862356eb2281755.mailgun.org +#MAILGUN_KEY=0277360e4a8c28b83092fdc0920c98e0-73ae490d-f4d427fd +#MAILER_DSN="mailgun+api://$MAILGUN_KEY:$MAILGUN_DOMAIN@default" ###< symfony/mailer ### ###> nelmio/cors-bundle ### CORS_ALLOW_ORIGIN=^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$ ###< nelmio/cors-bundle ### + +###> symfony/mailgun-mailer ### +# MAILER_DSN=mailgun://KEY:DOMAIN@default?region=us +# MAILER_DSN=mailgun+smtp://USERNAME:PASSWORD@default?region=us + +MAILGUN_DOMAIN=sandbox3a45137aa4b14102a862356eb2281755.mailgun.org +MAILGUN_KEY=0277360e4a8c28b83092fdc0920c98e0-73ae490d-f4d427fd +#MAILER_DSN="mailgun+api://$MAILGUN_KEY:$MAILGUN_DOMAIN@default" +MAILER_DSN=mailgun://$MAILGUN_KEY:$MAILGUN_DOMAIN@default?region=us +###< symfony/mailgun-mailer ### diff --git a/composer.json b/composer.json index 5dbfb51f..9e708383 100644 --- a/composer.json +++ b/composer.json @@ -24,8 +24,10 @@ "symfony/flex": "^1.1", "symfony/form": "^5.1", "symfony/framework-bundle": "^5.1", + "symfony/http-client": "5.1.*", "symfony/intl": "^5.1", "symfony/mailer": "^5.1", + "symfony/mailgun-mailer": "5.1.*", "symfony/monolog-bundle": "^3.1", "symfony/polyfill-intl-messageformatter": "^1.12", "symfony/security-bundle": "^5.1", diff --git a/composer.lock b/composer.lock index 92ce6d3a..a8a80454 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dfcc38e119c1809b350c5b9240d5d89b", + "content-hash": "b9daa81dc36c32b7a0aef93ade348bb9", "packages": [ { "name": "api-platform/api-pack", @@ -3657,6 +3657,139 @@ "homepage": "https://symfony.com", "time": "2020-05-25T12:33:44+00:00" }, + { + "name": "symfony/http-client", + "version": "v5.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "050dc633a598bdadbd49449500c87e30dabe5c58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/050dc633a598bdadbd49449500c87e30dabe5c58", + "reference": "050dc633a598bdadbd49449500c87e30dabe5c58", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/log": "^1.0", + "symfony/http-client-contracts": "^2.1.1", + "symfony/polyfill-php73": "^1.11", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.0|^2" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "1.1" + }, + "require-dev": { + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.3.1", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/http-kernel": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpClient component", + "homepage": "https://symfony.com", + "time": "2020-07-06T13:23:11+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v2.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "cd88921e9add61f2064c9c6b30de4f589db42962" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/cd88921e9add61f2064c9c6b30de4f589db42962", + "reference": "cd88921e9add61f2064c9c6b30de4f589db42962", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/http-client-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2020-07-06T13:23:11+00:00" + }, { "name": "symfony/http-foundation", "version": "v5.1.0", @@ -4020,6 +4153,59 @@ "homepage": "https://symfony.com", "time": "2020-05-30T20:35:19+00:00" }, + { + "name": "symfony/mailgun-mailer", + "version": "v5.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailgun-mailer.git", + "reference": "1cb33e34d75eb8563dcedf8919968f3476a9c628" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/1cb33e34d75eb8563dcedf8919968f3476a9c628", + "reference": "1cb33e34d75eb8563dcedf8919968f3476a9c628", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/mailer": "^5.1" + }, + "require-dev": { + "symfony/http-client": "^4.4|^5.0" + }, + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\Bridge\\Mailgun\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Mailgun Mailer Bridge", + "homepage": "https://symfony.com", + "time": "2020-05-30T20:35:19+00:00" + }, { "name": "symfony/mime", "version": "v5.1.0", diff --git a/config/packages/dev/mailer.yaml b/config/packages/dev/mailer.yaml index 8f1eb0ff..09e35709 100644 --- a/config/packages/dev/mailer.yaml +++ b/config/packages/dev/mailer.yaml @@ -1,4 +1,4 @@ framework: mailer: # this disables delivery of messages entirely - dsn: 'null://null' + # dsn: 'null://null' diff --git a/data/database.sqlite b/data/database.sqlite index 636254d0f608edbf93a348601bf8a299fa0cb66b..436027ae7fb1dd87f573e571caf7abd5eb6bc3ab 100644 GIT binary patch delta 996 zcmb7@%S#(k6o+Rr(TQ#1RZFlE#b5#=5j9VfN-!~#TFuM6G{#Dr$7GyjGLz0s65}R@ z3U*QG3kq#Py6Db@v{^_u?V{UOx@op`(Yh7-7j!0#MU+B&;2sVfeuwWo?)Gud_HoZ6 z6|8zZ=z>*m?!5lchsyULumi4kKk3dWzAA3W_Zs@1`|?_S0F^=g{q}lbjK?|N(sDGJ zW~d}*aTFMuFX9wMrdWI;%W9J>!O#XWqb=L@Hf_OfF`JE+ao%nkFWU9mU@2F1radXI z#gy9ENL8xd9IJO~1GLZRx`k7bOe7Lq&V@X#z*eA0%x@W!Q#DUE&6d2yrJ@ciPR>uQ zjf4VTcfyJJ7W^aY_3NE&xKWYI+;VL=^MAq>HaY7uJI5GJ6@R3xGwG|76+@+_Wrl#liK8SmHCa z(G35NR%;C|pYjC=ZfPZr1#JtQ91($Wvy1KSLE=3`waz^=wrVVqEMFd@2uOKpLXn4BbcyW}2`%o|offMiv zRKaa93!Z_u!f}j&+8|CInns5TnK>(akxn{*Ov5}=NF*}_^6C3; zHwCr>2>b-!!DsLhyaNZI+35Z99E^4${jk`J1wD(*ooA7$Lxw0@S?Rsz>2C8BK5U*! zCbne~umcHYc7*meLCr4df;2Y8HQ5R49%2CSR33@76KBdq1^{wT%BULw8PzKi5$ppu>yh= zkQD4g;0HJYhhSgm>Lt)zks<@IP%4Ad5+#C6!d$hGW$0=mgBO@YiNjg+@aQ38c=Zc* CfIe3M delta 151 zcmZp8AkgqYV1hKG=tLQ3M$wH4OZIay^G#ylZ{g?Wo3z z6AMu3b_V{Z{O9<$g9KOdi;FTd>oXQt=BDN6RmP{J=H|zj7N-_5Z$H=1Y_tFXGtf7~ diff --git a/migrations/Version20200727162131.php b/migrations/Version20200727162131.php deleted file mode 100644 index 93aa8d1c..00000000 --- a/migrations/Version20200727162131.php +++ /dev/null @@ -1,104 +0,0 @@ -abortIf($this->connection->getDatabasePlatform()->getName() !== 'sqlite', 'Migration can only be executed safely on \'sqlite\'.'); - - $this->addSql('CREATE TABLE garden (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, title VARCHAR(255) NOT NULL, description CLOB NOT NULL, address VARCHAR(255) NOT NULL, postcode INTEGER NOT NULL, town VARCHAR(255) NOT NULL, lat DOUBLE PRECISION NOT NULL, lng DOUBLE PRECISION NOT NULL)'); - $this->addSql('DROP INDEX IDX_53AD8F83F675F31B'); - $this->addSql('DROP INDEX IDX_53AD8F834B89032C'); - $this->addSql('CREATE TEMPORARY TABLE __temp__symfony_demo_comment AS SELECT id, post_id, author_id, content, published_at FROM symfony_demo_comment'); - $this->addSql('DROP TABLE symfony_demo_comment'); - $this->addSql('CREATE TABLE symfony_demo_comment (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, post_id INTEGER NOT NULL, author_id INTEGER NOT NULL, content CLOB NOT NULL COLLATE BINARY, published_at DATETIME NOT NULL, CONSTRAINT FK_53AD8F834B89032C FOREIGN KEY (post_id) REFERENCES symfony_demo_post (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_53AD8F83F675F31B FOREIGN KEY (author_id) REFERENCES symfony_demo_user (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); - $this->addSql('INSERT INTO symfony_demo_comment (id, post_id, author_id, content, published_at) SELECT id, post_id, author_id, content, published_at FROM __temp__symfony_demo_comment'); - $this->addSql('DROP TABLE __temp__symfony_demo_comment'); - $this->addSql('CREATE INDEX IDX_53AD8F83F675F31B ON symfony_demo_comment (author_id)'); - $this->addSql('CREATE INDEX IDX_53AD8F834B89032C ON symfony_demo_comment (post_id)'); - $this->addSql('DROP INDEX IDX_58A92E65F675F31B'); - $this->addSql('CREATE TEMPORARY TABLE __temp__symfony_demo_post AS SELECT id, author_id, title, slug, summary, content, published_at FROM symfony_demo_post'); - $this->addSql('DROP TABLE symfony_demo_post'); - $this->addSql('CREATE TABLE symfony_demo_post (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, author_id INTEGER NOT NULL, title VARCHAR(255) NOT NULL COLLATE BINARY, slug VARCHAR(255) NOT NULL COLLATE BINARY, summary VARCHAR(255) NOT NULL COLLATE BINARY, content CLOB NOT NULL COLLATE BINARY, published_at DATETIME NOT NULL, CONSTRAINT FK_58A92E65F675F31B FOREIGN KEY (author_id) REFERENCES symfony_demo_user (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); - $this->addSql('INSERT INTO symfony_demo_post (id, author_id, title, slug, summary, content, published_at) SELECT id, author_id, title, slug, summary, content, published_at FROM __temp__symfony_demo_post'); - $this->addSql('DROP TABLE __temp__symfony_demo_post'); - $this->addSql('CREATE INDEX IDX_58A92E65F675F31B ON symfony_demo_post (author_id)'); - $this->addSql('DROP INDEX IDX_6ABC1CC4BAD26311'); - $this->addSql('DROP INDEX IDX_6ABC1CC44B89032C'); - $this->addSql('CREATE TEMPORARY TABLE __temp__symfony_demo_post_tag AS SELECT post_id, tag_id FROM symfony_demo_post_tag'); - $this->addSql('DROP TABLE symfony_demo_post_tag'); - $this->addSql('CREATE TABLE symfony_demo_post_tag (post_id INTEGER NOT NULL, tag_id INTEGER NOT NULL, PRIMARY KEY(post_id, tag_id), CONSTRAINT FK_6ABC1CC44B89032C FOREIGN KEY (post_id) REFERENCES symfony_demo_post (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6ABC1CC4BAD26311 FOREIGN KEY (tag_id) REFERENCES symfony_demo_tag (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); - $this->addSql('INSERT INTO symfony_demo_post_tag (post_id, tag_id) SELECT post_id, tag_id FROM __temp__symfony_demo_post_tag'); - $this->addSql('DROP TABLE __temp__symfony_demo_post_tag'); - $this->addSql('CREATE INDEX IDX_6ABC1CC4BAD26311 ON symfony_demo_post_tag (tag_id)'); - $this->addSql('CREATE INDEX IDX_6ABC1CC44B89032C ON symfony_demo_post_tag (post_id)'); - $this->addSql('DROP INDEX UNIQ_8FB094A1E7927C74'); - $this->addSql('DROP INDEX UNIQ_8FB094A1F85E0677'); - $this->addSql('CREATE TEMPORARY TABLE __temp__symfony_demo_user AS SELECT id, full_name, username, email, password, roles FROM symfony_demo_user'); - $this->addSql('DROP TABLE symfony_demo_user'); - $this->addSql('CREATE TABLE symfony_demo_user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, full_name VARCHAR(255) NOT NULL COLLATE BINARY, username VARCHAR(255) NOT NULL COLLATE BINARY, email VARCHAR(255) NOT NULL COLLATE BINARY, password VARCHAR(255) NOT NULL COLLATE BINARY, roles CLOB NOT NULL --(DC2Type:json) - )'); - $this->addSql('INSERT INTO symfony_demo_user (id, full_name, username, email, password, roles) SELECT id, full_name, username, email, password, roles FROM __temp__symfony_demo_user'); - $this->addSql('DROP TABLE __temp__symfony_demo_user'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_8FB094A1E7927C74 ON symfony_demo_user (email)'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_8FB094A1F85E0677 ON symfony_demo_user (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() !== 'sqlite', 'Migration can only be executed safely on \'sqlite\'.'); - - $this->addSql('DROP TABLE garden'); - $this->addSql('DROP INDEX IDX_53AD8F834B89032C'); - $this->addSql('DROP INDEX IDX_53AD8F83F675F31B'); - $this->addSql('CREATE TEMPORARY TABLE __temp__symfony_demo_comment AS SELECT id, post_id, author_id, content, published_at FROM symfony_demo_comment'); - $this->addSql('DROP TABLE symfony_demo_comment'); - $this->addSql('CREATE TABLE symfony_demo_comment (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, post_id INTEGER NOT NULL, author_id INTEGER NOT NULL, content CLOB NOT NULL, published_at DATETIME NOT NULL)'); - $this->addSql('INSERT INTO symfony_demo_comment (id, post_id, author_id, content, published_at) SELECT id, post_id, author_id, content, published_at FROM __temp__symfony_demo_comment'); - $this->addSql('DROP TABLE __temp__symfony_demo_comment'); - $this->addSql('CREATE INDEX IDX_53AD8F834B89032C ON symfony_demo_comment (post_id)'); - $this->addSql('CREATE INDEX IDX_53AD8F83F675F31B ON symfony_demo_comment (author_id)'); - $this->addSql('DROP INDEX IDX_58A92E65F675F31B'); - $this->addSql('CREATE TEMPORARY TABLE __temp__symfony_demo_post AS SELECT id, author_id, title, slug, summary, content, published_at FROM symfony_demo_post'); - $this->addSql('DROP TABLE symfony_demo_post'); - $this->addSql('CREATE TABLE symfony_demo_post (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, author_id INTEGER NOT NULL, title VARCHAR(255) NOT NULL, slug VARCHAR(255) NOT NULL, summary VARCHAR(255) NOT NULL, content CLOB NOT NULL, published_at DATETIME NOT NULL)'); - $this->addSql('INSERT INTO symfony_demo_post (id, author_id, title, slug, summary, content, published_at) SELECT id, author_id, title, slug, summary, content, published_at FROM __temp__symfony_demo_post'); - $this->addSql('DROP TABLE __temp__symfony_demo_post'); - $this->addSql('CREATE INDEX IDX_58A92E65F675F31B ON symfony_demo_post (author_id)'); - $this->addSql('DROP INDEX IDX_6ABC1CC44B89032C'); - $this->addSql('DROP INDEX IDX_6ABC1CC4BAD26311'); - $this->addSql('CREATE TEMPORARY TABLE __temp__symfony_demo_post_tag AS SELECT post_id, tag_id FROM symfony_demo_post_tag'); - $this->addSql('DROP TABLE symfony_demo_post_tag'); - $this->addSql('CREATE TABLE symfony_demo_post_tag (post_id INTEGER NOT NULL, tag_id INTEGER NOT NULL, PRIMARY KEY(post_id, tag_id))'); - $this->addSql('INSERT INTO symfony_demo_post_tag (post_id, tag_id) SELECT post_id, tag_id FROM __temp__symfony_demo_post_tag'); - $this->addSql('DROP TABLE __temp__symfony_demo_post_tag'); - $this->addSql('CREATE INDEX IDX_6ABC1CC44B89032C ON symfony_demo_post_tag (post_id)'); - $this->addSql('CREATE INDEX IDX_6ABC1CC4BAD26311 ON symfony_demo_post_tag (tag_id)'); - $this->addSql('DROP INDEX UNIQ_8FB094A1F85E0677'); - $this->addSql('DROP INDEX UNIQ_8FB094A1E7927C74'); - $this->addSql('CREATE TEMPORARY TABLE __temp__symfony_demo_user AS SELECT id, full_name, username, email, password, roles FROM symfony_demo_user'); - $this->addSql('DROP TABLE symfony_demo_user'); - $this->addSql('CREATE TABLE symfony_demo_user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, full_name VARCHAR(255) NOT NULL, username VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, roles CLOB NOT NULL COLLATE BINARY)'); - $this->addSql('INSERT INTO symfony_demo_user (id, full_name, username, email, password, roles) SELECT id, full_name, username, email, password, roles FROM __temp__symfony_demo_user'); - $this->addSql('DROP TABLE __temp__symfony_demo_user'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_8FB094A1F85E0677 ON symfony_demo_user (username)'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_8FB094A1E7927C74 ON symfony_demo_user (email)'); - } -} diff --git a/migrations/Version20200727191337.php b/migrations/Version20200727191337.php new file mode 100644 index 00000000..e309a3d3 --- /dev/null +++ b/migrations/Version20200727191337.php @@ -0,0 +1,55 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('CREATE TABLE symfony_demo_comment (id INT AUTO_INCREMENT NOT NULL, post_id INT NOT NULL, author_id INT NOT NULL, content LONGTEXT NOT NULL, published_at DATETIME NOT NULL, INDEX IDX_53AD8F834B89032C (post_id), INDEX IDX_53AD8F83F675F31B (author_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE symfony_demo_tag (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_4D5855405E237E06 (name), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE symfony_demo_post (id INT AUTO_INCREMENT NOT NULL, author_id INT NOT NULL, title VARCHAR(255) NOT NULL, slug VARCHAR(255) NOT NULL, summary VARCHAR(255) NOT NULL, content LONGTEXT NOT NULL, published_at DATETIME NOT NULL, INDEX IDX_58A92E65F675F31B (author_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE symfony_demo_post_tag (post_id INT NOT NULL, tag_id INT NOT NULL, INDEX IDX_6ABC1CC44B89032C (post_id), INDEX IDX_6ABC1CC4BAD26311 (tag_id), PRIMARY KEY(post_id, tag_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE symfony_demo_user (id INT AUTO_INCREMENT NOT NULL, full_name VARCHAR(255) NOT NULL, username VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, roles LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', UNIQUE INDEX UNIQ_8FB094A1F85E0677 (username), UNIQUE INDEX UNIQ_8FB094A1E7927C74 (email), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE garden (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) NOT NULL, description LONGTEXT NOT NULL, address VARCHAR(255) NOT NULL, postcode INT NOT NULL, town VARCHAR(255) NOT NULL, lat DOUBLE PRECISION NOT NULL, lng DOUBLE PRECISION NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE symfony_demo_comment ADD CONSTRAINT FK_53AD8F834B89032C FOREIGN KEY (post_id) REFERENCES symfony_demo_post (id)'); + $this->addSql('ALTER TABLE symfony_demo_comment ADD CONSTRAINT FK_53AD8F83F675F31B FOREIGN KEY (author_id) REFERENCES symfony_demo_user (id)'); + $this->addSql('ALTER TABLE symfony_demo_post ADD CONSTRAINT FK_58A92E65F675F31B FOREIGN KEY (author_id) REFERENCES symfony_demo_user (id)'); + $this->addSql('ALTER TABLE symfony_demo_post_tag ADD CONSTRAINT FK_6ABC1CC44B89032C FOREIGN KEY (post_id) REFERENCES symfony_demo_post (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE symfony_demo_post_tag ADD CONSTRAINT FK_6ABC1CC4BAD26311 FOREIGN KEY (tag_id) REFERENCES symfony_demo_tag (id) ON DELETE CASCADE'); + } + + public function down(Schema $schema) : void + { + // this down() migration is auto-generated, please modify it to your needs + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('ALTER TABLE symfony_demo_post_tag DROP FOREIGN KEY FK_6ABC1CC4BAD26311'); + $this->addSql('ALTER TABLE symfony_demo_comment DROP FOREIGN KEY FK_53AD8F834B89032C'); + $this->addSql('ALTER TABLE symfony_demo_post_tag DROP FOREIGN KEY FK_6ABC1CC44B89032C'); + $this->addSql('ALTER TABLE symfony_demo_comment DROP FOREIGN KEY FK_53AD8F83F675F31B'); + $this->addSql('ALTER TABLE symfony_demo_post DROP FOREIGN KEY FK_58A92E65F675F31B'); + $this->addSql('DROP TABLE symfony_demo_comment'); + $this->addSql('DROP TABLE symfony_demo_tag'); + $this->addSql('DROP TABLE symfony_demo_post'); + $this->addSql('DROP TABLE symfony_demo_post_tag'); + $this->addSql('DROP TABLE symfony_demo_user'); + $this->addSql('DROP TABLE garden'); + } +} diff --git a/src/Controller/Admin/GardenController.php b/src/Controller/Admin/GardenController.php new file mode 100644 index 00000000..23fb9041 --- /dev/null +++ b/src/Controller/Admin/GardenController.php @@ -0,0 +1,193 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace App\Controller\Admin; + +use App\Entity\Garden; +use App\Form\GardenType; +use App\Repository\GardenRepository; +use App\Security\GardenVoter; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mime\Email; +use Symfony\Component\Routing\Annotation\Route; + + +/** + * + * @Route("/admin/garden") + * @IsGranted("ROLE_ADMIN") + * + * @author Boyquotes + */ +class GardenController extends AbstractController +{ + /** + * Lists all Garden entities. + * + * This controller responds to two different routes with the same URL: + * * 'admin_garden_index' is the route with a name that follows the same + * structure as the rest of the controllers of this class. + * * 'admin_index' is a nice shortcut to the backend homepage. This allows + * to create simpler links in the templates. Moreover, in the future we + * could move this annotation to any other controller while maintaining + * the route name and therefore, without breaking any existing link. + * + * @Route("/", methods="GET", name="admin_index") + * @Route("/", methods="GET", name="admin_garden_index") + */ + public function index(GardenRepository $gardens): Response + { + $authorGardens = $gardens->findAll(); + + return $this->render('admin/garden/index.html.twig', ['gardens' => $authorGardens]); + } + + /** + * Creates a new Garden entity. + * + * @Route("/new", methods="GET|POST", name="admin_garden_new") + * + * NOTE: the Method annotation is optional, but it's a recommended practice + * to constraint the HTTP methods each controller responds to (by default + * it responds to all methods). + */ + public function new(Request $request, MailerInterface $mailer): Response + { + $garden = new Garden(); + //~ $garden->setAuthor($this->getUser()); + + // See https://symfony.com/doc/current/form/multiple_buttons.html + $form = $this->createForm(GardenType::class, $garden) + ->add('saveAndCreateNew', SubmitType::class); + + $form->handleRequest($request); + + // the isSubmitted() method is completely optional because the other + // isValid() method already checks whether the form is submitted. + // However, we explicitly add it to improve code readability. + // See https://symfony.com/doc/current/forms.html#processing-forms + if ($form->isSubmitted() && $form->isValid()) { + $em = $this->getDoctrine()->getManager(); + $em->persist($garden); + $em->flush(); + + // Flash messages are used to notify the user about the result of the + // actions. They are deleted automatically from the session as soon + // as they are accessed. + // See https://symfony.com/doc/current/controller.html#flash-messages + $this->addFlash('success', 'garden.created_successfully'); + + $email = (new Email()) + ->from('hello@example.com') + ->to('nicolas@montpellibre.fr') + //->cc('cc@example.com') + //->bcc('bcc@example.com') + //->replyTo('fabien@example.com') + //->priority(Email::PRIORITY_HIGH) + ->subject('Time for Symfony Mailer!') + ->text('Sending emails is fun again!') + ->html('

See Twig integration for better HTML integration!

'); +dump($email); +dump($mailer); + try { + $mailer->send($email); + } catch (TransportExceptionInterface $e) { + // some error prevented the email sending; display an + // error message or try to resend the message + dump('ici'); + } + +exit; + if ($form->get('saveAndCreateNew')->isClicked()) { + return $this->redirectToRoute('admin_garden_new'); + } + + return $this->redirectToRoute('admin_garden_index'); + } + + return $this->render('admin/garden/new.html.twig', [ + 'garden' => $garden, + 'form' => $form->createView(), + ]); + } + + /** + * Finds and displays a Garden entity. + * + * @Route("/{id<\d+>}", methods="GET", name="admin_garden_show") + */ + public function show(Garden $garden): Response + { + // This security check can also be performed + // using an annotation: @IsGranted("show", subject="garden", message="Gardens can only be shown to their authors.") + $this->denyAccessUnlessGranted(GardenVoter::SHOW, $garden, 'Gardens can only be shown to their authors.'); + + return $this->render('admin/blog/show.html.twig', [ + 'garden' => $garden, + ]); + } + + /** + * Displays a form to edit an existing Garden entity. + * + * @Route("/{id<\d+>}/edit", methods="GET|POST", name="admin_garden_edit") + * @IsGranted("edit", subject="garden", message="Gardens can only be edited by their authors.") + */ + public function edit(Request $request, Garden $garden): Response + { + $form = $this->createForm(GardenType::class, $garden); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->getDoctrine()->getManager()->flush(); + + $this->addFlash('success', 'garden.updated_successfully'); + + return $this->redirectToRoute('admin_garden_edit', ['id' => $garden->getId()]); + } + + return $this->render('admin/blog/edit.html.twig', [ + 'garden' => $garden, + 'form' => $form->createView(), + ]); + } + + /** + * Deletes a Garden entity. + * + * @Route("/{id}/delete", methods="POST", name="admin_garden_delete") + * @IsGranted("delete", subject="garden") + */ + public function delete(Request $request, Garden $garden): Response + { + if (!$this->isCsrfTokenValid('delete', $request->request->get('token'))) { + return $this->redirectToRoute('admin_garden_index'); + } + + // Delete the tags associated with this blog garden. This is done automatically + // by Doctrine, except for SQLite (the database used in this application) + // because foreign key support is not enabled by default in SQLite + $garden->getTags()->clear(); + + $em = $this->getDoctrine()->getManager(); + $em->remove($garden); + $em->flush(); + + $this->addFlash('success', 'garden.deleted_successfully'); + + return $this->redirectToRoute('admin_garden_index'); + } +} diff --git a/src/Controller/RegistrationController.php b/src/Controller/RegistrationController.php new file mode 100644 index 00000000..d45388be --- /dev/null +++ b/src/Controller/RegistrationController.php @@ -0,0 +1,45 @@ +createForm(RegistrationFormType::class, $user); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + //~ // encode the plain password + $user->setPassword( + $passwordEncoder->encodePassword( + $user, + $form->get('password')->getData() + ) + ); + $user->setRoles(['ROLE_ADMIN']); + $entityManager = $this->getDoctrine()->getManager(); + $entityManager->persist($user); + $entityManager->flush(); + // do anything else you need here, like send an email + + return $this->redirectToRoute('homepage'); + } + + return $this->render('registration/register.html.twig', [ + 'registrationForm' => $form->createView(), + ]); + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php index 8e696ed8..5b2421a0 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -12,6 +12,7 @@ namespace App\Entity; use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Validator\Constraints as Assert; @@ -27,6 +28,7 @@ * * @author Ryan Weaver * @author Javier Eguiluz + * @UniqueEntity(fields={"username"}, message="There is already an account with this username") */ class User implements UserInterface, \Serializable { diff --git a/src/Form/GardenType.php b/src/Form/GardenType.php new file mode 100644 index 00000000..936b5bac --- /dev/null +++ b/src/Form/GardenType.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace App\Form; + +use App\Entity\Garden; +use App\Form\Type\DateTimePickerType; +use App\Form\Type\TagsInputType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\TextareaType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\String\Slugger\SluggerInterface; + +/** + * Defines the form used to create and manipulate blog posts. + * + * @author Ryan Weaver + * @author Javier Eguiluz + * @author Yonel Ceruto + */ +class GardenType extends AbstractType +{ + private $slugger; + + // Form types are services, so you can inject other services in them if needed + public function __construct(SluggerInterface $slugger) + { + $this->slugger = $slugger; + } + + /** + * {@inheritdoc} + */ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + // For the full reference of options defined by each form field type + // see https://symfony.com/doc/current/reference/forms/types.html + + // By default, form fields include the 'required' attribute, which enables + // the client-side form validation. This means that you can't test the + // server-side validation errors from the browser. To temporarily disable + // this validation, set the 'required' attribute to 'false': + // $builder->add('title', null, ['required' => false, ...]); + + $builder + ->add('title', null, [ + 'attr' => ['autofocus' => true], + 'label' => 'label.title', + ]) + ->add('description', null, [ + 'attr' => ['rows' => 20], + 'help' => 'help.garden_description', + 'label' => 'label.description', + ]) + ->add('address', TextareaType::class, [ + 'help' => 'help.gardeb_address', + 'label' => 'label.summary', + ]) + ->add('postcode', null, [ + 'attr' => [], + 'label' => 'label.postcode', + ]) + ->add('town', null, [ + 'attr' => [], + 'label' => 'label.town', + ]) + ->add('lat', null, [ + 'attr' => [], + 'label' => 'label.lat', + ]) + ->add('lng', null, [ + 'attr' => [], + 'label' => 'label.lng', + ]) + // form events let you modify information or fields at different steps + // of the form handling process. + // See https://symfony.com/doc/current/form/events.html + ->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) { + /** @var Garden */ + $post = $event->getData(); + }) + ; + } + + /** + * {@inheritdoc} + */ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Garden::class, + ]); + } +} diff --git a/src/Form/RegistrationFormType.php b/src/Form/RegistrationFormType.php new file mode 100644 index 00000000..50fe5018 --- /dev/null +++ b/src/Form/RegistrationFormType.php @@ -0,0 +1,57 @@ +add('username') + ->add('fullname') + ->add('email') + ->add('password') + //~ ->add('agreeTerms', CheckboxType::class, [ + //~ 'mapped' => false, + //~ 'constraints' => [ + //~ new IsTrue([ + //~ 'message' => 'You should agree to our terms.', + //~ ]), + //~ ], + //~ ]) + //~ ->add('plainPassword', PasswordType::class, [ + //~ // instead of being set onto the object directly, + //~ // this is read and encoded in the controller + //~ 'mapped' => false, + //~ 'constraints' => [ + //~ new NotBlank([ + //~ 'message' => 'Please enter a password', + //~ ]), + //~ new Length([ + //~ 'min' => 6, + //~ 'minMessage' => 'Your password should be at least {{ limit }} characters', + //~ // max length allowed by Symfony for security reasons + //~ 'max' => 4096, + //~ ]), + //~ ], + //~ ]) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => User::class, + ]); + } +} diff --git a/src/Repository/GardenRepository.php b/src/Repository/GardenRepository.php new file mode 100644 index 00000000..15a354d1 --- /dev/null +++ b/src/Repository/GardenRepository.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace App\Repository; + +use App\Entity\Garden; +use App\Entity\Tag; +use App\Pagination\Paginator; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Common\Persistence\ManagerRegistry; +use function Symfony\Component\String\u; + +/** + * This custom Doctrine repository contains some methods which are useful when + * querying for blog garden information. + * + * See https://symfony.com/doc/current/doctrine.html#querying-for-objects-the-repository + * + * @author Ryan Weaver + * @author Javier Eguiluz + * @author Yonel Ceruto + */ +class GardenRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Garden::class); + } + + +} diff --git a/src/Security/GardenVoter.php b/src/Security/GardenVoter.php new file mode 100644 index 00000000..dfad5fe9 --- /dev/null +++ b/src/Security/GardenVoter.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace App\Security; + +use App\Entity\Garden; +use App\Entity\User; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; + +/** + * It grants or denies permissions for actions related to blog gardens (such as + * showing, editing and deleting gardens). + * + * See https://symfony.com/doc/current/security/voters.html + * + * @author Yonel Ceruto + */ +class GardenVoter extends Voter +{ + // Defining these constants is overkill for this simple application, but for real + // applications, it's a recommended practice to avoid relying on "magic strings" + public const DELETE = 'delete'; + public const EDIT = 'edit'; + public const SHOW = 'show'; + + /** + * {@inheritdoc} + */ + protected function supports($attribute, $subject): bool + { + // this voter is only executed for three specific permissions on Garden objects + return $subject instanceof Garden && \in_array($attribute, [self::SHOW, self::EDIT, self::DELETE], true); + } + + /** + * {@inheritdoc} + */ + protected function voteOnAttribute($attribute, $garden, TokenInterface $token): bool + { + $user = $token->getUser(); + + // the user must be logged in; if not, deny permission + if (!$user instanceof User) { + return false; + } + + // the logic of this voter is pretty simple: if the logged user is the + // author of the given blog garden, grant permission; otherwise, deny it. + // (the supports() method guarantees that $garden is a Garden object) + return $user === $garden->getAuthor(); + } +} diff --git a/symfony.lock b/symfony.lock index 1d321002..bf8c99c4 100644 --- a/symfony.lock +++ b/symfony.lock @@ -342,6 +342,12 @@ "src/Kernel.php" ] }, + "symfony/http-client": { + "version": "v5.1.3" + }, + "symfony/http-client-contracts": { + "version": "v2.1.3" + }, "symfony/http-foundation": { "version": "v5.1.0-rc1" }, @@ -366,6 +372,15 @@ "config/packages/mailer.yaml" ] }, + "symfony/mailgun-mailer": { + "version": "4.4", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "4.4", + "ref": "addcb559b32d9dbb4843826b063565e1b15a2a57" + } + }, "symfony/maker-bundle": { "version": "1.0", "recipe": { diff --git a/templates/admin/garden/_delete_form.html.twig b/templates/admin/garden/_delete_form.html.twig new file mode 100644 index 00000000..257d4d8c --- /dev/null +++ b/templates/admin/garden/_delete_form.html.twig @@ -0,0 +1,8 @@ +{{ include('blog/_delete_post_confirmation.html.twig') }} +
+ + +
diff --git a/templates/admin/garden/_form.html.twig b/templates/admin/garden/_form.html.twig new file mode 100644 index 00000000..13fe0346 --- /dev/null +++ b/templates/admin/garden/_form.html.twig @@ -0,0 +1,26 @@ +{# + By default, forms enable client-side validation. This means that you can't + test the server-side validation errors from the browser. To temporarily + disable this validation, add the 'novalidate' attribute: + + {{ form_start(form, {attr: {novalidate: 'novalidate'}}) }} +#} + +{% if show_confirmation|default(false) %} + {% set attr = {'data-confirmation': 'true'} %} + {{ include('blog/_delete_post_confirmation.html.twig') }} +{% endif %} + +{{ form_start(form, {attr: attr|default({})}) }} + {{ form_widget(form) }} + + + + {% if include_back_to_home_link|default(false) %} + + {{ 'action.back_to_list'|trans }} + + {% endif %} +{{ form_end(form) }} diff --git a/templates/admin/garden/edit.html.twig b/templates/admin/garden/edit.html.twig new file mode 100644 index 00000000..106c952f --- /dev/null +++ b/templates/admin/garden/edit.html.twig @@ -0,0 +1,29 @@ +{% extends 'admin/layout.html.twig' %} + +{% block body_id 'admin_post_edit' %} + +{% block main %} +

{{ 'title.edit_post'|trans({'%id%': post.id}) }}

+ + {{ include('admin/blog/_form.html.twig', { + form: form, + button_label: 'action.save'|trans, + include_back_to_home_link: true, + }, with_context = false) }} +{% endblock %} + +{% block sidebar %} + + +
+ {{ include('admin/blog/_delete_form.html.twig', {post: post}, with_context = false) }} +
+ + {{ parent() }} + + {{ show_source_code(_self) }} +{% endblock %} diff --git a/templates/admin/garden/index.html.twig b/templates/admin/garden/index.html.twig new file mode 100644 index 00000000..ac7d6110 --- /dev/null +++ b/templates/admin/garden/index.html.twig @@ -0,0 +1,23 @@ +{% extends 'admin/layout.html.twig' %} + +{% block body_id 'admin_garden_index' %} + +{% block main %} +

{{ 'title.garden_list'|trans }}

+ + {% for garden in gardens %} + {{ garden.title }} + {% else %} + {{ 'garden.no_gardens_found'|trans }} + {% endfor %} +{% endblock %} + +{% block sidebar %} + + + {{ parent() }} +{% endblock %} diff --git a/templates/admin/garden/new.html.twig b/templates/admin/garden/new.html.twig new file mode 100644 index 00000000..ebb40bb6 --- /dev/null +++ b/templates/admin/garden/new.html.twig @@ -0,0 +1,31 @@ +{% extends 'admin/layout.html.twig' %} + +{% block body_id 'admin_garden_new' %} + +{% block main %} +

{{ 'title.garden_new'|trans }}

+ + {{ form_start(form) }} + {{ form_row(form.title) }} + {{ form_row(form.description) }} + {{ form_row(form.address) }} + {{ form_row(form.postcode) }} + {{ form_row(form.town) }} + {{ form_row(form.lat) }} + {{ form_row(form.lng) }} + + + {{ form_widget(form.saveAndCreateNew, {label: 'label.save_and_create_new', attr: {class: 'btn btn-primary'}}) }} + + {{ 'action.back_to_list'|trans }} + + {{ form_end(form) }} +{% endblock %} + +{% block sidebar %} + {{ parent() }} + + {{ show_source_code(_self) }} +{% endblock %} diff --git a/templates/admin/garden/show.html.twig b/templates/admin/garden/show.html.twig new file mode 100644 index 00000000..c891e17c --- /dev/null +++ b/templates/admin/garden/show.html.twig @@ -0,0 +1,36 @@ +{% extends 'admin/layout.html.twig' %} + +{% block body_id 'admin_post_show' %} + +{% block main %} +

{{ post.title }}

+ + + +
+

{{ 'label.summary'|trans }}: {{ post.summary }}

+
+ + {{ post.content|markdown_to_html|sanitize_html }} + + {{ include('blog/_post_tags.html.twig') }} +{% endblock %} + +{% block sidebar %} + + +
+ {{ include('admin/blog/_delete_form.html.twig', {post: post}, with_context = false) }} +
+ + {{ parent() }} + + {{ show_source_code(_self) }} +{% endblock %} diff --git a/templates/admin/layout.html.twig b/templates/admin/layout.html.twig index 92e55c10..244e33b9 100644 --- a/templates/admin/layout.html.twig +++ b/templates/admin/layout.html.twig @@ -13,6 +13,11 @@ {% endblock %} {% block header_navigation_links %} +
  • + + {{ 'menu.garden_list'|trans }} + +
  • {{ 'menu.post_list'|trans }} diff --git a/templates/garden/_comment_form.html.twig b/templates/garden/_comment_form.html.twig new file mode 100644 index 00000000..800a2fd5 --- /dev/null +++ b/templates/garden/_comment_form.html.twig @@ -0,0 +1,40 @@ +{# + By default, forms enable client-side validation. This means that you can't + test the server-side validation errors from the browser. To temporarily + disable this validation, add the 'novalidate' attribute: + + {{ form_start(form, {method: ..., action: ..., attr: {novalidate: 'novalidate'}}) }} +#} + +{{ form_start(form, {method: 'POST', action: path('comment_new', {'postSlug': post.slug})}) }} + {# instead of displaying form fields one by one, you can also display them + all with their default options and styles just by calling to this function: + + {{ form_widget(form) }} + #} + +
    + + {{ 'title.add_comment'|trans }} + + + {# Render any global form error (e.g. when a constraint on a public getter method failed) #} + {{ form_errors(form) }} + +
    + {{ form_label(form.content, 'label.content', {label_attr: {class: 'hidden'}}) }} + + {# Render any errors for the "content" field (e.g. when a class property constraint failed) #} + {{ form_errors(form.content) }} + + {{ form_widget(form.content, {attr: {rows: 10}}) }} + {{ form_help(form.content) }} +
    + +
    + +
    +
    +{{ form_end(form) }} diff --git a/templates/garden/_delete_garden_confirmation.html.twig b/templates/garden/_delete_garden_confirmation.html.twig new file mode 100644 index 00000000..3832c5e3 --- /dev/null +++ b/templates/garden/_delete_garden_confirmation.html.twig @@ -0,0 +1,19 @@ +{# Bootstrap modal, see https://getbootstrap.com/docs/3.4/javascript/#modals #} + diff --git a/templates/garden/_garden_tags.html.twig b/templates/garden/_garden_tags.html.twig new file mode 100644 index 00000000..9a821515 --- /dev/null +++ b/templates/garden/_garden_tags.html.twig @@ -0,0 +1,12 @@ +{% if not post.tags.empty %} +
    +{% endif %} + diff --git a/templates/garden/_rss.html.twig b/templates/garden/_rss.html.twig new file mode 100644 index 00000000..69d778b6 --- /dev/null +++ b/templates/garden/_rss.html.twig @@ -0,0 +1,5 @@ + diff --git a/templates/garden/about.html.twig b/templates/garden/about.html.twig new file mode 100644 index 00000000..2b0a629f --- /dev/null +++ b/templates/garden/about.html.twig @@ -0,0 +1,15 @@ +
    +
    +

    + {{ 'help.app_description'|trans|raw }} +

    +

    + {{ 'help.more_information'|trans|raw }} +

    +
    +
    + +{# it's not mandatory to set the timezone in localizeddate(). This is done to + avoid errors when the 'intl' PHP extension is not available and the application + is forced to use the limited "intl polyfill", which only supports UTC and GMT #} + diff --git a/templates/garden/comment_form_error.html.twig b/templates/garden/comment_form_error.html.twig new file mode 100644 index 00000000..67c2517b --- /dev/null +++ b/templates/garden/comment_form_error.html.twig @@ -0,0 +1,11 @@ +{% extends 'base.html.twig' %} + +{% block body_id 'comment_form_error' %} + +{% block main %} +

    {{ 'title.comment_error'|trans }}

    + +
    + {{ include('blog/_comment_form.html.twig') }} +
    +{% endblock %} diff --git a/templates/garden/garden_show.html.twig b/templates/garden/garden_show.html.twig new file mode 100644 index 00000000..2bd86070 --- /dev/null +++ b/templates/garden/garden_show.html.twig @@ -0,0 +1,77 @@ +{% extends 'base.html.twig' %} + +{% block body_id 'blog_post_show' %} + +{% block main %} +

    {{ post.title }}

    + + + + {{ post.content|markdown_to_html|sanitize_html }} + + {{ include('blog/_post_tags.html.twig') }} + +
    + {# The 'IS_AUTHENTICATED_FULLY' role ensures that the user has entered + their credentials (login + password) during this session. If they + are automatically logged via the 'Remember Me' functionality, they won't + be able to add a comment. + See https://symfony.com/doc/current/security/remember_me.html#forcing-the-user-to-re-authenticate-before-accessing-certain-resources + #} + {% if is_granted('IS_AUTHENTICATED_FULLY') %} + {{ render(controller('App\\Controller\\BlogController::commentForm', {'id': post.id})) }} + {% else %} +

    + + {{ 'action.sign_in'|trans }} + + {{ 'post.to_publish_a_comment'|trans }} +

    + {% endif %} +
    + +

    + {{ 'post.num_comments'|trans({ 'count': post.comments|length }) }} +

    + + {% for comment in post.comments %} +
    + +

    + {{ comment.author.fullName }} {{ 'post.commented_on'|trans }} + {# it's not mandatory to set the timezone in localizeddate(). This is done to + avoid errors when the 'intl' PHP extension is not available and the application + is forced to use the limited "intl polyfill", which only supports UTC and GMT #} + {{ comment.publishedAt|format_datetime('medium', 'short', '', 'UTC') }} +

    +
    + {{ comment.content|markdown_to_html|sanitize_html }} +
    +
    + {% else %} +
    +

    {{ 'post.no_comments'|trans }}

    +
    + {% endfor %} +{% endblock %} + +{% block sidebar %} + {% if is_granted('edit', post) %} + + {% endif %} + + {# the parent() function includes the contents defined by the parent template + ('base.html.twig') for this block ('sidebar'). This is a very convenient way + to share common contents in different templates #} + {{ parent() }} + + {{ show_source_code(_self) }} + {{ include('blog/_rss.html.twig') }} +{% endblock %} diff --git a/templates/garden/index.html.twig b/templates/garden/index.html.twig new file mode 100644 index 00000000..807dbb28 --- /dev/null +++ b/templates/garden/index.html.twig @@ -0,0 +1,57 @@ +{% extends 'base.html.twig' %} + +{% block body_id 'garden_index' %} + +{% block main %} + {% for garden in paginator.results %} +
    +

    + + {{ garden.title }} + +

    +{# + + +

    {{ garden.summary }}

    + + {{ include('blog/_garden_tags.html.twig') }} +#} +
    + {% else %} +
    {{ 'garden.no_gardens_found'|trans }}
    + {% endfor %} + + {% if paginator.hasToPaginate %} + + {% endif %} +{% endblock %} + +{% block sidebar %} + {{ parent() }} +{% endblock %} diff --git a/templates/garden/index.xml.twig b/templates/garden/index.xml.twig new file mode 100644 index 00000000..9ed0331a --- /dev/null +++ b/templates/garden/index.xml.twig @@ -0,0 +1,25 @@ + + + + {{ 'rss.title'|trans }} + {{ 'rss.description'|trans }} + {{ 'now'|date('r', timezone='GMT') }} + {{ (paginator.results|last).publishedAt|default('now')|date('r', timezone='GMT') }} + {{ url('blog_index') }} + {{ app.request.locale }} + + {% for post in paginator.results %} + + {{ post.title }} + {{ post.summary }} + {{ url('blog_post', {'slug': post.slug}) }} + {{ url('blog_post', {'slug': post.slug}) }} + {{ post.publishedAt|date(format='r', timezone='GMT') }} + {{ post.author.email }} + {% for tag in post.tags %} + {{ tag.name }} + {% endfor %} + + {% endfor %} + + diff --git a/templates/garden/search.html.twig b/templates/garden/search.html.twig new file mode 100644 index 00000000..f1ea3195 --- /dev/null +++ b/templates/garden/search.html.twig @@ -0,0 +1,31 @@ +{% extends 'base.html.twig' %} + +{% block javascripts %} + {{ parent() }} + {{ encore_entry_script_tags('search') }} +{% endblock %} + +{% block body_id 'blog_search' %} + +{% block main %} +
    +
    + +
    +
    + +
    +
    +{% endblock %} + +{% block sidebar %} + {{ parent() }} + + {{ show_source_code(_self) }} +{% endblock %} diff --git a/templates/registration/register.html.twig b/templates/registration/register.html.twig new file mode 100644 index 00000000..e8dfe021 --- /dev/null +++ b/templates/registration/register.html.twig @@ -0,0 +1,20 @@ +{% extends 'base.html.twig' %} + +{% block title %}Register{% endblock %} + +{% block body %} + {% for flashError in app.flashes('verify_email_error') %} + + {% endfor %} + +

    Register

    + + {{ form_start(registrationForm) }} + {{ form_row(registrationForm.username) }} + {{ form_row(registrationForm.fullname) }} + {{ form_row(registrationForm.email) }} + {{ form_row(registrationForm.password) }} + + + {{ form_end(registrationForm) }} +{% endblock %}