From 3d89b294c149b1b616775257704867df12a9af5e Mon Sep 17 00:00:00 2001
From: Emma
Date: Sun, 25 Mar 2018 03:14:46 +0200
Subject: [PATCH 01/24] update dependencies (fixes broken comments page)
---
composer.lock | 239 ++++++++++++++++++++++++++------------------------
1 file changed, 123 insertions(+), 116 deletions(-)
diff --git a/composer.lock b/composer.lock
index 8e5e0b1..570f35f 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1467,16 +1467,16 @@
},
{
"name": "league/commonmark",
- "version": "0.17.0",
+ "version": "0.17.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
- "reference": "3b4c2224524776a584de663c7a04bc8eb2e1544d"
+ "reference": "38b2fca6b8493abd9314fcf6b5b042cfba6a0f11"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/3b4c2224524776a584de663c7a04bc8eb2e1544d",
- "reference": "3b4c2224524776a584de663c7a04bc8eb2e1544d",
+ "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/38b2fca6b8493abd9314fcf6b5b042cfba6a0f11",
+ "reference": "38b2fca6b8493abd9314fcf6b5b042cfba6a0f11",
"shasum": ""
},
"require": {
@@ -1491,10 +1491,10 @@
"commonmark/commonmark.js": "0.28",
"erusev/parsedown": "~1.0",
"michelf/php-markdown": "~1.4",
- "mikehaertl/php-shellcommand": "~1.2.0",
- "phpunit/phpunit": "~5.7|~6.5",
- "scrutinizer/ocular": "~1.1",
- "symfony/finder": "~3.0|~4.0"
+ "mikehaertl/php-shellcommand": "^1.2",
+ "phpunit/phpunit": "^5.7|^6.5",
+ "scrutinizer/ocular": "^1.1",
+ "symfony/finder": "^3.0|^4.0"
},
"suggest": {
"league/commonmark-extras": "Library of useful extensions including smart punctuation"
@@ -1532,7 +1532,7 @@
"markdown",
"parser"
],
- "time": "2017-12-30T22:08:48+00:00"
+ "time": "2018-03-18T13:01:30+00:00"
},
{
"name": "league/flysystem",
@@ -1624,12 +1624,12 @@
"source": {
"type": "git",
"url": "https://github.com/liip/LiipImagineBundle.git",
- "reference": "13cdab6e01daeb945e690e2be8d3a627489b02a2"
+ "reference": "7700c5a82ff900189d00bedbc3d5bde9efbb4990"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/13cdab6e01daeb945e690e2be8d3a627489b02a2",
- "reference": "13cdab6e01daeb945e690e2be8d3a627489b02a2",
+ "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/7700c5a82ff900189d00bedbc3d5bde9efbb4990",
+ "reference": "7700c5a82ff900189d00bedbc3d5bde9efbb4990",
"shasum": ""
},
"require": {
@@ -1716,7 +1716,7 @@
"symfony",
"transformation"
],
- "time": "2018-03-02T20:07:20+00:00"
+ "time": "2018-03-21T08:53:31+00:00"
},
{
"name": "monolog/monolog",
@@ -2469,16 +2469,16 @@
},
{
"name": "psr/simple-cache",
- "version": "1.0.0",
+ "version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/simple-cache.git",
- "reference": "753fa598e8f3b9966c886fe13f370baa45ef0e24"
+ "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/753fa598e8f3b9966c886fe13f370baa45ef0e24",
- "reference": "753fa598e8f3b9966c886fe13f370baa45ef0e24",
+ "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+ "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
"shasum": ""
},
"require": {
@@ -2513,7 +2513,7 @@
"psr-16",
"simple-cache"
],
- "time": "2017-01-02T13:31:39+00:00"
+ "time": "2017-10-23T01:57:42+00:00"
},
{
"name": "ramsey/uuid",
@@ -2877,7 +2877,7 @@
},
{
"name": "symfony/asset",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/asset.git",
@@ -2933,7 +2933,7 @@
},
{
"name": "symfony/cache",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/cache.git",
@@ -3002,7 +3002,7 @@
},
{
"name": "symfony/config",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
@@ -3064,7 +3064,7 @@
},
{
"name": "symfony/console",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
@@ -3132,7 +3132,7 @@
},
{
"name": "symfony/debug",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
@@ -3188,16 +3188,16 @@
},
{
"name": "symfony/dependency-injection",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "2fca7961d10cf33fa528ce633e992942ef1bccd8"
+ "reference": "93ad14f124beacf16894b64bb5b3cdd5b4367e38"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/2fca7961d10cf33fa528ce633e992942ef1bccd8",
- "reference": "2fca7961d10cf33fa528ce633e992942ef1bccd8",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/93ad14f124beacf16894b64bb5b3cdd5b4367e38",
+ "reference": "93ad14f124beacf16894b64bb5b3cdd5b4367e38",
"shasum": ""
},
"require": {
@@ -3255,11 +3255,11 @@
],
"description": "Symfony DependencyInjection Component",
"homepage": "https://symfony.com",
- "time": "2018-02-26T15:55:47+00:00"
+ "time": "2018-03-05T18:28:26+00:00"
},
{
"name": "symfony/doctrine-bridge",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/doctrine-bridge.git",
@@ -3338,7 +3338,7 @@
},
{
"name": "symfony/event-dispatcher",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
@@ -3401,7 +3401,7 @@
},
{
"name": "symfony/expression-language",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/expression-language.git",
@@ -3451,7 +3451,7 @@
},
{
"name": "symfony/filesystem",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
@@ -3500,16 +3500,16 @@
},
{
"name": "symfony/finder",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "552e244df10237f845a94fd64b194f848805e34b"
+ "reference": "44a796d2ecc2a16a5fc8f2956a34ee617934d55f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/552e244df10237f845a94fd64b194f848805e34b",
- "reference": "552e244df10237f845a94fd64b194f848805e34b",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/44a796d2ecc2a16a5fc8f2956a34ee617934d55f",
+ "reference": "44a796d2ecc2a16a5fc8f2956a34ee617934d55f",
"shasum": ""
},
"require": {
@@ -3545,20 +3545,20 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
- "time": "2018-02-11T17:17:44+00:00"
+ "time": "2018-03-05T18:28:26+00:00"
},
{
"name": "symfony/flex",
- "version": "v1.0.70",
+ "version": "v1.0.76",
"source": {
"type": "git",
"url": "https://github.com/symfony/flex.git",
- "reference": "1f00c05d35523dc0ac52e4a457989a069be5a7a4"
+ "reference": "202c981673b255a41bac4076cb82629a0b8481cb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/flex/zipball/1f00c05d35523dc0ac52e4a457989a069be5a7a4",
- "reference": "1f00c05d35523dc0ac52e4a457989a069be5a7a4",
+ "url": "https://api.github.com/repos/symfony/flex/zipball/202c981673b255a41bac4076cb82629a0b8481cb",
+ "reference": "202c981673b255a41bac4076cb82629a0b8481cb",
"shasum": ""
},
"require": {
@@ -3591,11 +3591,11 @@
"email": "fabien.potencier@gmail.com"
}
],
- "time": "2018-02-22T07:00:47+00:00"
+ "time": "2018-03-23T16:30:50+00:00"
},
{
"name": "symfony/form",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/form.git",
@@ -3675,16 +3675,16 @@
},
{
"name": "symfony/framework-bundle",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/framework-bundle.git",
- "reference": "1221c54863061e71573b07882b4c53795fbfc229"
+ "reference": "d47d6da8c852648e26f12e55e4c895b81c4e99bf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/1221c54863061e71573b07882b4c53795fbfc229",
- "reference": "1221c54863061e71573b07882b4c53795fbfc229",
+ "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/d47d6da8c852648e26f12e55e4c895b81c4e99bf",
+ "reference": "d47d6da8c852648e26f12e55e4c895b81c4e99bf",
"shasum": ""
},
"require": {
@@ -3785,20 +3785,20 @@
],
"description": "Symfony FrameworkBundle",
"homepage": "https://symfony.com",
- "time": "2018-02-28T21:50:02+00:00"
+ "time": "2018-03-02T08:28:17+00:00"
},
{
"name": "symfony/http-foundation",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "94139989e51193e62a46bc87741ae05c8e8390f5"
+ "reference": "6c181e81a3a9a7996c62ebd7803592536e729c5a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/94139989e51193e62a46bc87741ae05c8e8390f5",
- "reference": "94139989e51193e62a46bc87741ae05c8e8390f5",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6c181e81a3a9a7996c62ebd7803592536e729c5a",
+ "reference": "6c181e81a3a9a7996c62ebd7803592536e729c5a",
"shasum": ""
},
"require": {
@@ -3838,20 +3838,20 @@
],
"description": "Symfony HttpFoundation Component",
"homepage": "https://symfony.com",
- "time": "2018-02-22T10:50:29+00:00"
+ "time": "2018-03-05T16:01:10+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "77cf672c855f038b1b916201d00b1d55899ee8e7"
+ "reference": "2a1ebfe8c37240500befcb17bceb3893adacffa3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/77cf672c855f038b1b916201d00b1d55899ee8e7",
- "reference": "77cf672c855f038b1b916201d00b1d55899ee8e7",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/2a1ebfe8c37240500befcb17bceb3893adacffa3",
+ "reference": "2a1ebfe8c37240500befcb17bceb3893adacffa3",
"shasum": ""
},
"require": {
@@ -3924,11 +3924,11 @@
],
"description": "Symfony HttpKernel Component",
"homepage": "https://symfony.com",
- "time": "2018-03-01T19:48:35+00:00"
+ "time": "2018-03-05T22:27:01+00:00"
},
{
"name": "symfony/inflector",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/inflector.git",
@@ -3985,7 +3985,7 @@
},
{
"name": "symfony/intl",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/intl.git",
@@ -4060,7 +4060,7 @@
},
{
"name": "symfony/monolog-bridge",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/monolog-bridge.git",
@@ -4126,16 +4126,16 @@
},
{
"name": "symfony/monolog-bundle",
- "version": "v3.1.2",
+ "version": "v3.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/monolog-bundle.git",
- "reference": "2b41b8b6d2c6edb1a5494f02f8e4129be2a44784"
+ "reference": "8781649349fe418d51d194f8c9d212c0b97c40dd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/2b41b8b6d2c6edb1a5494f02f8e4129be2a44784",
- "reference": "2b41b8b6d2c6edb1a5494f02f8e4129be2a44784",
+ "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/8781649349fe418d51d194f8c9d212c0b97c40dd",
+ "reference": "8781649349fe418d51d194f8c9d212c0b97c40dd",
"shasum": ""
},
"require": {
@@ -4185,11 +4185,11 @@
"log",
"logging"
],
- "time": "2017-11-06T16:02:17+00:00"
+ "time": "2018-03-05T14:51:36+00:00"
},
{
"name": "symfony/options-resolver",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
@@ -4448,7 +4448,7 @@
},
{
"name": "symfony/process",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
@@ -4497,7 +4497,7 @@
},
{
"name": "symfony/property-access",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/property-access.git",
@@ -4564,7 +4564,7 @@
},
{
"name": "symfony/property-info",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/property-info.git",
@@ -4688,7 +4688,7 @@
},
{
"name": "symfony/routing",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
@@ -4766,7 +4766,7 @@
},
{
"name": "symfony/security",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/security.git",
@@ -4843,7 +4843,7 @@
},
{
"name": "symfony/security-bundle",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/security-bundle.git",
@@ -4923,7 +4923,7 @@
},
{
"name": "symfony/serializer",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/serializer.git",
@@ -5032,16 +5032,16 @@
},
{
"name": "symfony/swiftmailer-bundle",
- "version": "v3.2.0",
+ "version": "v3.2.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/swiftmailer-bundle.git",
- "reference": "7756867d9b21231ae0ce746a6b861b7e293cc885"
+ "reference": "20e71c247a5a43ceb655db9712394d08c09b33ef"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/swiftmailer-bundle/zipball/7756867d9b21231ae0ce746a6b861b7e293cc885",
- "reference": "7756867d9b21231ae0ce746a6b861b7e293cc885",
+ "url": "https://api.github.com/repos/symfony/swiftmailer-bundle/zipball/20e71c247a5a43ceb655db9712394d08c09b33ef",
+ "reference": "20e71c247a5a43ceb655db9712394d08c09b33ef",
"shasum": ""
},
"require": {
@@ -5090,11 +5090,11 @@
],
"description": "Symfony SwiftmailerBundle",
"homepage": "http://symfony.com",
- "time": "2018-02-14T08:09:24+00:00"
+ "time": "2018-03-08T16:39:26+00:00"
},
{
"name": "symfony/templating",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/templating.git",
@@ -5149,7 +5149,7 @@
},
{
"name": "symfony/translation",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
@@ -5217,7 +5217,7 @@
},
{
"name": "symfony/twig-bridge",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/twig-bridge.git",
@@ -5307,7 +5307,7 @@
},
{
"name": "symfony/twig-bundle",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/twig-bundle.git",
@@ -5380,7 +5380,7 @@
},
{
"name": "symfony/validator",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/validator.git",
@@ -5464,7 +5464,7 @@
},
{
"name": "symfony/web-link",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/web-link.git",
@@ -5535,7 +5535,7 @@
},
{
"name": "symfony/yaml",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
@@ -5649,16 +5649,16 @@
},
{
"name": "twig/twig",
- "version": "v2.4.6",
+ "version": "v2.4.7",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
- "reference": "d2117ec118c1ff3d28ccddca8212d82787a4809f"
+ "reference": "69aacd44dbbaa3199d5afb68605c996d577896fc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/twigphp/Twig/zipball/d2117ec118c1ff3d28ccddca8212d82787a4809f",
- "reference": "d2117ec118c1ff3d28ccddca8212d82787a4809f",
+ "url": "https://api.github.com/repos/twigphp/Twig/zipball/69aacd44dbbaa3199d5afb68605c996d577896fc",
+ "reference": "69aacd44dbbaa3199d5afb68605c996d577896fc",
"shasum": ""
},
"require": {
@@ -5667,8 +5667,8 @@
},
"require-dev": {
"psr/container": "^1.0",
- "symfony/debug": "~2.7",
- "symfony/phpunit-bridge": "~3.3@dev"
+ "symfony/debug": "^2.7",
+ "symfony/phpunit-bridge": "^3.3"
},
"type": "library",
"extra": {
@@ -5711,7 +5711,7 @@
"keywords": [
"templating"
],
- "time": "2018-03-03T16:23:01+00:00"
+ "time": "2018-03-20T04:31:17+00:00"
},
{
"name": "webmozart/assert",
@@ -6373,16 +6373,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
- "version": "v2.10.3",
+ "version": "v2.11.1",
"source": {
"type": "git",
"url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git",
- "reference": "1634a2c250bf4640f1c5c963f63b413c2d966c8a"
+ "reference": "ad94441c17b8ef096e517acccdbf3238af8a2da8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/1634a2c250bf4640f1c5c963f63b413c2d966c8a",
- "reference": "1634a2c250bf4640f1c5c963f63b413c2d966c8a",
+ "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/ad94441c17b8ef096e517acccdbf3238af8a2da8",
+ "reference": "ad94441c17b8ef096e517acccdbf3238af8a2da8",
"shasum": ""
},
"require": {
@@ -6391,7 +6391,7 @@
"ext-json": "*",
"ext-tokenizer": "*",
"php": "^5.6 || >=7.0 <7.3",
- "php-cs-fixer/diff": "^1.2",
+ "php-cs-fixer/diff": "^1.3",
"symfony/console": "^3.2 || ^4.0",
"symfony/event-dispatcher": "^3.0 || ^4.0",
"symfony/filesystem": "^3.0 || ^4.0",
@@ -6406,14 +6406,14 @@
"hhvm": "*"
},
"require-dev": {
- "johnkary/phpunit-speedtrap": "^1.1 || ^2.0@dev",
+ "johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0",
"justinrainbow/json-schema": "^5.0",
"keradus/cli-executor": "^1.0",
"mikey179/vfsstream": "^1.6",
"php-coveralls/php-coveralls": "^2.0",
"php-cs-fixer/accessible-object": "^1.0",
- "phpunit/phpunit": "^5.7.23 || ^6.4.3",
- "phpunitgoodpractices/traits": "^1.0",
+ "phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0",
+ "phpunitgoodpractices/traits": "^1.3.1",
"symfony/phpunit-bridge": "^3.2.2 || ^4.0"
},
"suggest": {
@@ -6424,6 +6424,11 @@
"php-cs-fixer"
],
"type": "application",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.11-dev"
+ }
+ },
"autoload": {
"psr-4": {
"PhpCsFixer\\": "src/"
@@ -6434,6 +6439,8 @@
"tests/Test/AbstractIntegrationTestCase.php",
"tests/Test/Assert/AssertTokensTrait.php",
"tests/Test/Constraint/SameStringsConstraint.php",
+ "tests/Test/Constraint/SameStringsConstraintForV5.php",
+ "tests/Test/Constraint/SameStringsConstraintForV7.php",
"tests/Test/IntegrationCase.php",
"tests/Test/IntegrationCaseFactory.php",
"tests/Test/IntegrationCaseFactoryInterface.php",
@@ -6456,7 +6463,7 @@
}
],
"description": "A tool to automatically fix PHP code style",
- "time": "2018-02-22T16:49:33+00:00"
+ "time": "2018-03-21T17:41:26+00:00"
},
{
"name": "php-cs-fixer/diff",
@@ -6511,7 +6518,7 @@
},
{
"name": "symfony/browser-kit",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
@@ -6568,7 +6575,7 @@
},
{
"name": "symfony/css-selector",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
@@ -6621,7 +6628,7 @@
},
{
"name": "symfony/debug-bundle",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug-bundle.git",
@@ -6717,7 +6724,7 @@
},
{
"name": "symfony/dom-crawler",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
@@ -6773,7 +6780,7 @@
},
{
"name": "symfony/dotenv",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/dotenv.git",
@@ -6830,7 +6837,7 @@
},
{
"name": "symfony/phpunit-bridge",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/phpunit-bridge.git",
@@ -6979,7 +6986,7 @@
},
{
"name": "symfony/stopwatch",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/stopwatch.git",
@@ -7028,7 +7035,7 @@
},
{
"name": "symfony/var-dumper",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
@@ -7097,16 +7104,16 @@
},
{
"name": "symfony/web-profiler-bundle",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/web-profiler-bundle.git",
- "reference": "cd27b1813b0ce6b042b411cbb4e9d2aad6f07027"
+ "reference": "eaaf46b1ccaf83c8c97efd8d081e03d9ff0f29de"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/cd27b1813b0ce6b042b411cbb4e9d2aad6f07027",
- "reference": "cd27b1813b0ce6b042b411cbb4e9d2aad6f07027",
+ "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/eaaf46b1ccaf83c8c97efd8d081e03d9ff0f29de",
+ "reference": "eaaf46b1ccaf83c8c97efd8d081e03d9ff0f29de",
"shasum": ""
},
"require": {
@@ -7159,11 +7166,11 @@
],
"description": "Symfony WebProfilerBundle",
"homepage": "https://symfony.com",
- "time": "2018-02-28T21:50:02+00:00"
+ "time": "2018-03-02T08:28:17+00:00"
},
{
"name": "symfony/web-server-bundle",
- "version": "v4.0.5",
+ "version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/web-server-bundle.git",
From 22575f53fd8e79b35dd6d84fc373332d8bbf69f1 Mon Sep 17 00:00:00 2001
From: Emma
Date: Fri, 30 Mar 2018 06:31:51 +0200
Subject: [PATCH 02/24] prevent long site names from wrapping
---
assets/less/components/site-nav.less | 1 +
1 file changed, 1 insertion(+)
diff --git a/assets/less/components/site-nav.less b/assets/less/components/site-nav.less
index e228042..98c6374 100644
--- a/assets/less/components/site-nav.less
+++ b/assets/less/components/site-nav.less
@@ -17,6 +17,7 @@
display: block;
font-size: @nav-font-size;
padding: 1rem;
+ white-space: nowrap;
}
&__logo {
From 5b928aee944dfe4d6c3b3ace5b91b8daed91eb2c Mon Sep 17 00:00:00 2001
From: Emma
Date: Fri, 30 Mar 2018 06:51:28 +0200
Subject: [PATCH 03/24] update notice/new project URL
---
README.md | 29 ++++++++---------------------
assets/less/main.less | 2 +-
templates/base.html.twig | 2 +-
3 files changed, 10 insertions(+), 23 deletions(-)
diff --git a/README.md b/README.md
index a15a481..87f49e5 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,7 @@
-**Notice**: Now that the community Postmill was created for has become
-unpalatable for me, the future of this project is uncertain. Previously, I
-could spend time implementing a feature or improving something, and see the
-community embrace (or, on occasion, deride) my hard work. Now that I have no
-community, this is no longer the case, and I cannot imagine what motivation I
-could have to keep working on this project.
-
-For now, if you want to report issues or submit pull requests, I'll take a look
-at them. Any security holes I become aware of (either in Postmill itself or in
-its dependencies) will be fixed. But active development probably won't happen
-unless someone steps up. Consider Postmill to be on life support.
-
-I'm passionate about building communities, and I will probably make another
-effort to build something cool in the future, using the experience I made from
-Postmill. Until then, I'm taking a break.
-
-Thanks to everyone who contributed to Postmill, or merely sent kind words my
-way. You made it all worth it.
-
--- Emma
+Thanks to continued interest in Postmill, as well as some community efforts to
+improve it, I feel a bit more motivated to work on this project again. A
+[website][website] and new [support forums][support] have been set up. We'll see
+where things go from here.
***
@@ -75,7 +59,8 @@ Clone the repository somewhere and navigate there with the command line.
## Reporting issues
* Bugs and feature requests should be reported on the [issue tracker][issues].
-* Get support on Postmill's Matrix channel: [#postmill:matrix.org][matrix].
+* Get support on Postmill's [support board][support] or
+ [Matrix channel][matrix].
You can email emma1312@protonmail.ch to disclose or discuss something in private
with the creator of the software.
@@ -98,3 +83,5 @@ details.
[issues]: https://gitlab.com/edgyemma/Postmill/issues
[matrix]: https://matrix.to/#/#postmill:matrix.org
+[support]: https://community.postmill.xyz/f/Support
+[website]: https://postmill.xyz/
diff --git a/assets/less/main.less b/assets/less/main.less
index 1a541bb..2f7dee6 100644
--- a/assets/less/main.less
+++ b/assets/less/main.less
@@ -1,6 +1,6 @@
@import (less) '~normalize.css';
-/*! https://gitlab.com/edgyemma/Postmill */
+/*! https://postmill.xyz/ */
// global element styles (must be first)
@import 'global';
diff --git a/templates/base.html.twig b/templates/base.html.twig
index 2914210..6c8218b 100644
--- a/templates/base.html.twig
+++ b/templates/base.html.twig
@@ -49,7 +49,7 @@
{{ 'site_footer.version'|trans({
'%branch%': app_branch(),
'%version%': app_version(),
- '%app%': 'Postmill ',
+ '%app%': 'Postmill ',
})|raw }}
From c436a963720c83e8c179549cc78bfcfee0bedb89 Mon Sep 17 00:00:00 2001
From: Emma
Date: Sat, 31 Mar 2018 06:14:19 +0200
Subject: [PATCH 04/24] added webhooks
---
.env.dist | 1 +
composer.json | 1 +
composer.lock | 294 +++++++++++++++++++++-
config/app_routes/forum.yaml | 20 ++
config/bundles.php | 1 +
config/packages/eight_points_guzzle.yaml | 16 ++
config/services.yaml | 11 +
docs/webhooks.md | 178 +++++++++++++
src/Controller/CommentController.php | 36 ++-
src/Controller/ForumController.php | 143 +++++++++++
src/Controller/SubmissionController.php | 41 ++-
src/Entity/Forum.php | 27 ++
src/Entity/ForumWebhook.php | 105 ++++++++
src/Entity/UserFlags.php | 23 +-
src/Event/EntityModifiedEvent.php | 27 ++
src/EventListener/WebhookListener.php | 184 ++++++++++++++
src/Events.php | 12 +
src/Form/ForumWebhookType.php | 46 ++++
src/Form/Model/ForumWebhookData.php | 65 +++++
src/Migrations/Version20180325060258.php | 23 ++
src/Repository/ForumWebhookRepository.php | 14 ++
src/Serializer/CommentNormalizer.php | 85 +++++++
src/Serializer/SubmissionNormalizer.php | 107 ++++++++
src/Twig/AppExtension.php | 11 +-
symfony.lock | 21 ++
templates/forum/_webhook_form.html.twig | 9 +
templates/forum/add_webhook.html.twig | 15 ++
templates/forum/base.html.twig | 7 +
templates/forum/edit_webhook.html.twig | 17 ++
templates/forum/webhooks.html.twig | 57 +++++
tests/Event/EntityModifiedEventTest.php | 22 ++
translations/messages.en.yml | 16 ++
32 files changed, 1609 insertions(+), 26 deletions(-)
create mode 100644 config/packages/eight_points_guzzle.yaml
create mode 100644 docs/webhooks.md
create mode 100644 src/Entity/ForumWebhook.php
create mode 100644 src/Event/EntityModifiedEvent.php
create mode 100644 src/EventListener/WebhookListener.php
create mode 100644 src/Events.php
create mode 100644 src/Form/ForumWebhookType.php
create mode 100644 src/Form/Model/ForumWebhookData.php
create mode 100644 src/Migrations/Version20180325060258.php
create mode 100644 src/Repository/ForumWebhookRepository.php
create mode 100644 src/Serializer/CommentNormalizer.php
create mode 100644 src/Serializer/SubmissionNormalizer.php
create mode 100644 templates/forum/_webhook_form.html.twig
create mode 100644 templates/forum/add_webhook.html.twig
create mode 100644 templates/forum/edit_webhook.html.twig
create mode 100644 templates/forum/webhooks.html.twig
create mode 100644 tests/Event/EntityModifiedEventTest.php
diff --git a/.env.dist b/.env.dist
index a0191ee..217bf0e 100644
--- a/.env.dist
+++ b/.env.dist
@@ -5,6 +5,7 @@
SITE_NAME=Postmill
NO_REPLY_ADDRESS="no-reply@example.com"
APP_LOCALE=en
+APP_ENABLE_WEBHOOKS=0
###> symfony/framework-bundle ###
APP_ENV=dev
diff --git a/composer.json b/composer.json
index 237d465..d882b8c 100644
--- a/composer.json
+++ b/composer.json
@@ -20,6 +20,7 @@
"ext-iconv": "*",
"ext-pdo_pgsql": "*",
"doctrine/doctrine-migrations-bundle": "^1.2",
+ "eightpoints/guzzle-bundle": "^7.3",
"embed/embed": "^3.0",
"ezyang/htmlpurifier": "^4.8",
"friendsofsymfony/jsrouting-bundle": "^2.1",
diff --git a/composer.lock b/composer.lock
index 570f35f..ad67ac0 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "content-hash": "357889276824e57ec51081f41102f414",
+ "content-hash": "05cf9ecf6e555b98c3adb6f3f8d9ced3",
"packages": [
{
"name": "composer/ca-bundle",
@@ -1037,6 +1037,67 @@
],
"time": "2017-11-15T23:40:40+00:00"
},
+ {
+ "name": "eightpoints/guzzle-bundle",
+ "version": "7.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/8p/EightPointsGuzzleBundle.git",
+ "reference": "1b663153f860214f18a18d8df6bf5f0592689550"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/8p/EightPointsGuzzleBundle/zipball/1b663153f860214f18a18d8df6bf5f0592689550",
+ "reference": "1b663153f860214f18a18d8df6bf5f0592689550",
+ "shasum": ""
+ },
+ "require": {
+ "guzzlehttp/guzzle": "~6.0",
+ "php": ">=7.0",
+ "psr/log": "~1.0",
+ "symfony/expression-language": "~2.7|~3.0|~4.0",
+ "symfony/framework-bundle": "~2.7|~3.0|~4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~6.1",
+ "symfony/var-dumper": "~2.7|~3.0|~4.0",
+ "twig/twig": "~1.5|~2.0"
+ },
+ "type": "symfony-bundle",
+ "autoload": {
+ "psr-4": {
+ "EightPoints\\Bundle\\GuzzleBundle\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Community",
+ "homepage": "https://github.com/8p/GuzzleBundle/contributors"
+ },
+ {
+ "name": "Florian Preusner",
+ "email": "florian.preusner@8points.de",
+ "homepage": "https://github.com/florianpreusner"
+ }
+ ],
+ "description": "Integrates Guzzle 6.x, a PHP HTTP Client, into Symfony 2/3/4. Comes with easy and powerful configuration options and optional plugins.",
+ "homepage": "https://github.com/8p/GuzzleBundle",
+ "keywords": [
+ "Guzzle",
+ "bundle",
+ "client",
+ "curl",
+ "http client",
+ "rest",
+ "symfony",
+ "web service"
+ ],
+ "time": "2018-01-14T20:52:44+00:00"
+ },
{
"name": "embed/embed",
"version": "v3.3.1",
@@ -1357,6 +1418,187 @@
],
"time": "2017-12-28T11:21:03+00:00"
},
+ {
+ "name": "guzzlehttp/guzzle",
+ "version": "6.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle.git",
+ "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699",
+ "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699",
+ "shasum": ""
+ },
+ "require": {
+ "guzzlehttp/promises": "^1.0",
+ "guzzlehttp/psr7": "^1.4",
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "ext-curl": "*",
+ "phpunit/phpunit": "^4.0 || ^5.0",
+ "psr/log": "^1.0"
+ },
+ "suggest": {
+ "psr/log": "Required for using the Log middleware"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.2-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/functions_include.php"
+ ],
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Guzzle is a PHP HTTP client library",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": [
+ "client",
+ "curl",
+ "framework",
+ "http",
+ "http client",
+ "rest",
+ "web service"
+ ],
+ "time": "2017-06-22T18:50:49+00:00"
+ },
+ {
+ "name": "guzzlehttp/promises",
+ "version": "v1.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/promises.git",
+ "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+ "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Guzzle promises library",
+ "keywords": [
+ "promise"
+ ],
+ "time": "2016-12-20T10:07:11+00:00"
+ },
+ {
+ "name": "guzzlehttp/psr7",
+ "version": "1.4.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
+ "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0",
+ "psr/http-message": "~1.0"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Schultze",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "http",
+ "message",
+ "request",
+ "response",
+ "stream",
+ "uri",
+ "url"
+ ],
+ "time": "2017-03-20T17:10:46+00:00"
+ },
{
"name": "imagine/imagine",
"version": "v0.7.1",
@@ -2371,6 +2613,56 @@
],
"time": "2017-02-14T16:28:37+00:00"
},
+ {
+ "name": "psr/http-message",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "time": "2016-08-06T14:39:51+00:00"
+ },
{
"name": "psr/link",
"version": "1.0.0",
diff --git a/config/app_routes/forum.yaml b/config/app_routes/forum.yaml
index 3ba4a33..adc578d 100644
--- a/config/app_routes/forum.yaml
+++ b/config/app_routes/forum.yaml
@@ -119,6 +119,26 @@ forum_list:
methods: [GET]
requirements: { forums: \d+, sortBy: by_name|by_title|by_subscribers|by_submissions }
+forum_webhooks:
+ controller: App\Controller\ForumController::webhooks
+ methods: [GET]
+ path: /f/{forum_name}/webhooks
+
+forum_add_webhook:
+ controller: App\Controller\ForumController::addWebhook
+ methods: [GET, POST]
+ path: /f/{forum_name}/add_webhook
+
+forum_edit_webhook:
+ controller: App\Controller\ForumController::editWebhook
+ methods: [GET, POST]
+ path: /f/{forum_name}/edit_webhook/{webhook_id}
+
+forum_remove_webhook:
+ controller: App\Controller\ForumController::removeWebhook
+ methods: [POST]
+ path: /f/{forum_name}/remove_webhook
+
forums_by_category:
controller: App\Controller\ForumController::listCategories
path: /forums/by_category
diff --git a/config/bundles.php b/config/bundles.php
index fd9362e..a011418 100644
--- a/config/bundles.php
+++ b/config/bundles.php
@@ -21,4 +21,5 @@
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true],
Symfony\Bundle\WebServerBundle\WebServerBundle::class => ['dev' => true],
Gregwar\CaptchaBundle\GregwarCaptchaBundle::class => ['all' => true],
+ EightPoints\Bundle\GuzzleBundle\EightPointsGuzzleBundle::class => ['all' => true],
];
diff --git a/config/packages/eight_points_guzzle.yaml b/config/packages/eight_points_guzzle.yaml
new file mode 100644
index 0000000..30f9549
--- /dev/null
+++ b/config/packages/eight_points_guzzle.yaml
@@ -0,0 +1,16 @@
+eight_points_guzzle:
+ clients:
+ webhook_client:
+ options:
+ timeout: 5
+
+ # Configure headers.
+ # More info: http://docs.guzzlephp.org/en/stable/request-options.html#headers
+ headers:
+ User-Agent: "Postmill/0.6 (https://postmill.xyz/)"
+ Accept: application/json, */*
+ Content-Type: application/json
+
+ # Find plugins here:
+ # https://github.com/8p/EightPointsGuzzleBundle#known-and-supported-plugins
+ plugin: ~
diff --git a/config/services.yaml b/config/services.yaml
index cb7b4ba..13af52b 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -5,6 +5,7 @@ parameters:
user_forum_creation_interval: 1 day
uuid_regex: '[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}'
wiki_page_regex: '[A-Za-z][A-Za-z0-9_-]*(/[A-Za-z][A-Za-z0-9_-]*)*'
+ env(APP_ENABLE_WEBHOOKS): false
services:
_defaults:
@@ -40,6 +41,10 @@ services:
arguments:
$defaultLocale: "%env(APP_LOCALE)%"
+ App\Controller\ForumController:
+ arguments:
+ $enableWebhooks: "%env(bool:APP_ENABLE_WEBHOOKS)%"
+
App\Controller\UserController:
arguments:
$defaultLocale: "%env(APP_LOCALE)%"
@@ -63,6 +68,11 @@ services:
- { name: doctrine.event_listener, event: postPersist }
- { name: kernel.event_listener, event: kernel.terminate }
+ App\EventListener\WebhookListener:
+ arguments:
+ $client: "@eight_points_guzzle.client.webhook_client"
+ $webhooksEnabled: "%env(bool:APP_ENABLE_WEBHOOKS)%"
+
App\Form\RequestPasswordResetType:
arguments:
$bypass: "@=parameter('kernel.environment') === 'test'"
@@ -80,6 +90,7 @@ services:
App\Twig\AppExtension:
arguments:
$siteName: "%env(SITE_NAME)%"
+ $enableWebhooks: "%env(bool:APP_ENABLE_WEBHOOKS)%"
App\Utils\CachedMarkdownConverter:
arguments:
diff --git a/docs/webhooks.md b/docs/webhooks.md
new file mode 100644
index 0000000..bf8673a
--- /dev/null
+++ b/docs/webhooks.md
@@ -0,0 +1,178 @@
+# About webhooks
+
+A [webhook](https://en.wikipedia.org/wiki/Webhook) is a mechanism for notifying
+over HTTP a third-party server when an event occurs. For instance, if a webhook
+is configured to listen on 'new comment' events with the URL
+`http://example.com/`, Postmill will send a POST request to that URL every time
+a new comment is posted.
+
+Currently, webhooks are added on a per-forum basis, and are managed by forum
+moderators. The ability to add global webhooks is planned.
+
+For security reasons, webhooks are disabled by default. If you trust your mods,
+or you understand the security implications of letting your server make an
+arbitrary number of HTTP requests to arbitrary servers when an event occurs, you
+can set the `APP_ENABLE_WEBHOOKS` environment variable to `1` to enable
+webhooks.
+
+Performance
+---
+
+Webhooks are dispatched when Symfony's `kernel.terminate` event has been
+dispatched. In practice, this means that setups which don't use PHP-FPM will
+dispatch the webhooks before delivering the response, which can make events
+seem slow to users.
+
+Secret token
+---
+
+The secret token, if specified, will be included in the outgoing request via the
+`X-Postmill-Secret` header.
+
+Request bodies
+---
+
+### New submission
+
+~~~json
+{
+ "event": "new_submission",
+ "subject": {
+ "resource": "https://example.com/f/example/420/smoke-weed",
+ "id": 420,
+ "forum": "https://example.com/f/example",
+ "user": "https://example.com/user/emma",
+ "title": "Smoke weed!",
+ "body": "its good for your soul",
+ "url": "https://foo.example.com/",
+ "timestamp": "2018-04-20T06:09:00+00:00",
+ "locked": false,
+ "sticky": false,
+ "user_flag": "moderator",
+ "edited_at": "2018-06-09T04:21:09+00:00",
+ "moderated": false,
+ "comment_count": 0,
+ "upvotes": 0,
+ "downvotes": 0,
+ "thumbnail_1x": "https://example.com/very_long_image_url.jpg",
+ "thumbnail_2x": "https://example.com/very_long_image_url_2x.jpg"
+ }
+}
+~~~
+
+Note that thumbnails are very unlikely to be available before the webhook has
+been dispatched.
+
+### Edit submission
+
+~~~json
+{
+ "event": "edit_submission",
+ "subject": {
+ "before": {
+ "resource": "https://example.com/f/example/420/smoke-weed",
+ "id": 420,
+ "forum": "https://example.com/f/example",
+ "user": "https://example.com/user/emma",
+ "title": "Smoke weed!",
+ "body": "its good for your soul",
+ "url": "https://foo.example.com/",
+ "timestamp": "2018-04-20T06:09:00+00:00",
+ "locked": false,
+ "sticky": false,
+ "user_flag": "moderator",
+ "moderated": false,
+ "comment_count": 69,
+ "upvotes": 420,
+ "downvotes": 69,
+ "thumbnail_1x": "https://example.com/very_long_image_url.jpg",
+ "thumbnail_2x": "https://example.com/very_long_image_url_2x.jpg"
+ },
+ "after": {
+ "resource": "https://example.com/f/example/420/smoke-weed",
+ "id": 420,
+ "forum": "https://example.com/f/example",
+ "user": "https://example.com/user/emma",
+ "title": "Smoke weed!",
+ "body": "actually don't",
+ "url": "https://foo.example.com/",
+ "timestamp": "2018-04-20T06:09:00+00:00",
+ "locked": false,
+ "sticky": false,
+ "user_flag": "moderator",
+ "edited_at": "2018-06-09T04:21:09+00:00",
+ "moderated": false,
+ "comment_count": 69,
+ "upvotes": 420,
+ "downvotes": 69,
+ "thumbnail_1x": "https://example.com/very_long_image_url.jpg",
+ "thumbnail_2x": "https://example.com/very_long_image_url_2x.jpg"
+ }
+ }
+}
+~~~
+
+### New comment
+
+~~~json
+{
+ "event": "new_comment",
+ "subject": {
+ "resource": "https://example.com/f/example/420/comment/1312",
+ "id": 1312,
+ "body": "Raw body of comment",
+ "timestamp": "2018-04-20T06:09:00+00:00",
+ "user": "https://example.com/user/emma",
+ "submission": "https://example.com/f/example/420/smoke-weed",
+ "parent": "https://example.com/f/example/420/comment/69",
+ "reply_count": 0,
+ "upvotes": 0,
+ "downvotes": 0,
+ "soft_deleted": false,
+ "edited_at": "2018-06-09T04:21:09+00:00",
+ "moderated": false,
+ "user_flag": "admin"
+ }
+}
+~~~
+
+### Edit comment
+
+~~~json
+{
+ "event": "edit_comment",
+ "subject": {
+ "before": {
+ "resource": "https://example.com/f/example/420/comment/1312",
+ "id": 1312,
+ "body": "Smoke a little weed",
+ "timestamp": "2018-04-20T06:09:00+00:00",
+ "user": "https://example.com/user/emma",
+ "submission": "https://example.com/f/example/420/smoke-weed",
+ "parent": "https://example.com/f/example/420/comment/69",
+ "reply_count": 42069,
+ "upvotes": 420,
+ "downvotes": 69,
+ "soft_deleted": false,
+ "moderated": false,
+ "user_flag": "admin"
+ },
+ "after": {
+ "resource": "https://example.com/f/example/420/comment/1312",
+ "id": 1312,
+ "body": "Smoke lots of weed",
+ "timestamp": "2018-04-20T06:09:00+00:00",
+ "user": "https://example.com/user/emma",
+ "submission": "https://example.com/f/example/420/smoke-weed",
+ "parent": "https://example.com/f/example/420/comment/69",
+ "reply_count": 42069,
+ "upvotes": 420,
+ "downvotes": 69,
+ "soft_deleted": false,
+ "edited_at": "2018-06-09T04:21:09+00:00",
+ "moderated": false,
+ "user_flag": "admin"
+ }
+ }
+}
+~~~
diff --git a/src/Controller/CommentController.php b/src/Controller/CommentController.php
index b6573b8..b51b54c 100644
--- a/src/Controller/CommentController.php
+++ b/src/Controller/CommentController.php
@@ -7,6 +7,8 @@
use App\Entity\ForumLogCommentDeletion;
use App\Entity\Submission;
use App\Entity\User;
+use App\Event\EntityModifiedEvent;
+use App\Events;
use App\Form\CommentType;
use App\Form\Model\CommentData;
use App\Repository\CommentRepository;
@@ -15,6 +17,8 @@
use Doctrine\ORM\EntityManager;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
@@ -72,11 +76,12 @@ public function commentForm(
*
* @IsGranted("ROLE_USER")
*
- * @param EntityManager $em
- * @param Forum $forum
- * @param Submission $submission
- * @param Request $request
- * @param Comment|null $comment
+ * @param EntityManager $em
+ * @param Forum $forum
+ * @param Submission $submission
+ * @param Request $request
+ * @param EventDispatcherInterface $dispatcher
+ * @param Comment|null $comment
*
* @return Response
*/
@@ -85,6 +90,7 @@ public function comment(
Forum $forum,
Submission $submission,
Request $request,
+ EventDispatcherInterface $dispatcher,
Comment $comment = null
) {
$data = new CommentData();
@@ -100,6 +106,8 @@ public function comment(
$em->persist($reply);
$em->flush();
+ $dispatcher->dispatch(Events::NEW_COMMENT, new GenericEvent($reply));
+
return $this->redirectToRoute('comment', [
'forum_name' => $forum->getName(),
'submission_id' => $submission->getId(),
@@ -121,11 +129,12 @@ public function comment(
*
* @IsGranted("edit", subject="comment")
*
- * @param EntityManager $em
- * @param Forum $forum
- * @param Submission $submission
- * @param Comment $comment
- * @param Request $request
+ * @param EntityManager $em
+ * @param Forum $forum
+ * @param Submission $submission
+ * @param Comment $comment
+ * @param Request $request
+ * @param EventDispatcherInterface $dispatcher
*
* @return Response
*/
@@ -134,7 +143,8 @@ public function editComment(
Forum $forum,
Submission $submission,
Comment $comment,
- Request $request
+ Request $request,
+ EventDispatcherInterface $dispatcher
) {
$data = CommentData::createFromComment($comment);
@@ -142,10 +152,14 @@ public function editComment(
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
+ $before = clone $comment;
$data->updateComment($comment);
$em->flush();
+ $event = new EntityModifiedEvent($before, $comment);
+ $dispatcher->dispatch(Events::EDIT_COMMENT, $event);
+
return $this->redirectToRoute('comment', [
'forum_name' => $forum->getName(),
'submission_id' => $submission->getId(),
diff --git a/src/Controller/ForumController.php b/src/Controller/ForumController.php
index b69d1f9..49efcce 100644
--- a/src/Controller/ForumController.php
+++ b/src/Controller/ForumController.php
@@ -3,13 +3,16 @@
namespace App\Controller;
use App\Entity\Forum;
+use App\Entity\ForumWebhook;
use App\Entity\Moderator;
use App\Entity\User;
use App\Form\ForumAppearanceType;
use App\Form\ForumBanType;
use App\Form\ForumType;
+use App\Form\ForumWebhookType;
use App\Form\Model\ForumBanData;
use App\Form\Model\ForumData;
+use App\Form\Model\ForumWebhookData;
use App\Form\Model\ModeratorData;
use App\Form\ModeratorType;
use App\Form\PasswordConfirmType;
@@ -17,8 +20,10 @@
use App\Repository\ForumCategoryRepository;
use App\Repository\ForumLogEntryRepository;
use App\Repository\ForumRepository;
+use App\Repository\ForumWebhookRepository;
use App\Repository\SubmissionRepository;
use Doctrine\ORM\EntityManager;
+use Ramsey\Uuid\Uuid;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Component\HttpFoundation\Request;
@@ -29,6 +34,15 @@
* @Entity("user", expr="repository.findOneOrRedirectToCanonical(username, 'username')")
*/
final class ForumController extends AbstractController {
+ /**
+ * @var bool
+ */
+ private $enableWebhooks;
+
+ public function __construct(bool $enableWebhooks) {
+ $this->enableWebhooks = $enableWebhooks;
+ }
+
/**
* Show the front page of a given forum.
*
@@ -387,6 +401,135 @@ public function banHistory(Forum $forum, User $user, int $page = 1) {
]);
}
+ /**
+ * @IsGranted("moderator", subject="forum")
+ *
+ * @param Forum $forum
+ *
+ * @return Response
+ */
+ public function webhooks(Forum $forum) {
+ if (!$this->enableWebhooks) {
+ throw $this->createNotFoundException('Webhooks are not enabled');
+ }
+
+ return $this->render('forum/webhooks.html.twig', [
+ 'forum' => $forum,
+ ]);
+ }
+
+ /**
+ * @IsGranted("moderator", subject="forum")
+ *
+ * @param Forum $forum
+ * @param Request $request
+ * @param EntityManager $em
+ *
+ * @return Response
+ */
+ public function addWebhook(Forum $forum, Request $request, EntityManager $em) {
+ if (!$this->enableWebhooks) {
+ throw $this->createNotFoundException('Webhooks are not enabled');
+ }
+
+ $data = new ForumWebhookData();
+
+ $form = $this->createForm(ForumWebhookType::class, $data);
+ $form->handleRequest($request);
+
+ if ($form->isSubmitted() && $form->isValid()) {
+ $webhook = $data->toWebhook($forum);
+
+ $em->persist($webhook);
+ $em->flush();
+
+ $this->addFlash('success', 'flash.webhook_added');
+
+ return $this->redirectToRoute('forum_webhooks', [
+ 'forum_name' => $forum->getName(),
+ ]);
+ }
+
+ return $this->render('forum/add_webhook.html.twig', [
+ 'form' => $form->createView(),
+ 'forum' => $forum,
+ ]);
+ }
+
+ /**
+ * @Entity("webhook", expr="repository.findOneBy({forum: forum, id: webhook_id})")
+ * @IsGranted("moderator", subject="forum")
+ *
+ * @param Forum $forum
+ * @param ForumWebhook $webhook
+ * @param Request $request
+ * @param EntityManager $em
+ *
+ * @return Response
+ */
+ public function editWebhook(Forum $forum, ForumWebhook $webhook, Request $request, EntityManager $em) {
+ if (!$this->enableWebhooks) {
+ throw $this->createNotFoundException('Webhooks are not enabled');
+ }
+
+ $data = new ForumWebhookData($webhook);
+
+ $form = $this->createForm(ForumWebhookType::class, $data);
+ $form->handleRequest($request);
+
+ if ($form->isSubmitted() && $form->isValid()) {
+ $data->updateWebhook($webhook);
+
+ $em->flush();
+
+ $this->addFlash('success', 'flash.webhook_edited');
+
+ return $this->redirectToRoute('forum_webhooks', [
+ 'forum_name' => $forum->getName(),
+ ]);
+ }
+
+ return $this->render('forum/edit_webhook.html.twig', [
+ 'form' => $form->createView(),
+ 'forum' => $forum,
+ ]);
+ }
+
+ /**
+ * @IsGranted("moderator", subject="forum")
+ *
+ * @param Forum $forum
+ * @param Request $request
+ * @param ForumWebhookRepository $repository
+ * @param EntityManager $em
+ *
+ * @return Response
+ */
+ public function removeWebhook(Forum $forum, Request $request, ForumWebhookRepository $repository, EntityManager $em) {
+ if (!$this->enableWebhooks) {
+ throw $this->createNotFoundException('Webhooks are not enabled');
+ }
+
+ $this->validateCsrf('remove_webhook', $request->request->get('token'));
+
+ $ids = (array) $request->request->get('webhook');
+ $ids = \array_filter($ids, function ($id) {
+ return \is_string($id) && Uuid::isValid($id);
+ });
+
+ $webhooks = $repository->findBy(['id' => $ids, 'forum' => $forum]);
+
+ foreach ($webhooks as $webhook) {
+ $em->remove($webhook);
+ }
+
+ $em->flush();
+
+ return $this->redirectToRoute('forum_webhooks', [
+ 'forum_name' => $forum->getName(),
+ ]);
+ }
+
/**
* @IsGranted("moderator", subject="forum")
*
diff --git a/src/Controller/SubmissionController.php b/src/Controller/SubmissionController.php
index eff0fc7..d4a190c 100644
--- a/src/Controller/SubmissionController.php
+++ b/src/Controller/SubmissionController.php
@@ -7,6 +7,8 @@
use App\Entity\ForumLogSubmissionDeletion;
use App\Entity\ForumLogSubmissionLock;
use App\Entity\Submission;
+use App\Event\EntityModifiedEvent;
+use App\Events;
use App\Form\DeleteReasonType;
use App\Form\Model\SubmissionData;
use App\Form\SubmissionType;
@@ -14,6 +16,8 @@
use Doctrine\ORM\EntityManager;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -79,13 +83,19 @@ public function shortcut(Submission $submission) {
*
* @IsGranted("ROLE_USER")
*
- * @param EntityManager $em
- * @param Request $request
- * @param Forum $forum
+ * @param Forum|null $forum
+ * @param EntityManager $em
+ * @param Request $request
+ * @param EventDispatcherInterface $dispatcher
*
* @return Response
*/
- public function submit(EntityManager $em, Request $request, Forum $forum = null) {
+ public function submit(
+ Forum $forum = null,
+ EntityManager $em,
+ Request $request,
+ EventDispatcherInterface $dispatcher
+ ) {
$data = new SubmissionData($forum);
$form = $this->createForm(SubmissionType::class, $data);
@@ -97,6 +107,8 @@ public function submit(EntityManager $em, Request $request, Forum $forum = null)
$em->persist($submission);
$em->flush();
+ $dispatcher->dispatch(Events::NEW_SUBMISSION, new GenericEvent($submission));
+
return $this->redirectToRoute('submission', [
'forum_name' => $submission->getForum()->getName(),
'submission_id' => $submission->getId(),
@@ -113,26 +125,37 @@ public function submit(EntityManager $em, Request $request, Forum $forum = null)
/**
* @IsGranted("edit", subject="submission")
*
- * @param EntityManager $em
- * @param Forum $forum
- * @param Submission $submission
- * @param Request $request
+ * @param Forum $forum
+ * @param Submission $submission
+ * @param EntityManager $em
+ * @param Request $request
+ * @param EventDispatcherInterface $dispatcher
*
* @return Response
*/
- public function editSubmission(EntityManager $em, Forum $forum, Submission $submission, Request $request) {
+ public function editSubmission(
+ Forum $forum,
+ Submission $submission,
+ EntityManager $em,
+ Request $request,
+ EventDispatcherInterface $dispatcher
+ ) {
$data = SubmissionData::createFromSubmission($submission);
$form = $this->createForm(SubmissionType::class, $data);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
+ $before = clone $submission;
$data->updateSubmission($submission);
$em->flush();
$this->addFlash('notice', 'flash.submission_edited');
+ $event = new EntityModifiedEvent($before, $submission);
+ $dispatcher->dispatch(Events::EDIT_SUBMISSION, $event);
+
return $this->redirectToRoute('submission', [
'forum_name' => $forum->getName(),
'submission_id' => $submission->getId(),
diff --git a/src/Entity/Forum.php b/src/Entity/Forum.php
index 679dd81..230aa67 100644
--- a/src/Entity/Forum.php
+++ b/src/Entity/Forum.php
@@ -130,6 +130,13 @@ class Forum {
*/
private $logEntries;
+ /**
+ * @ORM\OneToMany(targetEntity="ForumWebhook", mappedBy="forum")
+ *
+ * @var ForumWebhook[]|Collection
+ */
+ private $webhooks;
+
public function __construct(
string $name,
string $title,
@@ -148,6 +155,7 @@ public function __construct(
$this->submissions = new ArrayCollection();
$this->subscriptions = new ArrayCollection();
$this->logEntries = new ArrayCollection();
+ $this->webhooks = new ArrayCollection();
if ($user) {
$this->addModerator(new Moderator($this, $user));
@@ -390,6 +398,25 @@ public function addLogEntry(ForumLogEntry $entry) {
}
}
+ /**
+ * @return ForumWebhook[]|Collection
+ */
+ public function getWebhooks(): Collection {
+ return $this->webhooks;
+ }
+
+ /**
+ * @param string $event
+ *
+ * @return ForumWebhook[]
+ */
+ public function getWebhooksByEvent(string $event): array {
+ $criteria = Criteria::create()
+ ->where(Criteria::expr()->eq('event', $event));
+
+ return $this->webhooks->matching($criteria)->toArray();
+ }
+
public static function normalizeName(string $name): string {
return mb_strtolower($name, 'UTF-8');
}
diff --git a/src/Entity/ForumWebhook.php b/src/Entity/ForumWebhook.php
new file mode 100644
index 0000000..7fddce5
--- /dev/null
+++ b/src/Entity/ForumWebhook.php
@@ -0,0 +1,105 @@
+id = Uuid::uuid4();
+ $this->forum = $forum;
+ $this->setEvent($event);
+ $this->url = $url;
+ $this->secretToken = $secretToken;
+ }
+
+ public function getId(): Uuid {
+ return $this->id;
+ }
+
+ public function getForum(): Forum {
+ return $this->forum;
+ }
+
+ public function getEvent(): string {
+ return $this->event;
+ }
+
+ public function setEvent(string $event): void {
+ if (!\in_array($event, self::EVENTS, true)) {
+ throw new \DomainException(\sprintf('Invalid event "%s"', $event));
+ }
+
+ $this->event = $event;
+ }
+
+ public function getUrl(): string {
+ return $this->url;
+ }
+
+ public function setUrl(string $url): void {
+ $this->url = $url;
+ }
+
+ public function getSecretToken(): ?string {
+ return $this->secretToken;
+ }
+
+ public function setSecretToken(?string $secretToken): void {
+ $this->secretToken = $secretToken;
+ }
+}
diff --git a/src/Entity/UserFlags.php b/src/Entity/UserFlags.php
index 105d270..5c8679b 100644
--- a/src/Entity/UserFlags.php
+++ b/src/Entity/UserFlags.php
@@ -5,15 +5,30 @@
/**
* Flags that apply to a submission or comment and which describes the role of
* the poster.
+ *
+ * @todo just use strings as constants instead - needs db changes, etc.
*/
final class UserFlags {
- const FLAGS = [
+ public const FLAGS = [
self::FLAG_NONE,
self::FLAG_MODERATOR,
self::FLAG_ADMIN,
];
- const FLAG_NONE = 0;
- const FLAG_MODERATOR = 1;
- const FLAG_ADMIN = 2;
+ public const FLAG_NONE = 0;
+ public const FLAG_MODERATOR = 1;
+ public const FLAG_ADMIN = 2;
+
+ public static function toReadable(int $userFlag): ?string {
+ switch ($userFlag) {
+ case self::FLAG_NONE:
+ return null;
+ case self::FLAG_MODERATOR:
+ return 'moderator';
+ case self::FLAG_ADMIN:
+ return 'admin';
+ default:
+ throw new \InvalidArgumentException();
+ }
+ }
}
diff --git a/src/Event/EntityModifiedEvent.php b/src/Event/EntityModifiedEvent.php
new file mode 100644
index 0000000..f3ab753
--- /dev/null
+++ b/src/Event/EntityModifiedEvent.php
@@ -0,0 +1,27 @@
+before = $before;
+ $this->after = $after;
+ }
+
+ public function getBefore() {
+ return $this->before;
+ }
+
+ public function getAfter() {
+ return $this->after;
+ }
+}
diff --git a/src/EventListener/WebhookListener.php b/src/EventListener/WebhookListener.php
new file mode 100644
index 0000000..256230c
--- /dev/null
+++ b/src/EventListener/WebhookListener.php
@@ -0,0 +1,184 @@
+client = $client;
+ $this->requestStack = $requestStack;
+ $this->serializer = $serializer;
+ $this->webhooksEnabled = $webhooksEnabled;
+ $this->logger = $logger ?: new NullLogger();
+ }
+
+ public function onKernelTerminate(PostResponseEvent $event): void {
+ if (
+ !$this->webhooksEnabled ||
+ $event->getRequestType() !== HttpKernelInterface::MASTER_REQUEST ||
+ !$event->getRequest()->attributes->has(self::QUEUE_KEY)
+ ) {
+ return;
+ }
+
+ // request not available from stack after kernel.terminate
+ $queue = $this->getQueue($event->getRequest());
+
+ if ($queue->isEmpty()) {
+ return;
+ }
+
+ $requests = function () use ($queue) {
+ while (!$queue->isEmpty()) {
+ $item = $queue->pop();
+
+ /* @var ForumWebhook $webhook */
+ $webhook = $item['webhook'];
+
+ if ($webhook->getSecretToken() !== null) {
+ $headers['X-Postmill-Secret'] = $webhook->getSecretToken();
+ }
+
+ $body = $this->serializer->serialize([
+ 'event' => $webhook->getEvent(),
+ 'subject' => $item['subject'],
+ ], 'json');
+
+ yield new GuzzleRequest('POST', $webhook->getUrl(), $headers ?? [], $body);
+ }
+ };
+
+ $pool = new Pool($this->client, $requests(), [
+ 'concurrency' => 5,
+ 'rejected' => function ($reason) {
+ // todo - log so forum mods can see this
+ $this->logger->warning('Webhook failed', [
+ 'reason' => $reason,
+ ]);
+ },
+ ]);
+
+ $pool->promise()->wait();
+ }
+
+ public function onNewSubmission(GenericEvent $event): void {
+ /* @var Submission $subject */
+ $subject = $event->getSubject();
+ $forum = $subject->getForum();
+
+ $this->addToQueue($forum, ForumWebhook::EVENT_NEW_SUBMISSION, $subject);
+ }
+
+ public function onEditSubmission(EntityModifiedEvent $event): void {
+ /* @var Submission $subject */
+ $subject = $event->getAfter();
+ $forum = $subject->getForum();
+
+ $this->addToQueue($forum, ForumWebhook::EVENT_EDIT_SUBMISSION, [
+ 'before' => $event->getBefore(),
+ 'after' => $event->getAfter(),
+ ]);
+ }
+
+ public function onNewComment(GenericEvent $event): void {
+ /* @var Comment $subject */
+ $subject = $event->getSubject();
+ $forum = $subject->getSubmission()->getForum();
+
+ $this->addToQueue($forum, ForumWebhook::EVENT_NEW_COMMENT, $subject);
+ }
+
+ public function onEditComment(EntityModifiedEvent $event): void {
+ /* @var Comment $subject */
+ $subject = $event->getAfter();
+ $forum = $subject->getSubmission()->getForum();
+
+ $this->addToQueue($forum, ForumWebhook::EVENT_EDIT_COMMENT, [
+ 'before' => $event->getBefore(),
+ 'after' => $event->getAfter(),
+ ]);
+ }
+
+ private function getQueue(Request $request = null): \SplStack {
+ $attributes = ($request ?: $this->requestStack->getMasterRequest())->attributes;
+
+ $queue = $attributes->get(self::QUEUE_KEY, new \SplStack());
+
+ if (!$attributes->has(self::QUEUE_KEY)) {
+ $attributes->set(self::QUEUE_KEY, $queue);
+ }
+
+ return $queue;
+ }
+
+ private function addToQueue(Forum $forum, string $eventName, $subject): void {
+ $webhooks = $forum->getWebhooksByEvent($eventName);
+ $queue = $this->getQueue();
+
+ foreach ($webhooks as $webhook) {
+ $queue->push(['webhook' => $webhook, 'subject' => $subject]);
+ }
+ }
+
+ public static function getSubscribedEvents(): array {
+ return [
+ Events::NEW_SUBMISSION => 'onNewSubmission',
+ Events::EDIT_SUBMISSION => 'onEditSubmission',
+ Events::NEW_COMMENT => 'onNewComment',
+ Events::EDIT_COMMENT => 'onEditComment',
+ KernelEvents::TERMINATE => 'onKernelTerminate',
+ ];
+ }
+}
diff --git a/src/Events.php b/src/Events.php
new file mode 100644
index 0000000..1637112
--- /dev/null
+++ b/src/Events.php
@@ -0,0 +1,12 @@
+getData() && $builder->getData()->getEntityId();
+
+ $builder
+ ->add('event', ChoiceType::class, [
+ 'choices' => ForumWebhook::EVENTS,
+ 'choice_label' => function ($key) {
+ return 'label.event.'.$key;
+ },
+ 'label' => 'label.event',
+ 'placeholder' => $editing ? null : 'placeholder.choose_one',
+ ])
+ ->add('url', UrlType::class, [
+ 'label' => 'label.url',
+ ])
+ ->add('secretToken', TextareaType::class, [
+ 'label' => 'label.secret_token',
+ 'required' => false,
+ ])
+ ->add('submit', SubmitType::class, [
+ 'label' => $editing ? 'action.save' : 'action.add',
+ ])
+ ;
+ }
+
+ public function configureOptions(OptionsResolver $resolver) {
+ $resolver->setDefaults([
+ 'data_class' => ForumWebhookData::class,
+ ]);
+ }
+}
diff --git a/src/Form/Model/ForumWebhookData.php b/src/Form/Model/ForumWebhookData.php
new file mode 100644
index 0000000..0d6d601
--- /dev/null
+++ b/src/Form/Model/ForumWebhookData.php
@@ -0,0 +1,65 @@
+entityId = $forumWebhook->getId();
+ $this->event = $forumWebhook->getEvent();
+ $this->url = $forumWebhook->getUrl();
+ $this->secretToken = $forumWebhook->getSecretToken();
+ }
+ }
+
+ public function toWebhook(Forum $forum): ForumWebhook {
+ return new ForumWebhook(
+ $forum,
+ $this->event,
+ $this->url,
+ $this->secretToken
+ );
+ }
+
+ public function updateWebhook(ForumWebhook $forumWebhook): void {
+ $forumWebhook->setEvent($this->event);
+ $forumWebhook->setUrl($this->url);
+ $forumWebhook->setSecretToken($this->secretToken);
+ }
+
+ public function getEntityId(): ?Uuid {
+ return $this->entityId;
+ }
+}
diff --git a/src/Migrations/Version20180325060258.php b/src/Migrations/Version20180325060258.php
new file mode 100644
index 0000000..4ae24db
--- /dev/null
+++ b/src/Migrations/Version20180325060258.php
@@ -0,0 +1,23 @@
+abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
+
+ $this->addSql('CREATE TABLE forum_webhooks (id UUID NOT NULL, forum_id BIGINT NOT NULL, event TEXT NOT NULL, url TEXT NOT NULL, secret_token TEXT DEFAULT NULL, PRIMARY KEY(id))');
+ $this->addSql('CREATE INDEX IDX_BE7FC23A29CCBAD0 ON forum_webhooks (forum_id)');
+ $this->addSql('COMMENT ON COLUMN forum_webhooks.id IS \'(DC2Type:uuid)\'');
+ $this->addSql('ALTER TABLE forum_webhooks ADD CONSTRAINT FK_BE7FC23A29CCBAD0 FOREIGN KEY (forum_id) REFERENCES forums (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ }
+
+ public function down(Schema $schema) {
+ $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
+
+ $this->addSql('DROP TABLE forum_webhooks');
+ }
+}
diff --git a/src/Repository/ForumWebhookRepository.php b/src/Repository/ForumWebhookRepository.php
new file mode 100644
index 0000000..58f83c4
--- /dev/null
+++ b/src/Repository/ForumWebhookRepository.php
@@ -0,0 +1,14 @@
+urlGenerator = $urlGenerator;
+ }
+
+ public function denormalize($data, $class, $format = null, array $context = []) {
+ // TODO: Implement denormalize() method.
+ }
+
+ public function supportsDenormalization($data, $type, $format = null): bool {
+ return false;
+ }
+
+ public function normalize($object, $format = null, array $context = []): array {
+ if (!$object instanceof Comment) {
+ throw new \InvalidArgumentException();
+ }
+
+ $normalized = [
+ 'resource' => $this->urlGenerator->generate('comment', [
+ 'forum_name' => $object->getSubmission()->getForum()->getName(),
+ 'submission_id' => $object->getSubmission()->getId(),
+ 'comment_id' => $object->getId(),
+ ], UrlGeneratorInterface::ABSOLUTE_URL),
+ 'id' => $object->getId(),
+ 'body' => $object->getBody(),
+ 'timestamp' => $object->getTimestamp()->format('c'),
+ 'user' => $this->urlGenerator->generate('user', [
+ 'username' => $object->getUser()->getUsername(),
+ ], UrlGeneratorInterface::ABSOLUTE_URL),
+ 'submission' => $this->urlGenerator->generate('submission', [
+ 'forum_name' => $object->getSubmission()->getForum()->getName(),
+ 'submission_id' => $object->getSubmission()->getId(),
+ 'slug' => Slugger::slugify($object->getSubmission()->getTitle()),
+ ], UrlGeneratorInterface::ABSOLUTE_URL),
+ 'parent' => $object->getParent()
+ ? $this->urlGenerator->generate('comment', [
+ 'forum_name' => $object->getSubmission()->getForum()->getName(),
+ 'submission_id' => $object->getSubmission()->getId(),
+ 'comment_id' => $object->getParent()->getId(),
+ ], UrlGeneratorInterface::ABSOLUTE_URL)
+ : null,
+ 'reply_count' => \count($object->getChildren()),
+ 'upvotes' => $object->getUpvotes(),
+ 'downvotes' => $object->getDownvotes(),
+ 'soft_deleted' => $object->isSoftDeleted(),
+ 'edited_at' => $object->getEditedAt()
+ ? $object->getEditedAt()->format('c')
+ : null,
+ 'moderated' => $object->isModerated(),
+ 'user_flag' => UserFlags::toReadable($object->getUserFlag()),
+ ];
+
+ return \array_filter($normalized, function ($element) {
+ return $element !== null;
+ });
+ }
+
+ public function supportsNormalization($data, $format = null): bool {
+ return $data instanceof Comment;
+ }
+}
diff --git a/src/Serializer/SubmissionNormalizer.php b/src/Serializer/SubmissionNormalizer.php
new file mode 100644
index 0000000..af01062
--- /dev/null
+++ b/src/Serializer/SubmissionNormalizer.php
@@ -0,0 +1,107 @@
+urlGenerator = $urlGenerator;
+ $this->cacheManager = $liipCacheManager;
+ $this->requestStack = $requestStack;
+ }
+
+ public function denormalize($data, $class, $format = null, array $context = []) {
+ // TODO
+ }
+
+ public function supportsDenormalization($data, $type, $format = null): bool {
+ return false;
+ }
+
+ public function normalize($object, $format = null, array $context = []): array {
+ if (!$object instanceof Submission) {
+ throw new \InvalidArgumentException();
+ }
+
+ $normalized = [
+ 'resource' => $this->urlGenerator->generate('submission', [
+ 'forum_name' => $object->getForum()->getName(),
+ 'submission_id' => $object->getId(),
+ 'slug' => Slugger::slugify($object->getTitle()),
+ ], UrlGeneratorInterface::ABSOLUTE_URL),
+ 'id' => $object->getId(),
+ 'forum' => $this->urlGenerator->generate('forum', [
+ 'forum_name' => $object->getForum()->getName(),
+ ], UrlGeneratorInterface::ABSOLUTE_URL),
+ 'user' => $this->urlGenerator->generate('user', [
+ 'username' => $object->getUser()->getUsername(),
+ ], UrlGeneratorInterface::ABSOLUTE_URL),
+ 'title' => $object->getTitle(),
+ 'body' => $object->getBody(),
+ 'url' => $object->getUrl(),
+ 'timestamp' => $object->getTimestamp()->format('c'),
+ 'locked' => $object->isLocked(),
+ 'sticky' => $object->isSticky(),
+ 'user_flag' => UserFlags::toReadable($object->getUserFlag()),
+ 'edited_at' => $object->getEditedAt()
+ ? $object->getEditedAt()->format('c')
+ : null,
+ 'moderated' => $object->isModerated(),
+ 'comment_count' => \count($object->getComments()),
+ 'upvotes' => $object->getUpvotes(),
+ 'downvotes' => $object->getDownvotes(),
+ ];
+
+ if ($object->getImage()) {
+ $normalized['thumbnail_1x'] = $this->cacheManager->generateUrl(
+ $object->getImage(),
+ 'submission_thumbnail_1x'
+ );
+
+ $normalized['thumbnail_2x'] = $this->cacheManager->generateUrl(
+ $object->getImage(),
+ 'submission_thumbnail_2x'
+ );
+ }
+
+ return \array_filter($normalized, function ($element) {
+ return $element !== null;
+ });
+ }
+
+ public function supportsNormalization($data, $format = null): bool {
+ return $data instanceof Submission;
+ }
+}
diff --git a/src/Twig/AppExtension.php b/src/Twig/AppExtension.php
index 0c94230..a8cfdc9 100644
--- a/src/Twig/AppExtension.php
+++ b/src/Twig/AppExtension.php
@@ -25,10 +25,16 @@ final class AppExtension extends AbstractExtension {
*/
private $version;
- public function __construct(string $siteName, ?string $branch, ?string $version) {
+ /**
+ * @var bool
+ */
+ private $enableWebhooks;
+
+ public function __construct(string $siteName, ?string $branch, ?string $version, bool $enableWebhooks) {
$this->siteName = $siteName;
$this->branch = $branch;
$this->version = $version;
+ $this->enableWebhooks = $enableWebhooks;
}
public function getFunctions(): array {
@@ -42,6 +48,9 @@ public function getFunctions(): array {
new TwigFunction('app_version', function () {
return $this->version;
}),
+ new TwigFunction('app_webhooks_enabled', function () {
+ return $this->enableWebhooks;
+ }),
];
}
}
diff --git a/symfony.lock b/symfony.lock
index 4a238af..c38bfba 100644
--- a/symfony.lock
+++ b/symfony.lock
@@ -89,6 +89,15 @@
"egulias/email-validator": {
"version": "2.1.3"
},
+ "eightpoints/guzzle-bundle": {
+ "version": "7.0",
+ "recipe": {
+ "repo": "github.com/symfony/recipes-contrib",
+ "branch": "master",
+ "version": "7.0",
+ "ref": "8be83603226d268ee8a93721b5bb77ec71d8d334"
+ }
+ },
"embed/embed": {
"version": "v3.2.2"
},
@@ -122,6 +131,15 @@
"gregwar/captcha-bundle": {
"version": "dev-master"
},
+ "guzzlehttp/guzzle": {
+ "version": "6.3.0"
+ },
+ "guzzlehttp/promises": {
+ "version": "v1.3.1"
+ },
+ "guzzlehttp/psr7": {
+ "version": "1.4.2"
+ },
"imagine/imagine": {
"version": "v0.6.3"
},
@@ -176,6 +194,9 @@
"psr/container": {
"version": "1.0.0"
},
+ "psr/http-message": {
+ "version": "1.0.1"
+ },
"psr/link": {
"version": "1.0.0"
},
diff --git a/templates/forum/_webhook_form.html.twig b/templates/forum/_webhook_form.html.twig
new file mode 100644
index 0000000..d9a9b16
--- /dev/null
+++ b/templates/forum/_webhook_form.html.twig
@@ -0,0 +1,9 @@
+{{ form_start(form) }}
+ {{ form_row(form.event) }}
+ {{ form_row(form.url) }}
+ {{ form_row(form.secretToken) }}
+
+
+ {{ form_widget(form.submit) }}
+
+{{ form_end(form) }}
diff --git a/templates/forum/add_webhook.html.twig b/templates/forum/add_webhook.html.twig
new file mode 100644
index 0000000..ac42e75
--- /dev/null
+++ b/templates/forum/add_webhook.html.twig
@@ -0,0 +1,15 @@
+{% extends 'forum/base.html.twig' %}
+
+{% block page_classes 'add-forum-webhook-page' %}
+{% block title 'title.add_forum_webhook'|trans({'%forum%': '/f/'~forum.name}) %}
+
+{% block body %}
+
+ {{ 'title.add_forum_webhook'|trans({'%forum%': '/f/%s '|format(
+ path('forum', {forum_name: forum.name})|e,
+ forum.name|e
+ ) })|raw }}
+
+
+ {{ include('forum/_webhook_form.html.twig', {form: form}, with_context=false) }}
+{% endblock %}
diff --git a/templates/forum/base.html.twig b/templates/forum/base.html.twig
index fa0b83f..94f7571 100644
--- a/templates/forum/base.html.twig
+++ b/templates/forum/base.html.twig
@@ -64,6 +64,13 @@
{{ 'nav.appearance'|trans }}
{% endset %}
{% set items = items|merge({('nav.appearance'|trans): item}) %}
+
+ {% if app_webhooks_enabled() %}
+ {% set item %}
+ {{ 'nav.webhooks'|trans }}
+ {% endset %}
+ {% set items = items|merge({('nav.webhooks'|trans): item}) %}
+ {% endif %}
{% endif %}
{% if is_granted('ROLE_ADMIN') %}
diff --git a/templates/forum/edit_webhook.html.twig b/templates/forum/edit_webhook.html.twig
new file mode 100644
index 0000000..41fba84
--- /dev/null
+++ b/templates/forum/edit_webhook.html.twig
@@ -0,0 +1,17 @@
+{% extends 'forum/base.html.twig' %}
+
+{% block page_classes 'edit-forum-webhook-page' %}
+{% block title 'title.editing_forum_webhook'|trans({'%forum%': '/f/'~forum.name}) %}
+
+{% block body %}
+
+ {{ 'title.editing_forum_webhook'|trans({
+ '%forum%': '%s '|format(
+ path('forum', {forum_name: forum.name})|e,
+ '/f/'~forum.name|e
+ )})|raw }}
+
+
+ {{ include('forum/_webhook_form.html.twig', {form: form}, with_context=false) }}
+{% endblock %}
+
diff --git a/templates/forum/webhooks.html.twig b/templates/forum/webhooks.html.twig
new file mode 100644
index 0000000..a5a2f3b
--- /dev/null
+++ b/templates/forum/webhooks.html.twig
@@ -0,0 +1,57 @@
+{% extends 'forum/base.html.twig' %}
+
+{% block page_classes 'forum-webhooks-page' %}
+{% block title 'title.forum_webhooks'|trans({'%forum%': '/f/'~forum.name}) %}
+
+{% block body %}
+
+ {{ 'title.forum_webhooks'|trans({'%forum%': '/f/%s '|format(
+ path('forum', {forum_name: forum.name})|e,
+ forum.name|e
+ ) })|raw }}
+
+
+
+
+ {{ 'nav.add_webhook'|trans }}
+
+
+
+ {% if forum.webhooks|length > 0 %}
+
+ {% else %}
+
+
{{ 'flash.no_entries_to_display'|trans }}
+
+ {% endif %}
+{% endblock %}
+
diff --git a/tests/Event/EntityModifiedEventTest.php b/tests/Event/EntityModifiedEventTest.php
new file mode 100644
index 0000000..1d44474
--- /dev/null
+++ b/tests/Event/EntityModifiedEventTest.php
@@ -0,0 +1,22 @@
+
Date: Sat, 31 Mar 2018 06:39:28 +0200
Subject: [PATCH 05/24] add webhook documentation to index
---
docs/index.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/index.md b/docs/index.md
index 3158ade..a73d0b5 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -2,6 +2,10 @@
* **[Getting started](../README.md#getting-started)**
+* **Reference**
+
+ * [Webhooks](webhooks.md)
+
* **Configuration & administration**
* [Database setup](database-setup.md)
From 495261a813e482f804cb6bfeb924c95b3d2983ab Mon Sep 17 00:00:00 2001
From: Emma
Date: Sat, 31 Mar 2018 20:13:09 +0200
Subject: [PATCH 06/24] refactor SubmissionImageListener to use guzzle
---
composer.json | 1 -
composer.lock | 3 +-
config/packages/eight_points_guzzle.yaml | 7 +
config/services.yaml | 5 +-
src/EventListener/SubmissionImageListener.php | 142 ++++++++++--------
5 files changed, 87 insertions(+), 71 deletions(-)
diff --git a/composer.json b/composer.json
index d882b8c..82c7651 100644
--- a/composer.json
+++ b/composer.json
@@ -16,7 +16,6 @@
"require": {
"php": "^7.1.3",
"ext-apcu": "*",
- "ext-curl": "*",
"ext-iconv": "*",
"ext-pdo_pgsql": "*",
"doctrine/doctrine-migrations-bundle": "^1.2",
diff --git a/composer.lock b/composer.lock
index ad67ac0..16a5bb2 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "content-hash": "05cf9ecf6e555b98c3adb6f3f8d9ced3",
+ "content-hash": "0d8eeac6d40a570be8925fb7940fdc02",
"packages": [
{
"name": "composer/ca-bundle",
@@ -7529,7 +7529,6 @@
"platform": {
"php": "^7.1.3",
"ext-apcu": "*",
- "ext-curl": "*",
"ext-iconv": "*",
"ext-pdo_pgsql": "*"
},
diff --git a/config/packages/eight_points_guzzle.yaml b/config/packages/eight_points_guzzle.yaml
index 30f9549..d3705f9 100644
--- a/config/packages/eight_points_guzzle.yaml
+++ b/config/packages/eight_points_guzzle.yaml
@@ -14,3 +14,10 @@ eight_points_guzzle:
# Find plugins here:
# https://github.com/8p/EightPointsGuzzleBundle#known-and-supported-plugins
plugin: ~
+
+ submission_image_client:
+ options:
+ timeout: 10
+ headers:
+ User-Agent: "Postmill/0.6 (https://postmill.xyz/)"
+ Accept: image/png, image/jpeg, image/gif
diff --git a/config/services.yaml b/config/services.yaml
index 13af52b..7a07afb 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -62,11 +62,8 @@ services:
App\EventListener\SubmissionImageListener:
arguments:
+ $client: "@eight_points_guzzle.client.submission_image_client"
$filesystem: "@oneup_flysystem.submission_images_filesystem"
- public: true
- tags:
- - { name: doctrine.event_listener, event: postPersist }
- - { name: kernel.event_listener, event: kernel.terminate }
App\EventListener\WebhookListener:
arguments:
diff --git a/src/EventListener/SubmissionImageListener.php b/src/EventListener/SubmissionImageListener.php
index 352d8f2..b983518 100644
--- a/src/EventListener/SubmissionImageListener.php
+++ b/src/EventListener/SubmissionImageListener.php
@@ -3,19 +3,23 @@
namespace App\EventListener;
use App\Entity\Submission;
-use Doctrine\Common\Persistence\ManagerRegistry;
-use Doctrine\ORM\Event\LifecycleEventArgs;
+use App\Events;
+use Doctrine\ORM\EntityManagerInterface;
use Embed\Embed;
use Embed\Exceptions\EmbedException;
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\GuzzleException;
use League\Flysystem\FileExistsException;
use League\Flysystem\FilesystemInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser;
use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
use Symfony\Component\HttpFoundation\RequestStack;
-use Symfony\Component\HttpKernel\Event\KernelEvent;
-use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpKernel\Event\PostResponseEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Validator\Constraints\Image;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
@@ -23,13 +27,18 @@
/**
* Download related image after submission.
*/
-final class SubmissionImageListener {
+final class SubmissionImageListener implements EventSubscriberInterface {
const QUEUE_KEY = 'submission_thumbnail_queue';
/**
- * @var ManagerRegistry
+ * @var Client
*/
- private $registry;
+ private $client;
+
+ /**
+ * @var EntityManagerInterface
+ */
+ private $entityManager;
/**
* @var FilesystemInterface
@@ -46,16 +55,21 @@ final class SubmissionImageListener {
*/
private $validator;
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
public function __construct(
- ManagerRegistry $registry,
+ Client $client,
+ EntityManagerInterface $entityManager,
FilesystemInterface $filesystem,
RequestStack $requestStack,
ValidatorInterface $validator,
LoggerInterface $logger = null
) {
- // we must inject the registry rather than the manager because of a
- // nasty infinite loop bug that would occur in the container
- $this->registry = $registry;
+ $this->client = $client;
+ $this->entityManager = $entityManager;
$this->filesystem = $filesystem;
$this->requestStack = $requestStack;
$this->validator = $validator;
@@ -65,18 +79,20 @@ public function __construct(
/**
* Stick every submission with a URL in a queue.
*
- * @param LifecycleEventArgs $args
+ * @param GenericEvent $event
*/
- public function postPersist(LifecycleEventArgs $args) {
+ public function onNewSubmission(GenericEvent $event) {
$request = $this->requestStack->getMasterRequest();
- $entity = $args->getEntity();
- if (!$request || !$entity instanceof Submission || !$entity->getUrl() || $entity->getImage()) {
+ /* @var Submission $submission */
+ $submission = $event->getSubject();
+
+ if (!$request || !$submission->getUrl() || $submission->getImage()) {
return;
}
$queue = $request->attributes->get(self::QUEUE_KEY, []);
- $queue[] = $entity;
+ $queue[] = $submission;
$request->attributes->set(self::QUEUE_KEY, $queue);
}
@@ -84,20 +100,16 @@ public function postPersist(LifecycleEventArgs $args) {
/**
* Loop through the queue at the end of the request and download the images.
*
- * @param KernelEvent $event
+ * @param PostResponseEvent $event
*/
- public function onKernelTerminate(KernelEvent $event) {
- if ($event->getRequestType() !== HttpKernelInterface::MASTER_REQUEST) {
- return;
- }
-
+ public function onKernelTerminate(PostResponseEvent $event) {
$queue = $event->getRequest()->attributes->get(self::QUEUE_KEY, []);
if (!$queue) {
return;
}
- /** @var Submission $submission */
+ /* @var Submission $submission */
foreach ($queue as $submission) {
try {
$embed = Embed::create($submission->getUrl());
@@ -111,7 +123,7 @@ public function onKernelTerminate(KernelEvent $event) {
}
}
- $this->registry->getManager()->flush();
+ $this->entityManager->flush();
}
/**
@@ -121,45 +133,21 @@ public function onKernelTerminate(KernelEvent $event) {
*
* @return string|null the final file name, or null if the download failed
*/
- private function getFilename(string $imageUrl) {
- error_clear_last();
+ private function getFilename(string $imageUrl): ?string {
+ $oldExceptionHandler = \set_error_handler(__NAMESPACE__.'\error_handler');
try {
- // fixme: don't create temporary files
- $tempFile = @tempnam(sys_get_temp_dir(), 'postmill');
- $fh = @fopen($tempFile, 'wb+');
-
- if (!$fh) {
- $this->logger->warning('Could not open file for writing', [
- 'error' => error_get_last(),
- ]);
-
- return null;
- }
+ $tempFile = \tempnam(\sys_get_temp_dir(), 'pml');
- // todo: refactor to use guzzle or something
- $ch = curl_init($imageUrl);
- curl_setopt($ch, CURLOPT_FILE, $fh);
- curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
- curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
- curl_setopt($ch, CURLOPT_TIMEOUT, 10);
+ $this->client->get($imageUrl, ['sink' => $tempFile]);
- $success = curl_exec($ch) && curl_getinfo($ch, CURLINFO_RESPONSE_CODE) == 200;
-
- if (!$success) {
- $this->logger->info('Bad HTTP response', [
- 'curl' => curl_getinfo($ch),
- ]);
-
- return null;
- }
-
- $imageConstraint = new Image(['detectCorrupted' => true]);
-
- $violations = $this->validator->validate($tempFile, $imageConstraint);
+ $violations = $this->validator->validate(
+ $tempFile,
+ new Image(['detectCorrupted' => true])
+ );
if (count($violations) > 0) {
- /** @var ConstraintViolationInterface $violation */
+ /* @var ConstraintViolationInterface $violation */
foreach ($violations as $violation) {
$message = $violation->getMessageTemplate();
$params = $violation->getParameters();
@@ -176,19 +164,45 @@ private function getFilename(string $imageUrl) {
$filename = sprintf('%s.%s', hash_file('sha256', $tempFile), $ext);
try {
+ $fh = fopen($tempFile, 'rb');
$success = $this->filesystem->writeStream($filename, $fh);
} catch (FileExistsException $e) {
$success = true;
}
- return $success ? $filename : null;
- } finally {
- if (isset($ch)) {
- @curl_close($ch);
+ if ($success) {
+ return $filename;
}
-
- @fclose($fh);
- @unlink($tempFile);
}
+ catch (GuzzleException $e) {
+ $this->logger->notice('Failed to download submission image', [
+ 'exception' => $e,
+ ]);
+ } catch (\ErrorException $e) {
+ $this->logger->warning($e->getMessage(), [
+ 'exception' => $e,
+ ]);
+ } finally {
+ \set_exception_handler($oldExceptionHandler);
+ @\unlink($tempFile);
+ @\fclose($fh);
+ }
+
+ return null;
+ }
+
+ public static function getSubscribedEvents() {
+ return [
+ Events::NEW_SUBMISSION => 'onNewSubmission',
+ KernelEvents::TERMINATE => 'onKernelTerminate',
+ ];
}
}
+
+function error_handler($severity, $message, $file, $line) {
+ if (!(error_reporting() & $severity)) {
+ return;
+ }
+
+ throw new \ErrorException($message, 0, $severity, $file, $line);
+}
From b12fecc2e14f561643b9c476a8e247bf48cb3a2f Mon Sep 17 00:00:00 2001
From: Emma
Date: Sat, 31 Mar 2018 23:54:22 +0200
Subject: [PATCH 07/24] factor EntityEditListener away
---
config/services.yaml | 4 --
src/Controller/CommentController.php | 2 +-
src/Controller/SubmissionController.php | 2 +-
src/EventListener/EntityEditListener.php | 74 ------------------------
src/Form/Model/CommentData.php | 12 +++-
src/Form/Model/SubmissionData.php | 20 +++++--
tests/Form/Model/CommentDataTest.php | 54 +++++++++++++++++
tests/Form/Model/SubmissionDataTest.php | 66 +++++++++++++++++++++
8 files changed, 148 insertions(+), 86 deletions(-)
delete mode 100644 src/EventListener/EntityEditListener.php
create mode 100644 tests/Form/Model/CommentDataTest.php
create mode 100644 tests/Form/Model/SubmissionDataTest.php
diff --git a/config/services.yaml b/config/services.yaml
index 7a07afb..9d66a0b 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -13,10 +13,6 @@ services:
autowire: true
public: false
- _instanceof:
- Doctrine\Common\EventSubscriber:
- tags: ['doctrine.event_subscriber']
-
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
diff --git a/src/Controller/CommentController.php b/src/Controller/CommentController.php
index b51b54c..f728460 100644
--- a/src/Controller/CommentController.php
+++ b/src/Controller/CommentController.php
@@ -153,7 +153,7 @@ public function editComment(
if ($form->isSubmitted() && $form->isValid()) {
$before = clone $comment;
- $data->updateComment($comment);
+ $data->updateComment($comment, $this->getUser());
$em->flush();
diff --git a/src/Controller/SubmissionController.php b/src/Controller/SubmissionController.php
index d4a190c..039e027 100644
--- a/src/Controller/SubmissionController.php
+++ b/src/Controller/SubmissionController.php
@@ -147,7 +147,7 @@ public function editSubmission(
if ($form->isSubmitted() && $form->isValid()) {
$before = clone $submission;
- $data->updateSubmission($submission);
+ $data->updateSubmission($submission, $this->getUser());
$em->flush();
diff --git a/src/EventListener/EntityEditListener.php b/src/EventListener/EntityEditListener.php
deleted file mode 100644
index 7abf731..0000000
--- a/src/EventListener/EntityEditListener.php
+++ /dev/null
@@ -1,74 +0,0 @@
-tokenStorage = $tokenStorage;
- }
-
- /**
- * @param PreUpdateEventArgs $args
- */
- public function preUpdate(PreUpdateEventArgs $args) {
- $entity = $args->getEntity();
-
- if (
- $entity instanceof Comment && $this->commentIsEdited($args) ||
- $entity instanceof Submission && $this->submissionIsEdited($args)
- ) {
- $entity->setEditedAt(new \DateTime('@'.time()));
-
- if ($this->isModerated($args->getEntity())) {
- $entity->setModerated(true);
- }
- }
- }
-
- private function commentIsEdited(PreUpdateEventArgs $args): bool {
- return $args->hasChangedField('body');
- }
-
- private function submissionIsEdited(PreUpdateEventArgs $args): bool {
- return $args->hasChangedField('body') ||
- $args->hasChangedField('title') ||
- $args->hasChangedField('url');
- }
-
- /**
- * @param object $entity
- *
- * @return bool
- */
- private function isModerated($entity): bool {
- $token = $this->tokenStorage->getToken();
-
- return $token &&
- $token->getUser() instanceof User &&
- $token->getUser() !== $entity->getUser();
- }
-
- /**
- * {@inheritdoc}
- */
- public function getSubscribedEvents() {
- return ['preUpdate'];
- }
-}
diff --git a/src/Form/Model/CommentData.php b/src/Form/Model/CommentData.php
index 8e00b59..3033a92 100644
--- a/src/Form/Model/CommentData.php
+++ b/src/Form/Model/CommentData.php
@@ -47,9 +47,17 @@ public function toComment(
);
}
- public function updateComment(Comment $comment) {
- $comment->setBody($this->body);
+ public function updateComment(Comment $comment, User $editingUser) {
$comment->setUserFlag($this->userFlag);
+
+ if ($this->body !== $comment->getBody()) {
+ $comment->setBody($this->body);
+ $comment->setEditedAt(new \DateTime('@'.time()));
+
+ if (!$comment->isModerated()) {
+ $comment->setModerated($comment->getUser() !== $editingUser);
+ }
+ }
}
/**
diff --git a/src/Form/Model/SubmissionData.php b/src/Form/Model/SubmissionData.php
index 590a49f..a7862c5 100644
--- a/src/Form/Model/SubmissionData.php
+++ b/src/Form/Model/SubmissionData.php
@@ -82,10 +82,22 @@ public function toSubmission(User $user, $ip): Submission {
);
}
- public function updateSubmission(Submission $submission) {
- $submission->setTitle($this->title);
- $submission->setUrl($this->url);
- $submission->setBody($this->body);
+ public function updateSubmission(Submission $submission, User $editingUser) {
+ if (
+ $this->url !== $submission->getUrl() ||
+ $this->title !== $submission->getTitle() ||
+ $this->body !== $submission->getBody()
+ ) {
+ $submission->setTitle($this->title);
+ $submission->setUrl($this->url);
+ $submission->setBody($this->body);
+ $submission->setEditedAt(new \DateTime('@'.time()));
+
+ if (!$submission->isModerated()) {
+ $submission->setModerated($submission->getUser() !== $editingUser);
+ }
+ }
+
$submission->setUserFlag($this->userFlag);
$submission->setSticky($this->sticky);
}
diff --git a/tests/Form/Model/CommentDataTest.php b/tests/Form/Model/CommentDataTest.php
new file mode 100644
index 0000000..ce072ba
--- /dev/null
+++ b/tests/Form/Model/CommentDataTest.php
@@ -0,0 +1,54 @@
+comment = $this->getMockBuilder(Comment::class)
+ ->setMethods(['getUser'])
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->comment
+ ->method('getUser')
+ ->willReturn($this->createMock(User::class));
+
+ $this->comment->setBody('foo');
+ }
+
+ public function testUpdate() {
+ $data = CommentData::createFromComment($this->comment);
+ $data->setBody('bar');
+ $data->updateComment($this->comment, $this->comment->getUser());
+
+ $this->assertEquals(new \DateTime('@'.time()), $this->comment->getEditedAt());
+ $this->assertFalse($this->comment->isModerated());
+
+ sleep(5);
+
+ $data->setBody('baz');
+ $data->updateComment($this->comment, $this->createMock(User::class));
+
+ $this->assertEquals(new \DateTime('@'.time()), $this->comment->getEditedAt());
+ $this->assertTrue($this->comment->isModerated());
+ }
+}
diff --git a/tests/Form/Model/SubmissionDataTest.php b/tests/Form/Model/SubmissionDataTest.php
new file mode 100644
index 0000000..49d0501
--- /dev/null
+++ b/tests/Form/Model/SubmissionDataTest.php
@@ -0,0 +1,66 @@
+submission = $this->getMockBuilder(Submission::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getUser', 'getForum'])
+ ->getMock();
+
+ $this->submission
+ ->method('getUser')
+ ->willReturn($this->createMock(User::class));
+
+ $this->submission->setUserFlag(0);
+ $this->submission->setTitle('foo');
+ $this->submission->setUrl('http://example.com/');
+ $this->submission->setBody('bar');
+ }
+
+ public function testUpdate() {
+ $data = SubmissionData::createFromSubmission($this->submission);
+ $data->setBody('bleh');
+ $data->updateSubmission($this->submission, $this->submission->getUser());
+
+ $this->assertEquals('bleh', $this->submission->getBody());
+ $this->assertFalse($this->submission->isModerated());
+ $this->assertEquals(new \DateTime('@'.time()), $this->submission->getEditedAt());
+
+ sleep(5);
+
+ $data->setTitle('poop');
+ $data->updateSubmission($this->submission, $this->createMock(User::class));
+
+ $this->assertTrue($this->submission->isModerated());
+ $this->assertEquals(new \DateTime('@'.time()), $this->submission->getEditedAt());
+
+ sleep(5);
+
+ $data->setUrl('https://example.net/a');
+ $data->updateSubmission($this->submission, $this->submission->getUser());
+
+ $this->assertTrue($this->submission->isModerated());
+ $this->assertEquals(new \DateTime('@'.time()), $this->submission->getEditedAt());
+ }
+}
From d892901aebd23408f6e467c032bb4905274e7ece Mon Sep 17 00:00:00 2001
From: Emma
Date: Sun, 1 Apr 2018 03:52:34 +0200
Subject: [PATCH 08/24] remove unnecessary service dependency
---
src/Serializer/SubmissionNormalizer.php | 8 --------
1 file changed, 8 deletions(-)
diff --git a/src/Serializer/SubmissionNormalizer.php b/src/Serializer/SubmissionNormalizer.php
index af01062..fe19298 100644
--- a/src/Serializer/SubmissionNormalizer.php
+++ b/src/Serializer/SubmissionNormalizer.php
@@ -6,7 +6,6 @@
use App\Entity\UserFlags;
use App\Utils\Slugger;
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
-use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
@@ -23,15 +22,9 @@ class SubmissionNormalizer extends AbstractNormalizer {
*/
private $cacheManager;
- /**
- * @var RequestStack
- */
- private $requestStack;
-
public function __construct(
UrlGeneratorInterface $urlGenerator,
CacheManager $liipCacheManager,
- RequestStack $requestStack,
ClassMetadataFactoryInterface $classMetadataFactory = null,
NameConverterInterface $nameConverter = null
) {
@@ -39,7 +32,6 @@ public function __construct(
$this->urlGenerator = $urlGenerator;
$this->cacheManager = $liipCacheManager;
- $this->requestStack = $requestStack;
}
public function denormalize($data, $class, $format = null, array $context = []) {
From e80c6524c85345142eb4a8e4a58a39208e5ba5e2 Mon Sep 17 00:00:00 2001
From: Tirifto
Date: Wed, 4 Apr 2018 20:48:27 +0200
Subject: [PATCH 09/24] Updated Esperanto translation.
Corrected errors, used easier words.
---
translations/messages.eo.yml | 186 ++++++++++++++++-----------------
translations/security.eo.yml | 1 +
translations/validators.eo.yml | 4 +-
3 files changed, 96 insertions(+), 95 deletions(-)
create mode 100644 translations/security.eo.yml
diff --git a/translations/messages.eo.yml b/translations/messages.eo.yml
index af8930e..e555de6 100644
--- a/translations/messages.eo.yml
+++ b/translations/messages.eo.yml
@@ -1,27 +1,27 @@
action:
upvote: Porvoĉdoni
downvote: Kontraŭvoĉdoni
- retract_upvote: Retract porvoĉdono
- retract_downvote: Retract kontraŭvoĉdono
+ retract_upvote: Repreni porvoĉdonon
+ retract_downvote: Repreni kontraŭvoĉdonon
add_moderator:
title: Aldoni kontrolanton al %forum%
ban_add:
- title: Forbari IP adreson
+ title: Forbari IP-adreson
ban_form:
- ip: IP adreso
- reason: Kialo por forbari
- expiry_date: Expires at (YYYY-MM-DD hh:mm)
+ ip: IP-adreso
+ reason: Kialo forbari
+ expiry_date: Finiĝos je (YYYY-MM-DD hh:mm)
ban: Forbari
user: Uzanto de IP
ban_list:
title: Forbaroj
- ip: IP adreso
+ ip: IP-adreso
timestamp: Tempo
- banned_by: Forbaris de
+ banned_by: Forbarita de
reason: Kialo
user: Uzanto
user_none: nenio
@@ -31,7 +31,7 @@ ban_list:
banned:
title: Forbaris
- message: Vi estas forbara!
+ message: Vi estas forbarita!
comments:
author_deleted: '[forigis]'
@@ -40,24 +40,24 @@ comments:
delete_thread: Forigi kun respondoj
edit: Redakti
permalink: Konstanta ligilo
- parent: Gepatro
+ parent: Gepatra afiŝo
reply: Respondi
- admin_ban: IP forbaro
- form_load_error: Eraro ŝarĝis formon. Bonvolu, denove provi.
+ admin_ban: IP-forbaro
+ form_load_error: Eraro pri enlego de formo. Bonvolu denove provi.
info: '%user% skribis %timestamp%'
info_at_timestamp: je %timestamp%
not_logged_in: Vi devas %login_link% aŭ %register_link% por komenti.
- not_logged_in_login_link_label: ensaluti
- not_logged_in_register_link_label: registri
+ not_logged_in_login_link_label: saluti
+ not_logged_in_register_link_label: registriĝi
viewing_thread: Vidi unu komentan fadenon.
thread_return: Vidi ĉiujn komentojn
return_to_forum: ← Reveni al %forum%
- edit_info: (redaktis %edited_at%)
- moderator_info: (kontrolanto redaktis %edited_at%)
+ edit_info: (redaktita je %edited_at%)
+ moderator_info: (kontrolanto redaktis je %edited_at%)
comment_form:
comment: Komento
- submit: Afiŝis
+ submit: Afiŝi
edit_title: Redaktas komenton
create_title: Skribas komenton
@@ -65,7 +65,7 @@ compose_message:
title: Kreas mesaĝon por %username%
create_forum:
- create_new_forum: Kreas novan forumon
+ create_new_forum: Kreas novan diskutejon
edit_forum:
title: Redaktas %forum%
@@ -74,46 +74,46 @@ edit_user:
title: Redaktas uzanton %username%
flash:
- ban_added: La IP adreso estas forbara.
- forum_deleted: La forumojn kaj ĉiujn enhavojn savis.
- forum_updated: La redaktojn savis.
- forum_moderator_added: La uzanto levigis al kontrolanto
- inbox_cleared: La enirkeston vakigis.
- reset_password_email_sent: Reŝargan retpoŝton sendis al specifita adreso.
- submission_deleted: La sendadon savis.
- submission_edited: La sendadon redaktis.
- user_password_updated: Via pasvorto ĝisdatigis.
+ ban_added: La IP adreso estas forbarita.
+ forum_deleted: La diskutejo kaj ties enhavo foriĝis.
+ forum_updated: La redaktoj konserviĝis.
+ forum_moderator_added: La uzanto fariĝis kontrolanto
+ inbox_cleared: La enirkesto vakiĝis.
+ reset_password_email_sent: Restariga retletero sendiĝis al la adreso, kiun vi specifis.
+ submission_deleted: La afiŝo foriĝis.
+ submission_edited: La afiŝo redaktiĝis.
+ user_password_updated: Via pasvorto ĝisdatiĝis.
user_settings_updated: Uzanto-agordoj ĝisdatigis.
- no_messages: Sen mesaĝoj montri.
+ no_messages: Sen mesaĝoj montreblaj.
forum:
moderators: Kontrolantoj
manage: Administri
- edit: Redakti fourmon
+ edit: Redakti diskutejon
subscribe: Aboni
unsubscribe: Malaboni
subscriber_count: '{0} Sen abonantoj|{1} %count% abonanto|[1,Inf[ %count% abonantoj'
- add_moderator: Aldoni kontrolanto
+ add_moderator: Aldoni kontrolanton
forum_form:
name: Nomo
title: Titolo
description: Priskribo
category: Kategorio
- create: Krei forumon
+ create: Krei diskutejon
save: Konservi ŝanĝojn
- delete: Savi forumon
- confirm_delete: Ĉu vi certe volas savi ĉi-tion forumon kaj ĉiujn komentojn kaj ĉiujn sendadojn?
- featured: Malfonen porti
- uncategorized_placeholder: (senkategorio)
+ delete: Forigi fdiskutejon
+ confirm_delete: Ĉu vi certe volas forigi ĉi tiun diskutejon kaj ĉiujn afiŝojn kun komentoj?
+ featured: Montri ĉefpaĝe
+ uncategorized_placeholder: (sen kategorio)
forum_list:
name: Nomo
title: Titolo
subscribers: Abonantoj
- page_title: Listo de forumoj
- create_forum: Krei forumon
- submission_count: Afiŝonombro
+ page_title: Listo de diskutejoj
+ create_forum: Krei diskutejon
+ submission_count: Da afiŝoj
list_view: Lista vido
category_view: Kategoria vido
uncategorized: Senkategoria vido
@@ -122,21 +122,21 @@ forum_moderators:
title: Kontrolantoj de /%forum_name%
username: Uzantonomo
since: Ekde
- last_seen: Laste vidis
+ last_seen: Laste vidita
front:
- subscribed_forums: Abonaj forumoj
- no_subscriptions: Vi ne havas neniun albonajn forumojn. Anstataŭe montras priatentitajn forumojn.
- featured_forums: Priatentitaj Forumoj
- no_forums: Estas neniuj priatentitaj forumoj por montri.
+ subscribed_forums: Abonitaj diskutejoj
+ no_subscriptions: Vi abonas neniun diskutejon. Anstataŭe montriĝas elstarigitaj diskutejoj.
+ featured_forums: Elstarigitaj diskutejoj
+ no_forums: Estas neniuj elstarigitaj diskutejoj montreblaj.
all: Ĉiuj
- featured: Priatentitaj
- subscribed: Abonaj
+ featured: Elstarigitaj
+ subscribed: Abonataj
inbox:
title: Enirkesto
- clear_inbox: Vakigi enirkeston
- empty: La enirkesto estas vakiga
+ clear_inbox: Malplenigi enirkeston
+ empty: La enirkesto estas malplena.
message_reply_head: 'Re: %title%'
label:
@@ -148,10 +148,10 @@ label:
toolbox: Ilaro
login_form:
- log_in: Ensaluti
+ log_in: Saluti
username: Uzantonomo
password: Pasvorto
- reset_password: Reŝargi pasvorton?
+ reset_password: Ĉu restarigi pasvorton?
markdown_type:
help: Sintaksa referenco
@@ -164,17 +164,17 @@ message_form:
moderator_form:
user: Uzantonomo
- submit: Aldoni tiel kontrolanto
+ submit: Aldoni kiel kontrolanton
nav:
submit: Afiŝi
- log_in: Ensaluti
- log_out: Elsaluti
+ log_in: Saluti
+ log_out: Adiaŭi
profile: Profilo
register: Registri
my_account: Mia konto
user_settings: Uzanto-agordoj
- forums: Forumoj
+ forums: Diskutejoj
inbox_count: Enirkesto (%count%)
messages: Mesaĝoj
wiki: Vikio
@@ -182,65 +182,65 @@ nav:
previous: Antaŭa
request_password_reset:
- title: Peti pasvortan reŝargon ligilon
+ title: Peti ligilon al pasvorta restarigo
multiple_accounts_notice: >
- Se vi havas multaj kontoj estas registra kun unu retpoŝtadreso vi ricevos unu retpoŝto po konto.
+ Se vi havas multaj kontoj estas registritajn kun unu retpoŝtadreso, vi ricevos unu retleteron po konto.
no_confirmation_notice: >
- Se vi neniam ricevos retpoŝton, la adreso vi tajpis eble estas malprava.
- Por privateca kialoj, ni ne konfirmos se uzanto kun retpoŝto ekzistas.
+ Se vi neniam ricevos retleteron, la adreso kiun vi donis eble estas malĝusta.
+ Por privatecaj kialoj, ni ne konfirmos ĉu uzanto kun retpoŝtadreso ekzistas.
request_password_reset_form:
email: Retpoŝtadreso
submit: Sendi
reset_password:
- email_subject: '%site_name% - Reŝargi pasvorton por uzanto %username%'
+ email_subject: '%site_name% - Restarigi pasvorton por uzanto %username%'
# this should be kept at 76 columns --------------------------------------------
email_body: |
- Iu petis pasvortan reŝargon por via %site_name% konto. Por reŝargi pasvoton,
- klicku la ligilo sub:
+ Iu petis pasvortan restarigon por via konto ĉe %site_name%. Por
+ restarigi pasvorton, alklaku la ĉi-suban ligilon:
%reset_link%
- Se vi ne petis pasvortan reŝargon, vi povas sendanĝere malatenti ĉi-tion
- mesaĝon.
+ Se vi ne petis pasvortan restarigon, vi povas sendanĝere malatenti
+ ĉi tiun mesaĝon.
- title: Reŝargi pasvorton
+ title: Restarigi pasvorton
site_footer:
- version: Uzanta %app% %version% (%branch%). Farita kun ☭.
+ version: Povigita de %app% %version% (%branch%). Farita kun ☭.
submission_form:
title: Titolo
url: URL
body: Korpo
- forum: Forumo
+ forum: Diskutejo
sticky: Gluita
- create: Krei submission
- edit: Rekonti submission
- delete: Savi submission
- confirm_delete: Ĉu vi certe volas savi ĉi-tion submission?
+ create: Krei afiŝon
+ edit: Redakti afiŝon
+ delete: Forigi afiŝon
+ confirm_delete: Ĉu vi certe volas forigi ĉi tiun afiŝon?
submissions:
comments: '{0} Sen komentoj|{1} %count% komento|[1,Inf[ %count% komentoj'
edit: Redakti
- info_with_forum_name: Afiŝis de %submitter% %timestamp% en %forum%
- info_without_forum_name: Afiŝis de %submitter% %timestamp%
+ info_with_forum_name: Afiŝis %submitter% %timestamp% en %forum%
+ info_without_forum_name: Afiŝis %submitter% %timestamp%
info_at_timestamp: je %timestamp%
- sort_by_hot: Varma
- sort_by_new: Nova
- sort_by_top: Supra
- sort_by_controversial: Polemika
+ sort_by_hot: Furoraj
+ sort_by_new: Novaj
+ sort_by_top: Supraj
+ sort_by_controversial: Disputigaj
total_votes: '{1} %count% voĉdono|[0,Inf[ %count% voĉdonoj'
vote_stats: (+%up%, −%down%)
- ip_ban: IP forbaro
+ ip_ban: IP-forbaro
edit_info: (redaktis %edited_at%)
moderator_info: (kontrolanto redaktis %edit_at%)
time:
on_timestamp: je %timestamp%
- earlier_format: '%relative_time% frue'
+ earlier_format: '%relative_time% antaŭe'
later_format: '%relative_time% poste'
title:
@@ -250,14 +250,14 @@ user:
submissions: Afiŝoj
comments: Komentoj
moderates: '%username% estas kontrolanto por:'
- username_rules: Permesita karakteroj estas A ĝis Z, a ĝis z, 0 ĝis 9, kaj substrekas.
- password_rules: Minimuma de 8 karakteroj; maksimuma de 72 ĉar limigoj de la «bcrypt» algoritmo.
- email_optional: Provizi retpoŝton nedevigas. Ni nur uzos ĝin por reagordi pasvortojn kaj retpoŝti du-faktorajn identokontrolojn.
+ username_rules: Permesitaj signoj estas A ĝis Z, a ĝis z, 0 ĝis 9, kaj substrekoj.
+ password_rules: Minimume 8 signoj; maksimume 72 pro limigoj de la algoritmo «bcrypt».
+ email_optional: Retpoŝtadreso malnepras. Ni nur uzos ĝin por reagordi pasvortojn kaj retpoŝti du-fazajn identokontrolojn.
message: Sendi mesaĝon
- registered: Registris %timestamp%
+ registered: Registriĝis %timestamp%
user_flag:
- post_as_label: Afiŝi tiel
+ post_as_label: Afiŝi kiel
none_label: (sen)
admin: administranto
admin_label: Administranto
@@ -270,37 +270,37 @@ user_form:
repeat_password: Pasvorto (ripetu)
new_password: Nova pasvorto
repeat_new_password: Nova pasvorto (ripetu)
- email: Retpoŝto
- register: Registri
+ email: Retpoŝtadreso
+ register: Registriĝi
verification: Certigo
- save: Konservi modifiĝojn
+ save: Konservi ŝanĝojn
user_settings:
- title: Redaktante uzanton-agordojn por %username%
+ title: Redaktante uzanto-agordojn por %username%
user_settings_form:
locale: Lingvo
night_mode: Nokta reĝimo
- save: Konservi modifiĝojn
+ save: Konservi ŝanĝojn
wiki:
not_found_title: Ne trovis paĝon.
- not_found_message: La petitis paĝon ne trovitis.
- create_this_page: Krei ĉi-tiun paĝon
+ not_found_message: Petita paĝo ne troviĝis.
+ create_this_page: Krei ĉi tiun paĝon
editing_title: Redaktas "%path%"
create_title: Kreas "%path%"
history_title: Montras historion de "%path%"
- revision_time: Redaktis
+ revision_time: Redaktita
user: Uzanto
- id_label: ID
- locked_notice: La paĝon ŝlositis. Nur administrantoj povas redakti ĝin.
+ id_label: Identigilo
+ locked_notice: La paĝo estas ŝlosita. Nur administrantoj povas redakti ĝin.
page_title: Paĝa Titolo
path: Dosierindiko
all_pages: Ĉiuj paĝoj
page_history: Paĝa historio
edit_page: Redakti paĝon
navigation: Navigado
- last_edited: Laste redaktis de %username% %timestamp%
+ last_edited: Laste redaktis %username% %timestamp%
wiki_form:
title: Titolo
diff --git a/translations/security.eo.yml b/translations/security.eo.yml
new file mode 100644
index 0000000..de46a26
--- /dev/null
+++ b/translations/security.eo.yml
@@ -0,0 +1 @@
+'Your account has been banned.': 'Via konto estas forbarita.'
diff --git a/translations/validators.eo.yml b/translations/validators.eo.yml
index cbb7e6a..0ece474 100644
--- a/translations/validators.eo.yml
+++ b/translations/validators.eo.yml
@@ -1,3 +1,3 @@
'The comment must not be empty.': 'La komento devas ne malpleni.'
-'The name must contain only contain letters, numbers, and underscores.': 'La nomo devas nur enhavi karakterojn, nombrojn, kaj substrekojn.'
-'No such user.': 'Ne tia uzanto.'
+'The name must contain only contain letters, numbers, and underscores.': 'La nomo devas enhavi nur signojn, nombrojn, kaj substrekojn.'
+'No such user.': 'Neniu uzanto tia.'
From 1c9a69a547fb0bd8727d3dd4c1621c36564039bc Mon Sep 17 00:00:00 2001
From: Emma
Date: Thu, 5 Apr 2018 07:49:48 +0200
Subject: [PATCH 10/24] update dependencies (fixes cache warmup)
---
composer.lock | 431 +++++++++++++++++++++++++-------------------------
1 file changed, 216 insertions(+), 215 deletions(-)
diff --git a/composer.lock b/composer.lock
index 16a5bb2..04e4803 100644
--- a/composer.lock
+++ b/composer.lock
@@ -8,16 +8,16 @@
"packages": [
{
"name": "composer/ca-bundle",
- "version": "1.1.0",
+ "version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
- "reference": "943b2c4fcad1ef178d16a713c2468bf7e579c288"
+ "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/ca-bundle/zipball/943b2c4fcad1ef178d16a713c2468bf7e579c288",
- "reference": "943b2c4fcad1ef178d16a713c2468bf7e579c288",
+ "url": "https://api.github.com/repos/composer/ca-bundle/zipball/d2c0a83b7533d6912e8d516756ebd34f893e9169",
+ "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169",
"shasum": ""
},
"require": {
@@ -26,7 +26,7 @@
"php": "^5.3.2 || ^7.0"
},
"require-dev": {
- "phpunit/phpunit": "^4.8.35",
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5",
"psr/log": "^1.0",
"symfony/process": "^2.5 || ^3.0 || ^4.0"
},
@@ -60,7 +60,7 @@
"ssl",
"tls"
],
- "time": "2017-11-29T09:37:33+00:00"
+ "time": "2018-03-29T19:57:20+00:00"
},
{
"name": "doctrine/annotations",
@@ -346,16 +346,16 @@
},
{
"name": "doctrine/dbal",
- "version": "v2.6.3",
+ "version": "v2.7.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
- "reference": "e3eed9b1facbb0ced3a0995244843a189e7d1b13"
+ "reference": "f76bf5ef631cec551a86c2291fc749534febebf1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/dbal/zipball/e3eed9b1facbb0ced3a0995244843a189e7d1b13",
- "reference": "e3eed9b1facbb0ced3a0995244843a189e7d1b13",
+ "url": "https://api.github.com/repos/doctrine/dbal/zipball/f76bf5ef631cec551a86c2291fc749534febebf1",
+ "reference": "f76bf5ef631cec551a86c2291fc749534febebf1",
"shasum": ""
},
"require": {
@@ -364,9 +364,11 @@
"php": "^7.1"
},
"require-dev": {
- "phpunit/phpunit": "^5.4.6",
+ "doctrine/coding-standard": "^4.0",
+ "phpunit/phpunit": "^7.0",
"phpunit/phpunit-mock-objects": "!=3.2.4,!=3.2.5",
- "symfony/console": "2.*||^3.0"
+ "symfony/console": "^2.0.5||^3.0",
+ "symfony/phpunit-bridge": "^3.4.5|^4.0.5"
},
"suggest": {
"symfony/console": "For helpful console commands such as SQL execution and import of files."
@@ -377,7 +379,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.6.x-dev"
+ "dev-master": "2.7.x-dev"
}
},
"autoload": {
@@ -415,7 +417,7 @@
"persistence",
"queryobject"
],
- "time": "2017-11-19T13:38:54+00:00"
+ "time": "2018-04-01T23:33:17+00:00"
},
{
"name": "doctrine/doctrine-bundle",
@@ -504,39 +506,39 @@
},
{
"name": "doctrine/doctrine-cache-bundle",
- "version": "1.3.2",
+ "version": "1.3.3",
"source": {
"type": "git",
"url": "https://github.com/doctrine/DoctrineCacheBundle.git",
- "reference": "9baecbd6bfdd1123b0cf8c1b88fee0170a84ddd1"
+ "reference": "4c8e363f96427924e7e519c5b5119b4f54512697"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/DoctrineCacheBundle/zipball/9baecbd6bfdd1123b0cf8c1b88fee0170a84ddd1",
- "reference": "9baecbd6bfdd1123b0cf8c1b88fee0170a84ddd1",
+ "url": "https://api.github.com/repos/doctrine/DoctrineCacheBundle/zipball/4c8e363f96427924e7e519c5b5119b4f54512697",
+ "reference": "4c8e363f96427924e7e519c5b5119b4f54512697",
"shasum": ""
},
"require": {
"doctrine/cache": "^1.4.2",
"doctrine/inflector": "~1.0",
"php": ">=5.3.2",
- "symfony/doctrine-bridge": "~2.2|~3.0|~4.0"
+ "symfony/doctrine-bridge": "~2.7|~3.3|~4.0"
},
"require-dev": {
"instaclick/coding-standard": "~1.1",
"instaclick/object-calisthenics-sniffs": "dev-master",
"instaclick/symfony2-coding-standard": "dev-remaster",
- "phpunit/phpunit": "~4",
+ "phpunit/phpunit": "~4|~5",
"predis/predis": "~0.8",
"satooshi/php-coveralls": "^1.0",
"squizlabs/php_codesniffer": "~1.5",
- "symfony/console": "~2.2|~3.0|~4.0",
- "symfony/finder": "~2.2|~3.0|~4.0",
- "symfony/framework-bundle": "~2.2|~3.0|~4.0",
- "symfony/phpunit-bridge": "~2.7|~3.0|~4.0",
- "symfony/security-acl": "~2.3|~3.0",
- "symfony/validator": "~2.2|~3.0|~4.0",
- "symfony/yaml": "~2.2|~3.0|~4.0"
+ "symfony/console": "~2.7|~3.3|~4.0",
+ "symfony/finder": "~2.7|~3.3|~4.0",
+ "symfony/framework-bundle": "~2.7|~3.3|~4.0",
+ "symfony/phpunit-bridge": "~2.7|~3.3|~4.0",
+ "symfony/security-acl": "~2.7|~3.3",
+ "symfony/validator": "~2.7|~3.3|~4.0",
+ "symfony/yaml": "~2.7|~3.3|~4.0"
},
"suggest": {
"symfony/security-acl": "For using this bundle to cache ACLs"
@@ -588,7 +590,7 @@
"cache",
"caching"
],
- "time": "2017-10-12T17:23:29+00:00"
+ "time": "2018-03-27T09:22:12+00:00"
},
{
"name": "doctrine/doctrine-migrations-bundle",
@@ -1420,16 +1422,16 @@
},
{
"name": "guzzlehttp/guzzle",
- "version": "6.3.0",
+ "version": "6.3.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
- "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699"
+ "reference": "68d0ea14d5a3f42a20e87632a5f84931e2709c90"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699",
- "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/68d0ea14d5a3f42a20e87632a5f84931e2709c90",
+ "reference": "68d0ea14d5a3f42a20e87632a5f84931e2709c90",
"shasum": ""
},
"require": {
@@ -1439,7 +1441,7 @@
},
"require-dev": {
"ext-curl": "*",
- "phpunit/phpunit": "^4.0 || ^5.0",
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4",
"psr/log": "^1.0"
},
"suggest": {
@@ -1448,7 +1450,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "6.2-dev"
+ "dev-master": "6.3-dev"
}
},
"autoload": {
@@ -1481,7 +1483,7 @@
"rest",
"web service"
],
- "time": "2017-06-22T18:50:49+00:00"
+ "time": "2018-03-26T16:33:04+00:00"
},
{
"name": "guzzlehttp/promises",
@@ -1709,16 +1711,16 @@
},
{
"name": "league/commonmark",
- "version": "0.17.1",
+ "version": "0.17.5",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
- "reference": "38b2fca6b8493abd9314fcf6b5b042cfba6a0f11"
+ "reference": "82d7ab62d7f68391cb9d323f3ccce50be24a5369"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/38b2fca6b8493abd9314fcf6b5b042cfba6a0f11",
- "reference": "38b2fca6b8493abd9314fcf6b5b042cfba6a0f11",
+ "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/82d7ab62d7f68391cb9d323f3ccce50be24a5369",
+ "reference": "82d7ab62d7f68391cb9d323f3ccce50be24a5369",
"shasum": ""
},
"require": {
@@ -1774,7 +1776,7 @@
"markdown",
"parser"
],
- "time": "2018-03-18T13:01:30+00:00"
+ "time": "2018-03-29T14:35:19+00:00"
},
{
"name": "league/flysystem",
@@ -2320,16 +2322,16 @@
},
{
"name": "paragonie/random_compat",
- "version": "v2.0.11",
+ "version": "v2.0.12",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
- "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8"
+ "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/paragonie/random_compat/zipball/5da4d3c796c275c55f057af5a643ae297d96b4d8",
- "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8",
+ "url": "https://api.github.com/repos/paragonie/random_compat/zipball/258c89a6b97de7dfaf5b8c7607d0478e236b04fb",
+ "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb",
"shasum": ""
},
"require": {
@@ -2364,7 +2366,7 @@
"pseudorandom",
"random"
],
- "time": "2017-09-27T21:40:39+00:00"
+ "time": "2018-04-04T21:24:14+00:00"
},
{
"name": "phpdocumentor/reflection-common",
@@ -3169,7 +3171,7 @@
},
{
"name": "symfony/asset",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/asset.git",
@@ -3225,16 +3227,16 @@
},
{
"name": "symfony/cache",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/cache.git",
- "reference": "fcffcf7f26d232b64329f37182defe253caa06b0"
+ "reference": "681c245e629409a2f1ded6bf783e833d291d8af2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/cache/zipball/fcffcf7f26d232b64329f37182defe253caa06b0",
- "reference": "fcffcf7f26d232b64329f37182defe253caa06b0",
+ "url": "https://api.github.com/repos/symfony/cache/zipball/681c245e629409a2f1ded6bf783e833d291d8af2",
+ "reference": "681c245e629409a2f1ded6bf783e833d291d8af2",
"shasum": ""
},
"require": {
@@ -3290,20 +3292,20 @@
"caching",
"psr6"
],
- "time": "2018-02-11T17:17:44+00:00"
+ "time": "2018-04-02T14:35:51+00:00"
},
{
"name": "symfony/config",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
- "reference": "289eadd3771f7682ea2540e4925861c18ec5b4d0"
+ "reference": "7c19370ab04e9ac05b74a504198e165f5ccf6dd8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/289eadd3771f7682ea2540e4925861c18ec5b4d0",
- "reference": "289eadd3771f7682ea2540e4925861c18ec5b4d0",
+ "url": "https://api.github.com/repos/symfony/config/zipball/7c19370ab04e9ac05b74a504198e165f5ccf6dd8",
+ "reference": "7c19370ab04e9ac05b74a504198e165f5ccf6dd8",
"shasum": ""
},
"require": {
@@ -3352,20 +3354,20 @@
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
- "time": "2018-02-04T16:43:51+00:00"
+ "time": "2018-03-19T22:35:49+00:00"
},
{
"name": "symfony/console",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "555c8dbe0ae9e561740451eabdbed2cc554b6a51"
+ "reference": "aad9a6fe47319f22748fd764f52d3a7ca6fa6b64"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/555c8dbe0ae9e561740451eabdbed2cc554b6a51",
- "reference": "555c8dbe0ae9e561740451eabdbed2cc554b6a51",
+ "url": "https://api.github.com/repos/symfony/console/zipball/aad9a6fe47319f22748fd764f52d3a7ca6fa6b64",
+ "reference": "aad9a6fe47319f22748fd764f52d3a7ca6fa6b64",
"shasum": ""
},
"require": {
@@ -3420,20 +3422,20 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2018-02-26T15:55:47+00:00"
+ "time": "2018-04-03T05:24:00+00:00"
},
{
"name": "symfony/debug",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
- "reference": "1721e4e7effb23480966690cdcdc7d2a4152d489"
+ "reference": "5961d02d48828671f5d8a7805e06579d692f6ede"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/debug/zipball/1721e4e7effb23480966690cdcdc7d2a4152d489",
- "reference": "1721e4e7effb23480966690cdcdc7d2a4152d489",
+ "url": "https://api.github.com/repos/symfony/debug/zipball/5961d02d48828671f5d8a7805e06579d692f6ede",
+ "reference": "5961d02d48828671f5d8a7805e06579d692f6ede",
"shasum": ""
},
"require": {
@@ -3476,20 +3478,20 @@
],
"description": "Symfony Debug Component",
"homepage": "https://symfony.com",
- "time": "2018-02-28T21:50:02+00:00"
+ "time": "2018-04-03T05:24:00+00:00"
},
{
"name": "symfony/dependency-injection",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "93ad14f124beacf16894b64bb5b3cdd5b4367e38"
+ "reference": "9f1cea656afc5512c6f5e58d61fcea12acee113e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/93ad14f124beacf16894b64bb5b3cdd5b4367e38",
- "reference": "93ad14f124beacf16894b64bb5b3cdd5b4367e38",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/9f1cea656afc5512c6f5e58d61fcea12acee113e",
+ "reference": "9f1cea656afc5512c6f5e58d61fcea12acee113e",
"shasum": ""
},
"require": {
@@ -3547,20 +3549,20 @@
],
"description": "Symfony DependencyInjection Component",
"homepage": "https://symfony.com",
- "time": "2018-03-05T18:28:26+00:00"
+ "time": "2018-04-02T09:52:41+00:00"
},
{
"name": "symfony/doctrine-bridge",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/doctrine-bridge.git",
- "reference": "404becb75de28db14134d662f5b9b78b9a262acb"
+ "reference": "6743ff309bee333f885b237d04e81652f73f51a3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/404becb75de28db14134d662f5b9b78b9a262acb",
- "reference": "404becb75de28db14134d662f5b9b78b9a262acb",
+ "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/6743ff309bee333f885b237d04e81652f73f51a3",
+ "reference": "6743ff309bee333f885b237d04e81652f73f51a3",
"shasum": ""
},
"require": {
@@ -3626,11 +3628,11 @@
],
"description": "Symfony Doctrine Bridge",
"homepage": "https://symfony.com",
- "time": "2018-02-04T13:08:26+00:00"
+ "time": "2018-03-19T22:35:49+00:00"
},
{
"name": "symfony/event-dispatcher",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
@@ -3693,7 +3695,7 @@
},
{
"name": "symfony/expression-language",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/expression-language.git",
@@ -3743,7 +3745,7 @@
},
{
"name": "symfony/filesystem",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
@@ -3792,16 +3794,16 @@
},
{
"name": "symfony/finder",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "44a796d2ecc2a16a5fc8f2956a34ee617934d55f"
+ "reference": "c72995d9f5999b3fcdd8660c0c9690243252e1e1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/44a796d2ecc2a16a5fc8f2956a34ee617934d55f",
- "reference": "44a796d2ecc2a16a5fc8f2956a34ee617934d55f",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/c72995d9f5999b3fcdd8660c0c9690243252e1e1",
+ "reference": "c72995d9f5999b3fcdd8660c0c9690243252e1e1",
"shasum": ""
},
"require": {
@@ -3837,20 +3839,20 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
- "time": "2018-03-05T18:28:26+00:00"
+ "time": "2018-04-02T09:52:41+00:00"
},
{
"name": "symfony/flex",
- "version": "v1.0.76",
+ "version": "v1.0.78",
"source": {
"type": "git",
"url": "https://github.com/symfony/flex.git",
- "reference": "202c981673b255a41bac4076cb82629a0b8481cb"
+ "reference": "eead30b31db70691cd1fd1e7225190c818a1a5f6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/flex/zipball/202c981673b255a41bac4076cb82629a0b8481cb",
- "reference": "202c981673b255a41bac4076cb82629a0b8481cb",
+ "url": "https://api.github.com/repos/symfony/flex/zipball/eead30b31db70691cd1fd1e7225190c818a1a5f6",
+ "reference": "eead30b31db70691cd1fd1e7225190c818a1a5f6",
"shasum": ""
},
"require": {
@@ -3883,20 +3885,20 @@
"email": "fabien.potencier@gmail.com"
}
],
- "time": "2018-03-23T16:30:50+00:00"
+ "time": "2018-03-27T10:04:58+00:00"
},
{
"name": "symfony/form",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/form.git",
- "reference": "26c2749671eb73602b4264e4a3221fb451dbc8d5"
+ "reference": "5a3f505b256a65978d1d4f924ed53ceb503d1d5f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/form/zipball/26c2749671eb73602b4264e4a3221fb451dbc8d5",
- "reference": "26c2749671eb73602b4264e4a3221fb451dbc8d5",
+ "url": "https://api.github.com/repos/symfony/form/zipball/5a3f505b256a65978d1d4f924ed53ceb503d1d5f",
+ "reference": "5a3f505b256a65978d1d4f924ed53ceb503d1d5f",
"shasum": ""
},
"require": {
@@ -3963,20 +3965,20 @@
],
"description": "Symfony Form Component",
"homepage": "https://symfony.com",
- "time": "2018-03-01T10:21:51+00:00"
+ "time": "2018-04-02T10:05:09+00:00"
},
{
"name": "symfony/framework-bundle",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/framework-bundle.git",
- "reference": "d47d6da8c852648e26f12e55e4c895b81c4e99bf"
+ "reference": "a8e73bb539054efdca59a4ec0d3af99635812505"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/d47d6da8c852648e26f12e55e4c895b81c4e99bf",
- "reference": "d47d6da8c852648e26f12e55e4c895b81c4e99bf",
+ "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/a8e73bb539054efdca59a4ec0d3af99635812505",
+ "reference": "a8e73bb539054efdca59a4ec0d3af99635812505",
"shasum": ""
},
"require": {
@@ -4077,20 +4079,20 @@
],
"description": "Symfony FrameworkBundle",
"homepage": "https://symfony.com",
- "time": "2018-03-02T08:28:17+00:00"
+ "time": "2018-04-02T09:52:41+00:00"
},
{
"name": "symfony/http-foundation",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "6c181e81a3a9a7996c62ebd7803592536e729c5a"
+ "reference": "d0864a82e5891ab61d31eecbaa48bed5a09b8e6c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6c181e81a3a9a7996c62ebd7803592536e729c5a",
- "reference": "6c181e81a3a9a7996c62ebd7803592536e729c5a",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d0864a82e5891ab61d31eecbaa48bed5a09b8e6c",
+ "reference": "d0864a82e5891ab61d31eecbaa48bed5a09b8e6c",
"shasum": ""
},
"require": {
@@ -4130,20 +4132,20 @@
],
"description": "Symfony HttpFoundation Component",
"homepage": "https://symfony.com",
- "time": "2018-03-05T16:01:10+00:00"
+ "time": "2018-04-03T05:24:00+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "2a1ebfe8c37240500befcb17bceb3893adacffa3"
+ "reference": "38337d03a554a2b0e9f553d368723692b7c04a8f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/2a1ebfe8c37240500befcb17bceb3893adacffa3",
- "reference": "2a1ebfe8c37240500befcb17bceb3893adacffa3",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/38337d03a554a2b0e9f553d368723692b7c04a8f",
+ "reference": "38337d03a554a2b0e9f553d368723692b7c04a8f",
"shasum": ""
},
"require": {
@@ -4216,11 +4218,11 @@
],
"description": "Symfony HttpKernel Component",
"homepage": "https://symfony.com",
- "time": "2018-03-05T22:27:01+00:00"
+ "time": "2018-04-03T06:20:33+00:00"
},
{
"name": "symfony/inflector",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/inflector.git",
@@ -4277,16 +4279,16 @@
},
{
"name": "symfony/intl",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/intl.git",
- "reference": "929953d9e64c7209afb907fc34ac64b135c79cb8"
+ "reference": "d58df88e3cfdb2702f5fd8cd67c33961c2539e0c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/intl/zipball/929953d9e64c7209afb907fc34ac64b135c79cb8",
- "reference": "929953d9e64c7209afb907fc34ac64b135c79cb8",
+ "url": "https://api.github.com/repos/symfony/intl/zipball/d58df88e3cfdb2702f5fd8cd67c33961c2539e0c",
+ "reference": "d58df88e3cfdb2702f5fd8cd67c33961c2539e0c",
"shasum": ""
},
"require": {
@@ -4348,11 +4350,11 @@
"l10n",
"localization"
],
- "time": "2018-02-03T00:57:23+00:00"
+ "time": "2018-04-02T09:52:41+00:00"
},
{
"name": "symfony/monolog-bridge",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/monolog-bridge.git",
@@ -4481,7 +4483,7 @@
},
{
"name": "symfony/options-resolver",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
@@ -4740,16 +4742,16 @@
},
{
"name": "symfony/process",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "6ed08502a7c9559da8e60ea343bdbd19c3350b3e"
+ "reference": "d7dc1ee5dfe9f732cb1bba7310f5b99f2b7a6d25"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/6ed08502a7c9559da8e60ea343bdbd19c3350b3e",
- "reference": "6ed08502a7c9559da8e60ea343bdbd19c3350b3e",
+ "url": "https://api.github.com/repos/symfony/process/zipball/d7dc1ee5dfe9f732cb1bba7310f5b99f2b7a6d25",
+ "reference": "d7dc1ee5dfe9f732cb1bba7310f5b99f2b7a6d25",
"shasum": ""
},
"require": {
@@ -4785,11 +4787,11 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
- "time": "2018-02-19T12:18:43+00:00"
+ "time": "2018-04-03T05:24:00+00:00"
},
{
"name": "symfony/property-access",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/property-access.git",
@@ -4856,7 +4858,7 @@
},
{
"name": "symfony/property-info",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/property-info.git",
@@ -4980,16 +4982,16 @@
},
{
"name": "symfony/routing",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
- "reference": "9c6268c1970c7e507bedc8946bece32a7db23515"
+ "reference": "ca780c838046bfef4a6fd50284ae71a5d1f1a8b2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/routing/zipball/9c6268c1970c7e507bedc8946bece32a7db23515",
- "reference": "9c6268c1970c7e507bedc8946bece32a7db23515",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/ca780c838046bfef4a6fd50284ae71a5d1f1a8b2",
+ "reference": "ca780c838046bfef4a6fd50284ae71a5d1f1a8b2",
"shasum": ""
},
"require": {
@@ -5054,20 +5056,20 @@
"uri",
"url"
],
- "time": "2018-02-28T21:50:02+00:00"
+ "time": "2018-04-02T09:52:41+00:00"
},
{
"name": "symfony/security",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/security.git",
- "reference": "a0a161c5b28a4436d955dd6385c2ebbeca1852c3"
+ "reference": "3e08200cc978c71d2cfdbc9500db4f2b65bdd28f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/security/zipball/a0a161c5b28a4436d955dd6385c2ebbeca1852c3",
- "reference": "a0a161c5b28a4436d955dd6385c2ebbeca1852c3",
+ "url": "https://api.github.com/repos/symfony/security/zipball/3e08200cc978c71d2cfdbc9500db4f2b65bdd28f",
+ "reference": "3e08200cc978c71d2cfdbc9500db4f2b65bdd28f",
"shasum": ""
},
"require": {
@@ -5131,20 +5133,20 @@
],
"description": "Symfony Security Component",
"homepage": "https://symfony.com",
- "time": "2018-02-23T14:40:28+00:00"
+ "time": "2018-04-02T14:35:51+00:00"
},
{
"name": "symfony/security-bundle",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/security-bundle.git",
- "reference": "5cbcf5d33261aaa0b5895a253161c6ba32754efd"
+ "reference": "4edcb55716cdbbb26783313f34046ada778c87c5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/security-bundle/zipball/5cbcf5d33261aaa0b5895a253161c6ba32754efd",
- "reference": "5cbcf5d33261aaa0b5895a253161c6ba32754efd",
+ "url": "https://api.github.com/repos/symfony/security-bundle/zipball/4edcb55716cdbbb26783313f34046ada778c87c5",
+ "reference": "4edcb55716cdbbb26783313f34046ada778c87c5",
"shasum": ""
},
"require": {
@@ -5211,20 +5213,20 @@
],
"description": "Symfony SecurityBundle",
"homepage": "https://symfony.com",
- "time": "2018-02-19T12:18:43+00:00"
+ "time": "2018-03-19T22:35:49+00:00"
},
{
"name": "symfony/serializer",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/serializer.git",
- "reference": "0526b430c8548520fd5817f425d9a3d8a11a21e1"
+ "reference": "d0e9269101ccd978af971fd4d7a892848672bfa1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/serializer/zipball/0526b430c8548520fd5817f425d9a3d8a11a21e1",
- "reference": "0526b430c8548520fd5817f425d9a3d8a11a21e1",
+ "url": "https://api.github.com/repos/symfony/serializer/zipball/d0e9269101ccd978af971fd4d7a892848672bfa1",
+ "reference": "d0e9269101ccd978af971fd4d7a892848672bfa1",
"shasum": ""
},
"require": {
@@ -5289,7 +5291,7 @@
],
"description": "Symfony Serializer Component",
"homepage": "https://symfony.com",
- "time": "2018-02-19T12:18:43+00:00"
+ "time": "2018-03-19T17:30:36+00:00"
},
{
"name": "symfony/serializer-pack",
@@ -5324,16 +5326,16 @@
},
{
"name": "symfony/swiftmailer-bundle",
- "version": "v3.2.1",
+ "version": "v3.2.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/swiftmailer-bundle.git",
- "reference": "20e71c247a5a43ceb655db9712394d08c09b33ef"
+ "reference": "f1ba0552a9cd4df0191a58845fbd5541cf9eda2d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/swiftmailer-bundle/zipball/20e71c247a5a43ceb655db9712394d08c09b33ef",
- "reference": "20e71c247a5a43ceb655db9712394d08c09b33ef",
+ "url": "https://api.github.com/repos/symfony/swiftmailer-bundle/zipball/f1ba0552a9cd4df0191a58845fbd5541cf9eda2d",
+ "reference": "f1ba0552a9cd4df0191a58845fbd5541cf9eda2d",
"shasum": ""
},
"require": {
@@ -5382,11 +5384,11 @@
],
"description": "Symfony SwiftmailerBundle",
"homepage": "http://symfony.com",
- "time": "2018-03-08T16:39:26+00:00"
+ "time": "2018-04-03T16:29:41+00:00"
},
{
"name": "symfony/templating",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/templating.git",
@@ -5441,7 +5443,7 @@
},
{
"name": "symfony/translation",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
@@ -5509,16 +5511,16 @@
},
{
"name": "symfony/twig-bridge",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/twig-bridge.git",
- "reference": "575004ae3bcfb7d909a34db20edb7c349defb092"
+ "reference": "7596e74f91d9c2ecb5de35811b87655e9533096f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/575004ae3bcfb7d909a34db20edb7c349defb092",
- "reference": "575004ae3bcfb7d909a34db20edb7c349defb092",
+ "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/7596e74f91d9c2ecb5de35811b87655e9533096f",
+ "reference": "7596e74f91d9c2ecb5de35811b87655e9533096f",
"shasum": ""
},
"require": {
@@ -5527,7 +5529,7 @@
},
"conflict": {
"symfony/console": "<3.4",
- "symfony/form": "<3.4.5|<4.0.5,>=4.0"
+ "symfony/form": "<3.4.7|<4.0.7,>=4.0"
},
"require-dev": {
"symfony/asset": "~3.4|~4.0",
@@ -5535,7 +5537,7 @@
"symfony/dependency-injection": "~3.4|~4.0",
"symfony/expression-language": "~3.4|~4.0",
"symfony/finder": "~3.4|~4.0",
- "symfony/form": "^3.4.5|^4.0.5",
+ "symfony/form": "^3.4.7|^4.0.7",
"symfony/http-foundation": "~3.4|~4.0",
"symfony/http-kernel": "~3.4|~4.0",
"symfony/polyfill-intl-icu": "~1.0",
@@ -5595,11 +5597,11 @@
],
"description": "Symfony Twig Bridge",
"homepage": "https://symfony.com",
- "time": "2018-03-01T10:21:51+00:00"
+ "time": "2018-04-02T14:06:14+00:00"
},
{
"name": "symfony/twig-bundle",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/twig-bundle.git",
@@ -5672,16 +5674,16 @@
},
{
"name": "symfony/validator",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/validator.git",
- "reference": "56f72db4783d715a6742a783813362b514a77c1d"
+ "reference": "2f080fe8af133d12bdf3eeb55a9d01e1286f8e24"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/validator/zipball/56f72db4783d715a6742a783813362b514a77c1d",
- "reference": "56f72db4783d715a6742a783813362b514a77c1d",
+ "url": "https://api.github.com/repos/symfony/validator/zipball/2f080fe8af133d12bdf3eeb55a9d01e1286f8e24",
+ "reference": "2f080fe8af133d12bdf3eeb55a9d01e1286f8e24",
"shasum": ""
},
"require": {
@@ -5752,11 +5754,11 @@
],
"description": "Symfony Validator Component",
"homepage": "https://symfony.com",
- "time": "2018-03-01T10:21:51+00:00"
+ "time": "2018-04-02T09:52:41+00:00"
},
{
"name": "symfony/web-link",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/web-link.git",
@@ -5827,16 +5829,16 @@
},
{
"name": "symfony/yaml",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "de5f125ea39de846b90b313b2cfb031a0152d223"
+ "reference": "8b34ebb5989df61cbd77eff29a02c4db9ac1069c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/de5f125ea39de846b90b313b2cfb031a0152d223",
- "reference": "de5f125ea39de846b90b313b2cfb031a0152d223",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/8b34ebb5989df61cbd77eff29a02c4db9ac1069c",
+ "reference": "8b34ebb5989df61cbd77eff29a02c4db9ac1069c",
"shasum": ""
},
"require": {
@@ -5881,7 +5883,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
- "time": "2018-02-19T20:08:53+00:00"
+ "time": "2018-04-03T05:24:00+00:00"
},
{
"name": "twig/extensions",
@@ -5941,16 +5943,16 @@
},
{
"name": "twig/twig",
- "version": "v2.4.7",
+ "version": "v2.4.8",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
- "reference": "69aacd44dbbaa3199d5afb68605c996d577896fc"
+ "reference": "7b604c89da162034bdf4bb66310f358d313dd16d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/twigphp/Twig/zipball/69aacd44dbbaa3199d5afb68605c996d577896fc",
- "reference": "69aacd44dbbaa3199d5afb68605c996d577896fc",
+ "url": "https://api.github.com/repos/twigphp/Twig/zipball/7b604c89da162034bdf4bb66310f358d313dd16d",
+ "reference": "7b604c89da162034bdf4bb66310f358d313dd16d",
"shasum": ""
},
"require": {
@@ -6003,7 +6005,7 @@
"keywords": [
"templating"
],
- "time": "2018-03-20T04:31:17+00:00"
+ "time": "2018-04-02T09:24:19+00:00"
},
{
"name": "webmozart/assert",
@@ -6810,16 +6812,16 @@
},
{
"name": "symfony/browser-kit",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
- "reference": "fee0fcd501304b1c3190f6293f650cceb738a353"
+ "reference": "c43bfa0182363b3fd64331b5e64e467349ff4670"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/browser-kit/zipball/fee0fcd501304b1c3190f6293f650cceb738a353",
- "reference": "fee0fcd501304b1c3190f6293f650cceb738a353",
+ "url": "https://api.github.com/repos/symfony/browser-kit/zipball/c43bfa0182363b3fd64331b5e64e467349ff4670",
+ "reference": "c43bfa0182363b3fd64331b5e64e467349ff4670",
"shasum": ""
},
"require": {
@@ -6863,20 +6865,20 @@
],
"description": "Symfony BrowserKit Component",
"homepage": "https://symfony.com",
- "time": "2018-01-03T07:38:00+00:00"
+ "time": "2018-03-19T22:35:49+00:00"
},
{
"name": "symfony/css-selector",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
- "reference": "c69f1e93aa898fd9fec627ebef467188151c8dc2"
+ "reference": "03f965583147957f1ecbad7ea1c9d6fd5e525ec2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/c69f1e93aa898fd9fec627ebef467188151c8dc2",
- "reference": "c69f1e93aa898fd9fec627ebef467188151c8dc2",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/03f965583147957f1ecbad7ea1c9d6fd5e525ec2",
+ "reference": "03f965583147957f1ecbad7ea1c9d6fd5e525ec2",
"shasum": ""
},
"require": {
@@ -6916,11 +6918,11 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
- "time": "2018-02-03T14:58:37+00:00"
+ "time": "2018-03-19T22:35:49+00:00"
},
{
"name": "symfony/debug-bundle",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug-bundle.git",
@@ -6985,16 +6987,16 @@
},
{
"name": "symfony/debug-pack",
- "version": "v1.0.4",
+ "version": "v1.0.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug-pack.git",
- "reference": "2da9b0ea7a733fc29ec3e72139751fd8d2582887"
+ "reference": "ae4b15596788f9592f67f8615398671bf3f796e4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/debug-pack/zipball/2da9b0ea7a733fc29ec3e72139751fd8d2582887",
- "reference": "2da9b0ea7a733fc29ec3e72139751fd8d2582887",
+ "url": "https://api.github.com/repos/symfony/debug-pack/zipball/ae4b15596788f9592f67f8615398671bf3f796e4",
+ "reference": "ae4b15596788f9592f67f8615398671bf3f796e4",
"shasum": ""
},
"require": {
@@ -7002,7 +7004,6 @@
"php": "^7.0",
"symfony/debug-bundle": "^3.3|^4.0",
"symfony/monolog-bundle": "^3.0",
- "symfony/phpunit-bridge": "^3.3|^4.0",
"symfony/profiler-pack": "^1.0",
"symfony/var-dumper": "^3.3|^4.0"
},
@@ -7012,20 +7013,20 @@
"MIT"
],
"description": "A debug pack for Symfony projects",
- "time": "2017-12-12T01:47:04+00:00"
+ "time": "2018-03-29T13:53:44+00:00"
},
{
"name": "symfony/dom-crawler",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
- "reference": "26726ddc01601dc9393f2afc3369ce1ca64e4537"
+ "reference": "d6c04c7532535b5e0b63db45b543cd60818e0fbc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/26726ddc01601dc9393f2afc3369ce1ca64e4537",
- "reference": "26726ddc01601dc9393f2afc3369ce1ca64e4537",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/d6c04c7532535b5e0b63db45b543cd60818e0fbc",
+ "reference": "d6c04c7532535b5e0b63db45b543cd60818e0fbc",
"shasum": ""
},
"require": {
@@ -7068,11 +7069,11 @@
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
- "time": "2018-02-22T10:50:29+00:00"
+ "time": "2018-03-19T22:35:49+00:00"
},
{
"name": "symfony/dotenv",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/dotenv.git",
@@ -7129,16 +7130,16 @@
},
{
"name": "symfony/phpunit-bridge",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/phpunit-bridge.git",
- "reference": "14ffbbe2a72d0f6339b24eb830dd38cf63ba6630"
+ "reference": "cdfbf48b17ca56bb7fbcb4126f949665e5f426a3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/14ffbbe2a72d0f6339b24eb830dd38cf63ba6630",
- "reference": "14ffbbe2a72d0f6339b24eb830dd38cf63ba6630",
+ "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/cdfbf48b17ca56bb7fbcb4126f949665e5f426a3",
+ "reference": "cdfbf48b17ca56bb7fbcb4126f949665e5f426a3",
"shasum": ""
},
"require": {
@@ -7191,7 +7192,7 @@
],
"description": "Symfony PHPUnit Bridge",
"homepage": "https://symfony.com",
- "time": "2018-02-19T16:50:22+00:00"
+ "time": "2018-03-22T13:42:36+00:00"
},
{
"name": "symfony/polyfill-php72",
@@ -7278,7 +7279,7 @@
},
{
"name": "symfony/stopwatch",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/stopwatch.git",
@@ -7327,16 +7328,16 @@
},
{
"name": "symfony/var-dumper",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "c7d89044ed6ed3b7d8b558d509cca0666b947e58"
+ "reference": "f9b257f2009c7f09166bcfd3d3d96455741ed371"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c7d89044ed6ed3b7d8b558d509cca0666b947e58",
- "reference": "c7d89044ed6ed3b7d8b558d509cca0666b947e58",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/f9b257f2009c7f09166bcfd3d3d96455741ed371",
+ "reference": "f9b257f2009c7f09166bcfd3d3d96455741ed371",
"shasum": ""
},
"require": {
@@ -7392,20 +7393,20 @@
"debug",
"dump"
],
- "time": "2018-02-26T15:55:47+00:00"
+ "time": "2018-04-03T05:24:00+00:00"
},
{
"name": "symfony/web-profiler-bundle",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/web-profiler-bundle.git",
- "reference": "eaaf46b1ccaf83c8c97efd8d081e03d9ff0f29de"
+ "reference": "4d709edab20fdfc889a5c9a2b530453e4a4b8127"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/eaaf46b1ccaf83c8c97efd8d081e03d9ff0f29de",
- "reference": "eaaf46b1ccaf83c8c97efd8d081e03d9ff0f29de",
+ "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/4d709edab20fdfc889a5c9a2b530453e4a4b8127",
+ "reference": "4d709edab20fdfc889a5c9a2b530453e4a4b8127",
"shasum": ""
},
"require": {
@@ -7458,11 +7459,11 @@
],
"description": "Symfony WebProfilerBundle",
"homepage": "https://symfony.com",
- "time": "2018-03-02T08:28:17+00:00"
+ "time": "2018-04-02T09:52:41+00:00"
},
{
"name": "symfony/web-server-bundle",
- "version": "v4.0.6",
+ "version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/web-server-bundle.git",
From 59570284c31a951ec200fd2a1f3bc58f0500d3bf Mon Sep 17 00:00:00 2001
From: Emma
Date: Thu, 5 Apr 2018 08:04:44 +0200
Subject: [PATCH 11/24] replace deprecated psr-0 type hints with psr-4
---
tests/Security/UserCheckerTest.php | 5 +++--
tests/Security/Voter/TokenVoterTest.php | 3 ++-
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/tests/Security/UserCheckerTest.php b/tests/Security/UserCheckerTest.php
index 24f2794..86bca5e 100644
--- a/tests/Security/UserCheckerTest.php
+++ b/tests/Security/UserCheckerTest.php
@@ -4,6 +4,7 @@
use App\Entity\User;
use App\Security\UserChecker;
+use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
/**
@@ -14,7 +15,7 @@ class UserCheckerTest extends TestCase {
* @doesNotPerformAssertions
*/
public function testNonBannedUserDoesNotCauseExceptionOnAuth() {
- /** @var User|\PHPUnit_Framework_MockObject_MockObject $user */
+ /* @var User|MockObject $user */
$user = $this->createMock(User::class);
$user->method('isBanned')->willReturn(false, false);
@@ -25,7 +26,7 @@ public function testNonBannedUserDoesNotCauseExceptionOnAuth() {
* @expectedException \App\Security\Exception\AccountBannedException
*/
public function testBannedUserCausesExceptionOnPostAuth() {
- /** @var User|\PHPUnit_Framework_MockObject_MockObject $user */
+ /* @var User|MockObject $user */
$user = $this->createMock(User::class);
$user->method('isBanned')->willReturn(true);
diff --git a/tests/Security/Voter/TokenVoterTest.php b/tests/Security/Voter/TokenVoterTest.php
index 4366ac6..b3f54a1 100644
--- a/tests/Security/Voter/TokenVoterTest.php
+++ b/tests/Security/Voter/TokenVoterTest.php
@@ -4,6 +4,7 @@
use App\Entity\User;
use App\Security\Voter\TokenVoter;
+use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
@@ -57,7 +58,7 @@ private function getToken($roles, $createdAt) {
$user->method('getCreated')
->willReturn((new \DateTime('@'.time()))->modify($createdAt));
- /* @var TokenInterface|\PHPUnit_Framework_MockObject_MockObject $token */
+ /* @var TokenInterface|MockObject $token */
$token = $this->createMock(TokenInterface::class);
$token->method('getRoles')->willReturn($roles);
From b03c09530b70f4c3c4e87cd159673774aff4b141 Mon Sep 17 00:00:00 2001
From: Emma
Date: Sat, 7 Apr 2018 11:00:09 +0200
Subject: [PATCH 12/24] add NotForumBanned constraint/validator
---
src/Controller/CommentController.php | 6 +-
src/Form/Model/CommentData.php | 49 ++++++------
src/Form/Model/SubmissionData.php | 2 +
src/Validator/Constraints/NotForumBanned.php | 24 ++++++
.../Constraints/NotForumBannedValidator.php | 78 +++++++++++++++++++
translations/validators.en.yml | 1 +
6 files changed, 132 insertions(+), 28 deletions(-)
create mode 100644 src/Validator/Constraints/NotForumBanned.php
create mode 100644 src/Validator/Constraints/NotForumBannedValidator.php
diff --git a/src/Controller/CommentController.php b/src/Controller/CommentController.php
index f728460..f1acd27 100644
--- a/src/Controller/CommentController.php
+++ b/src/Controller/CommentController.php
@@ -93,15 +93,13 @@ public function comment(
EventDispatcherInterface $dispatcher,
Comment $comment = null
) {
- $data = new CommentData();
+ $data = new CommentData($submission);
$form = $this->createForm(CommentType::class, $data, ['forum' => $forum]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
- $user = $this->getUser();
- $ip = $request->getClientIp();
- $reply = $data->toComment($submission, $user, $comment, $ip);
+ $reply = $data->toComment($this->getUser(), $comment, $request->getClientIp());
$em->persist($reply);
$em->flush();
diff --git a/src/Form/Model/CommentData.php b/src/Form/Model/CommentData.php
index 3033a92..7669467 100644
--- a/src/Form/Model/CommentData.php
+++ b/src/Form/Model/CommentData.php
@@ -6,9 +6,18 @@
use App\Entity\Submission;
use App\Entity\User;
use App\Entity\UserFlags;
+use App\Validator\Constraints\NotForumBanned;
use Symfony\Component\Validator\Constraints as Assert;
+/**
+ * @NotForumBanned(forumPath="submission.forum", errorPath="body")
+ */
class CommentData {
+ /**
+ * @var Submission
+ */
+ private $submission;
+
/**
* @Assert\NotBlank(message="The comment must not be empty.")
* @Assert\Regex("/[[:graph:]]/u", message="The comment must not be empty.")
@@ -24,23 +33,23 @@ class CommentData {
private $userFlag = UserFlags::FLAG_NONE;
public static function createFromComment(Comment $comment): self {
- $self = new self();
+ $self = new self($comment->getSubmission());
+ $self->submission = $comment->getSubmission();
$self->body = $comment->getBody();
$self->userFlag = $comment->getUserFlag();
return $self;
}
- public function toComment(
- Submission $submission,
- User $user,
- Comment $parent = null,
- $ip = null
- ): Comment {
+ public function __construct(Submission $submission) {
+ $this->submission = $submission;
+ }
+
+ public function toComment(User $user, Comment $parent = null, $ip = null): Comment {
return new Comment(
$this->body,
$user,
- $submission,
+ $this->submission,
$this->userFlag,
$parent,
$ip
@@ -60,31 +69,23 @@ public function updateComment(Comment $comment, User $editingUser) {
}
}
- /**
- * @return string|null
- */
- public function getBody() {
+ public function getBody(): ?string {
return $this->body;
}
- /**
- * @param string|null $body
- */
- public function setBody($body) {
+ public function setBody($body): void {
$this->body = $body;
}
- /**
- * @return int|null
- */
- public function getUserFlag() {
+ public function getUserFlag(): ?int {
return $this->userFlag;
}
- /**
- * @param int|null $userFlag
- */
- public function setUserFlag($userFlag) {
+ public function setUserFlag($userFlag): void {
$this->userFlag = $userFlag;
}
+
+ public function getSubmission(): Submission {
+ return $this->submission;
+ }
}
diff --git a/src/Form/Model/SubmissionData.php b/src/Form/Model/SubmissionData.php
index a7862c5..cda1fff 100644
--- a/src/Form/Model/SubmissionData.php
+++ b/src/Form/Model/SubmissionData.php
@@ -6,6 +6,7 @@
use App\Entity\Submission;
use App\Entity\User;
use App\Entity\UserFlags;
+use App\Validator\Constraints\NotForumBanned;
use App\Validator\Constraints\RateLimit;
use Symfony\Component\Validator\Constraints as Assert;
@@ -44,6 +45,7 @@ class SubmissionData {
private $userFlag = UserFlags::FLAG_NONE;
/**
+ * @NotForumBanned()
* @Assert\NotBlank(groups={"create", "edit"})
*
* @var Forum|null
diff --git a/src/Validator/Constraints/NotForumBanned.php b/src/Validator/Constraints/NotForumBanned.php
new file mode 100644
index 0000000..cb10906
--- /dev/null
+++ b/src/Validator/Constraints/NotForumBanned.php
@@ -0,0 +1,24 @@
+ 'FORUM_BANNED_ERROR',
+ ];
+
+ public function getTargets(): array {
+ return [self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT];
+ }
+}
diff --git a/src/Validator/Constraints/NotForumBannedValidator.php b/src/Validator/Constraints/NotForumBannedValidator.php
new file mode 100644
index 0000000..ba9143f
--- /dev/null
+++ b/src/Validator/Constraints/NotForumBannedValidator.php
@@ -0,0 +1,78 @@
+tokenStorage = $tokenStorage;
+ }
+
+ public function validate($value, Constraint $constraint): void {
+ if (!$value || !$this->tokenStorage->getToken() || !$this->tokenStorage->getToken()->getUser()) {
+ return;
+ }
+
+ if (!\is_object($value)) {
+ throw new UnexpectedTypeException($value, 'object');
+ }
+
+ if (!$constraint instanceof NotForumBanned) {
+ throw new UnexpectedTypeException($constraint, NotForumBanned::class);
+ }
+
+ if ($constraint->forumPath) {
+ $propertyAccessor = PropertyAccess::createPropertyAccessor();
+
+ if (!$propertyAccessor->isReadable($value, $constraint->forumPath)) {
+ throw new InvalidArgumentException(sprintf(
+ 'Cannot read property %s on object of type %s',
+ $constraint->forumPath,
+ \get_class($value)
+ ));
+ }
+
+ $forum = $propertyAccessor->getValue($value, $constraint->forumPath);
+ } else {
+ $forum = $value;
+ }
+
+ if ($forum === null) {
+ return;
+ }
+
+ if (!$forum instanceof Forum) {
+ throw new InvalidArgumentException(sprintf(
+ 'Property %s on object of type %s is not of type %s',
+ $constraint->forumPath,
+ \get_class($value),
+ Forum::class
+ ));
+ }
+
+ $token = $this->tokenStorage->getToken();
+
+ if (!$token) {
+ return;
+ }
+
+ if ($forum->userIsBanned($token->getUser())) {
+ $this->context->buildViolation($constraint->message)
+ ->setCode(NotForumBanned::FORUM_BANNED_ERROR)
+ ->atPath($constraint->errorPath)
+ ->addViolation();
+ }
+ }
+}
diff --git a/translations/validators.en.yml b/translations/validators.en.yml
index a0673fb..91f6fbe 100644
--- a/translations/validators.en.yml
+++ b/translations/validators.en.yml
@@ -20,3 +20,4 @@
'The IP address is not valid.': 'The IP address is not valid.'
'The CIDR mask is not valid.': 'The CIDR mask is not valid.'
'Missing CIDR mask.': 'Missing CIDR mask.'
+'You have been banned from this forum.': 'You have been banned from this forum.'
From 097ee454c30cf826b5150d325f9f9c5897128cff Mon Sep 17 00:00:00 2001
From: Emma
Date: Sat, 7 Apr 2018 11:09:26 +0200
Subject: [PATCH 13/24] update dependencies
---
composer.lock | 192 +++++++++++++++++++++++++-------------------------
1 file changed, 96 insertions(+), 96 deletions(-)
diff --git a/composer.lock b/composer.lock
index 04e4803..fbd153c 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1780,16 +1780,16 @@
},
{
"name": "league/flysystem",
- "version": "1.0.43",
+ "version": "1.0.44",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
- "reference": "1ce7cc142d906ba58dc54c82915d355a9191c8a8"
+ "reference": "168dbe519737221dc87d17385cde33073881fd02"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1ce7cc142d906ba58dc54c82915d355a9191c8a8",
- "reference": "1ce7cc142d906ba58dc54c82915d355a9191c8a8",
+ "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/168dbe519737221dc87d17385cde33073881fd02",
+ "reference": "168dbe519737221dc87d17385cde33073881fd02",
"shasum": ""
},
"require": {
@@ -1860,7 +1860,7 @@
"sftp",
"storage"
],
- "time": "2018-03-01T10:27:04+00:00"
+ "time": "2018-04-06T09:58:14+00:00"
},
{
"name": "liip/imagine-bundle",
@@ -3171,7 +3171,7 @@
},
{
"name": "symfony/asset",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/asset.git",
@@ -3227,7 +3227,7 @@
},
{
"name": "symfony/cache",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/cache.git",
@@ -3296,7 +3296,7 @@
},
{
"name": "symfony/config",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
@@ -3358,7 +3358,7 @@
},
{
"name": "symfony/console",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
@@ -3426,7 +3426,7 @@
},
{
"name": "symfony/debug",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
@@ -3482,7 +3482,7 @@
},
{
"name": "symfony/dependency-injection",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
@@ -3553,7 +3553,7 @@
},
{
"name": "symfony/doctrine-bridge",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/doctrine-bridge.git",
@@ -3632,16 +3632,16 @@
},
{
"name": "symfony/event-dispatcher",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "85eaf6a8ec915487abac52e133efc4a268204428"
+ "reference": "63353a71073faf08f62caab4e6889b06a787f07b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/85eaf6a8ec915487abac52e133efc4a268204428",
- "reference": "85eaf6a8ec915487abac52e133efc4a268204428",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/63353a71073faf08f62caab4e6889b06a787f07b",
+ "reference": "63353a71073faf08f62caab4e6889b06a787f07b",
"shasum": ""
},
"require": {
@@ -3691,11 +3691,11 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
- "time": "2018-02-14T14:11:10+00:00"
+ "time": "2018-04-06T07:35:43+00:00"
},
{
"name": "symfony/expression-language",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/expression-language.git",
@@ -3745,7 +3745,7 @@
},
{
"name": "symfony/filesystem",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
@@ -3794,16 +3794,16 @@
},
{
"name": "symfony/finder",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "c72995d9f5999b3fcdd8660c0c9690243252e1e1"
+ "reference": "ca27c02b7a3fef4828c998c2ff9ba7aae1641c49"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/c72995d9f5999b3fcdd8660c0c9690243252e1e1",
- "reference": "c72995d9f5999b3fcdd8660c0c9690243252e1e1",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/ca27c02b7a3fef4828c998c2ff9ba7aae1641c49",
+ "reference": "ca27c02b7a3fef4828c998c2ff9ba7aae1641c49",
"shasum": ""
},
"require": {
@@ -3839,7 +3839,7 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
- "time": "2018-04-02T09:52:41+00:00"
+ "time": "2018-04-04T05:10:37+00:00"
},
{
"name": "symfony/flex",
@@ -3889,16 +3889,16 @@
},
{
"name": "symfony/form",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/form.git",
- "reference": "5a3f505b256a65978d1d4f924ed53ceb503d1d5f"
+ "reference": "8605af5a9181d44637de5706e32d8ab9caee799e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/form/zipball/5a3f505b256a65978d1d4f924ed53ceb503d1d5f",
- "reference": "5a3f505b256a65978d1d4f924ed53ceb503d1d5f",
+ "url": "https://api.github.com/repos/symfony/form/zipball/8605af5a9181d44637de5706e32d8ab9caee799e",
+ "reference": "8605af5a9181d44637de5706e32d8ab9caee799e",
"shasum": ""
},
"require": {
@@ -3965,20 +3965,20 @@
],
"description": "Symfony Form Component",
"homepage": "https://symfony.com",
- "time": "2018-04-02T10:05:09+00:00"
+ "time": "2018-04-06T07:35:43+00:00"
},
{
"name": "symfony/framework-bundle",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/framework-bundle.git",
- "reference": "a8e73bb539054efdca59a4ec0d3af99635812505"
+ "reference": "3571d235434b566aea39d8f8bfe38860344fd9a3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/a8e73bb539054efdca59a4ec0d3af99635812505",
- "reference": "a8e73bb539054efdca59a4ec0d3af99635812505",
+ "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/3571d235434b566aea39d8f8bfe38860344fd9a3",
+ "reference": "3571d235434b566aea39d8f8bfe38860344fd9a3",
"shasum": ""
},
"require": {
@@ -4079,11 +4079,11 @@
],
"description": "Symfony FrameworkBundle",
"homepage": "https://symfony.com",
- "time": "2018-04-02T09:52:41+00:00"
+ "time": "2018-04-04T18:24:59+00:00"
},
{
"name": "symfony/http-foundation",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
@@ -4136,16 +4136,16 @@
},
{
"name": "symfony/http-kernel",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "38337d03a554a2b0e9f553d368723692b7c04a8f"
+ "reference": "6dd620d96d64456075536ffe3c6c4658dd689021"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/38337d03a554a2b0e9f553d368723692b7c04a8f",
- "reference": "38337d03a554a2b0e9f553d368723692b7c04a8f",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6dd620d96d64456075536ffe3c6c4658dd689021",
+ "reference": "6dd620d96d64456075536ffe3c6c4658dd689021",
"shasum": ""
},
"require": {
@@ -4218,11 +4218,11 @@
],
"description": "Symfony HttpKernel Component",
"homepage": "https://symfony.com",
- "time": "2018-04-03T06:20:33+00:00"
+ "time": "2018-04-06T16:25:03+00:00"
},
{
"name": "symfony/inflector",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/inflector.git",
@@ -4279,7 +4279,7 @@
},
{
"name": "symfony/intl",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/intl.git",
@@ -4354,7 +4354,7 @@
},
{
"name": "symfony/monolog-bridge",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/monolog-bridge.git",
@@ -4483,7 +4483,7 @@
},
{
"name": "symfony/options-resolver",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
@@ -4742,7 +4742,7 @@
},
{
"name": "symfony/process",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
@@ -4791,7 +4791,7 @@
},
{
"name": "symfony/property-access",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/property-access.git",
@@ -4858,7 +4858,7 @@
},
{
"name": "symfony/property-info",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/property-info.git",
@@ -4982,16 +4982,16 @@
},
{
"name": "symfony/routing",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
- "reference": "ca780c838046bfef4a6fd50284ae71a5d1f1a8b2"
+ "reference": "0663036dd57dbfd4e9ff29f75bbd5dd3253ebe71"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/routing/zipball/ca780c838046bfef4a6fd50284ae71a5d1f1a8b2",
- "reference": "ca780c838046bfef4a6fd50284ae71a5d1f1a8b2",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/0663036dd57dbfd4e9ff29f75bbd5dd3253ebe71",
+ "reference": "0663036dd57dbfd4e9ff29f75bbd5dd3253ebe71",
"shasum": ""
},
"require": {
@@ -5056,20 +5056,20 @@
"uri",
"url"
],
- "time": "2018-04-02T09:52:41+00:00"
+ "time": "2018-04-04T13:50:32+00:00"
},
{
"name": "symfony/security",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/security.git",
- "reference": "3e08200cc978c71d2cfdbc9500db4f2b65bdd28f"
+ "reference": "6c15e6b36dfa873c9798e41cac5937b61d0eb3fe"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/security/zipball/3e08200cc978c71d2cfdbc9500db4f2b65bdd28f",
- "reference": "3e08200cc978c71d2cfdbc9500db4f2b65bdd28f",
+ "url": "https://api.github.com/repos/symfony/security/zipball/6c15e6b36dfa873c9798e41cac5937b61d0eb3fe",
+ "reference": "6c15e6b36dfa873c9798e41cac5937b61d0eb3fe",
"shasum": ""
},
"require": {
@@ -5133,20 +5133,20 @@
],
"description": "Symfony Security Component",
"homepage": "https://symfony.com",
- "time": "2018-04-02T14:35:51+00:00"
+ "time": "2018-04-06T07:35:43+00:00"
},
{
"name": "symfony/security-bundle",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/security-bundle.git",
- "reference": "4edcb55716cdbbb26783313f34046ada778c87c5"
+ "reference": "dfabecc1fd3e626e676edef97753a645b4de85a5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/security-bundle/zipball/4edcb55716cdbbb26783313f34046ada778c87c5",
- "reference": "4edcb55716cdbbb26783313f34046ada778c87c5",
+ "url": "https://api.github.com/repos/symfony/security-bundle/zipball/dfabecc1fd3e626e676edef97753a645b4de85a5",
+ "reference": "dfabecc1fd3e626e676edef97753a645b4de85a5",
"shasum": ""
},
"require": {
@@ -5213,11 +5213,11 @@
],
"description": "Symfony SecurityBundle",
"homepage": "https://symfony.com",
- "time": "2018-03-19T22:35:49+00:00"
+ "time": "2018-04-06T07:35:43+00:00"
},
{
"name": "symfony/serializer",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/serializer.git",
@@ -5388,7 +5388,7 @@
},
{
"name": "symfony/templating",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/templating.git",
@@ -5443,7 +5443,7 @@
},
{
"name": "symfony/translation",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
@@ -5511,7 +5511,7 @@
},
{
"name": "symfony/twig-bridge",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/twig-bridge.git",
@@ -5601,7 +5601,7 @@
},
{
"name": "symfony/twig-bundle",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/twig-bundle.git",
@@ -5674,16 +5674,16 @@
},
{
"name": "symfony/validator",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/validator.git",
- "reference": "2f080fe8af133d12bdf3eeb55a9d01e1286f8e24"
+ "reference": "b9546d78133d6af199ac6625d0d587a2d804f967"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/validator/zipball/2f080fe8af133d12bdf3eeb55a9d01e1286f8e24",
- "reference": "2f080fe8af133d12bdf3eeb55a9d01e1286f8e24",
+ "url": "https://api.github.com/repos/symfony/validator/zipball/b9546d78133d6af199ac6625d0d587a2d804f967",
+ "reference": "b9546d78133d6af199ac6625d0d587a2d804f967",
"shasum": ""
},
"require": {
@@ -5754,11 +5754,11 @@
],
"description": "Symfony Validator Component",
"homepage": "https://symfony.com",
- "time": "2018-04-02T09:52:41+00:00"
+ "time": "2018-04-06T07:35:43+00:00"
},
{
"name": "symfony/web-link",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/web-link.git",
@@ -5829,7 +5829,7 @@
},
{
"name": "symfony/yaml",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
@@ -6812,7 +6812,7 @@
},
{
"name": "symfony/browser-kit",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
@@ -6869,7 +6869,7 @@
},
{
"name": "symfony/css-selector",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
@@ -6922,7 +6922,7 @@
},
{
"name": "symfony/debug-bundle",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug-bundle.git",
@@ -7017,7 +7017,7 @@
},
{
"name": "symfony/dom-crawler",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
@@ -7073,7 +7073,7 @@
},
{
"name": "symfony/dotenv",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/dotenv.git",
@@ -7130,16 +7130,16 @@
},
{
"name": "symfony/phpunit-bridge",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/phpunit-bridge.git",
- "reference": "cdfbf48b17ca56bb7fbcb4126f949665e5f426a3"
+ "reference": "e82f3f46384482f2a7dab5f00c58a36b9726bde9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/cdfbf48b17ca56bb7fbcb4126f949665e5f426a3",
- "reference": "cdfbf48b17ca56bb7fbcb4126f949665e5f426a3",
+ "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/e82f3f46384482f2a7dab5f00c58a36b9726bde9",
+ "reference": "e82f3f46384482f2a7dab5f00c58a36b9726bde9",
"shasum": ""
},
"require": {
@@ -7192,7 +7192,7 @@
],
"description": "Symfony PHPUnit Bridge",
"homepage": "https://symfony.com",
- "time": "2018-03-22T13:42:36+00:00"
+ "time": "2018-04-04T18:24:59+00:00"
},
{
"name": "symfony/polyfill-php72",
@@ -7279,7 +7279,7 @@
},
{
"name": "symfony/stopwatch",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/stopwatch.git",
@@ -7328,16 +7328,16 @@
},
{
"name": "symfony/var-dumper",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "f9b257f2009c7f09166bcfd3d3d96455741ed371"
+ "reference": "e1b4d008100f4d203cc38b0d793ad6252d8d8af0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/f9b257f2009c7f09166bcfd3d3d96455741ed371",
- "reference": "f9b257f2009c7f09166bcfd3d3d96455741ed371",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e1b4d008100f4d203cc38b0d793ad6252d8d8af0",
+ "reference": "e1b4d008100f4d203cc38b0d793ad6252d8d8af0",
"shasum": ""
},
"require": {
@@ -7393,20 +7393,20 @@
"debug",
"dump"
],
- "time": "2018-04-03T05:24:00+00:00"
+ "time": "2018-04-04T05:10:37+00:00"
},
{
"name": "symfony/web-profiler-bundle",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/web-profiler-bundle.git",
- "reference": "4d709edab20fdfc889a5c9a2b530453e4a4b8127"
+ "reference": "4f6a1f77120b5e4b37c59db34a8dc0a4d3df5cf0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/4d709edab20fdfc889a5c9a2b530453e4a4b8127",
- "reference": "4d709edab20fdfc889a5c9a2b530453e4a4b8127",
+ "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/4f6a1f77120b5e4b37c59db34a8dc0a4d3df5cf0",
+ "reference": "4f6a1f77120b5e4b37c59db34a8dc0a4d3df5cf0",
"shasum": ""
},
"require": {
@@ -7459,11 +7459,11 @@
],
"description": "Symfony WebProfilerBundle",
"homepage": "https://symfony.com",
- "time": "2018-04-02T09:52:41+00:00"
+ "time": "2018-04-04T13:50:32+00:00"
},
{
"name": "symfony/web-server-bundle",
- "version": "v4.0.7",
+ "version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/web-server-bundle.git",
From 6368e3d75c6e2f136433463e1501fa947356cb5b Mon Sep 17 00:00:00 2001
From: Emma
Date: Sat, 7 Apr 2018 11:24:32 +0200
Subject: [PATCH 14/24] fix unit tests
---
tests/Form/Model/CommentDataTest.php | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/tests/Form/Model/CommentDataTest.php b/tests/Form/Model/CommentDataTest.php
index ce072ba..bb3c231 100644
--- a/tests/Form/Model/CommentDataTest.php
+++ b/tests/Form/Model/CommentDataTest.php
@@ -3,6 +3,7 @@
namespace App\Tests\Form\Model;
use App\Entity\Comment;
+use App\Entity\Submission;
use App\Entity\User;
use App\Form\Model\CommentData;
use PHPUnit\Framework\MockObject\MockObject;
@@ -24,10 +25,14 @@ public static function setUpBeforeClass() {
protected function setUp() {
$this->comment = $this->getMockBuilder(Comment::class)
- ->setMethods(['getUser'])
+ ->setMethods(['getSubmission', 'getUser'])
->disableOriginalConstructor()
->getMock();
+ $this->comment
+ ->method('getSubmission')
+ ->willReturn($this->createMock(Submission::class));
+
$this->comment
->method('getUser')
->willReturn($this->createMock(User::class));
From 23d7e675e4c640922057dcc1b50cb7f9ebc8970b Mon Sep 17 00:00:00 2001
From: Emma
Date: Sat, 7 Apr 2018 18:25:21 +0200
Subject: [PATCH 15/24] test Votable/minor refactorings for voting stuff
---
src/Entity/Comment.php | 2 +-
src/Entity/CommentVote.php | 2 +-
src/Entity/Submission.php | 2 +-
src/Entity/SubmissionVote.php | 2 +-
src/Entity/Votable.php | 4 +-
src/Entity/Vote.php | 17 ++---
tests/Entity/VotableTest.php | 119 ++++++++++++++++++++++++++++++++++
7 files changed, 134 insertions(+), 14 deletions(-)
create mode 100644 tests/Entity/VotableTest.php
diff --git a/src/Entity/Comment.php b/src/Entity/Comment.php
index fbe1341..9fe6b15 100644
--- a/src/Entity/Comment.php
+++ b/src/Entity/Comment.php
@@ -210,7 +210,7 @@ protected function createVote(User $user, ?string $ip, int $choice): Vote {
/**
* {@inheritdoc}
*/
- public function vote(User $user, ?string $ip, int $choice) {
+ public function vote(User $user, ?string $ip, int $choice): void {
if ($this->submission->getForum()->userIsBanned($user)) {
throw new BannedFromForumException();
}
diff --git a/src/Entity/CommentVote.php b/src/Entity/CommentVote.php
index 37e532e..3901ffa 100644
--- a/src/Entity/CommentVote.php
+++ b/src/Entity/CommentVote.php
@@ -25,7 +25,7 @@ class CommentVote extends Vote {
*/
private $comment;
- public function __construct(User $user, ?string $ip, $choice, Comment $comment) {
+ public function __construct(User $user, ?string $ip, int $choice, Comment $comment) {
parent::__construct($user, $ip, $choice);
$this->comment = $comment;
diff --git a/src/Entity/Submission.php b/src/Entity/Submission.php
index 3e936ec..7f82fb4 100644
--- a/src/Entity/Submission.php
+++ b/src/Entity/Submission.php
@@ -270,7 +270,7 @@ protected function createVote(User $user, ?string $ip, int $choice): Vote {
/**
* {@inheritdoc}
*/
- public function vote(User $user, ?string $ip, int $choice) {
+ public function vote(User $user, ?string $ip, int $choice): void {
if ($this->forum->userIsBanned($user)) {
throw new BannedFromForumException();
}
diff --git a/src/Entity/SubmissionVote.php b/src/Entity/SubmissionVote.php
index 402175b..8204f40 100644
--- a/src/Entity/SubmissionVote.php
+++ b/src/Entity/SubmissionVote.php
@@ -28,7 +28,7 @@ class SubmissionVote extends Vote {
/**
* {@inheritdoc}
*/
- public function __construct(User $user, ?string $ip, $choice, Submission $submission) {
+ public function __construct(User $user, ?string $ip, int $choice, Submission $submission) {
parent::__construct($user, $ip, $choice);
$this->submission = $submission;
diff --git a/src/Entity/Votable.php b/src/Entity/Votable.php
index 4050933..8eba94d 100644
--- a/src/Entity/Votable.php
+++ b/src/Entity/Votable.php
@@ -35,7 +35,7 @@ abstract protected function createVote(User $user, ?string $ip, int $choice): Vo
*
* @throws \InvalidArgumentException if the vote is not a VOTE_* constant
*/
- public function vote(User $user, ?string $ip, int $choice) {
+ public function vote(User $user, ?string $ip, int $choice): void {
$vote = $this->getUserVote($user);
if ($choice === self::VOTE_UP || $choice === self::VOTE_DOWN) {
@@ -100,7 +100,7 @@ private function getUserVote(User $user): ?Vote {
* collection in advance speeds up things considerably when there are
* multiple entities.
*/
- private function hydrateVoteCollection() {
+ private function hydrateVoteCollection(): void {
$this->getVotes()->getValues();
}
}
diff --git a/src/Entity/Vote.php b/src/Entity/Vote.php
index a1b8155..a084757 100644
--- a/src/Entity/Vote.php
+++ b/src/Entity/Vote.php
@@ -52,8 +52,10 @@ abstract class Vote {
* @param User $user
* @param string|null $ip
* @param bool|int $choice
+ *
+ * @throws \InvalidArgumentException if $choice is bad
*/
- public function __construct(User $user, ?string $ip, $choice) {
+ public function __construct(User $user, ?string $ip, int $choice) {
$this->timestamp = new \DateTime('@'.time());
$this->user = $user;
$this->setIp($ip); // must be after $this->user is declared
@@ -69,13 +71,12 @@ public function getChoice(): int {
}
/**
- * @param int|bool $choice true/Votable::VOTE_UP = upvote,
- * false/Votable::VOTE_DOWN = downvote
+ * @param int $choice one of Votable::VOTE_UP or Votable::VOTE_DOWN
+ *
+ * @throws \InvalidArgumentException if $choice isn't a valid parameter
*/
- public function setChoice($choice) {
- if (is_bool($choice)) {
- $this->upvote = $choice;
- } elseif ($choice === Votable::VOTE_UP || $choice === Votable::VOTE_DOWN) {
+ public function setChoice(int $choice): void {
+ if ($choice === Votable::VOTE_UP || $choice === Votable::VOTE_DOWN) {
$this->upvote = $choice === Votable::VOTE_UP;
} elseif ($choice === Votable::VOTE_RETRACT) {
throw new \InvalidArgumentException('A vote entity cannot have a "retracted" status');
@@ -96,7 +97,7 @@ public function getIp(): ?string {
return $this->ip;
}
- public function setIp(?string $ip) {
+ public function setIp(?string $ip): void {
if ($ip !== null && !filter_var($ip, FILTER_VALIDATE_IP)) {
throw new \InvalidArgumentException('Bad IP address');
}
diff --git a/tests/Entity/VotableTest.php b/tests/Entity/VotableTest.php
new file mode 100644
index 0000000..a88bc9a
--- /dev/null
+++ b/tests/Entity/VotableTest.php
@@ -0,0 +1,119 @@
+votable = $this->createVotable();
+ }
+
+ public function testVotableScores(): void {
+ $votable = $this->createVotable();
+ $user = $this->createMock(User::class);
+
+ $this->assertEquals(0, $votable->getNetScore());
+ $this->assertEquals(0, $votable->getUpvotes());
+ $this->assertEquals(0, $votable->getDownvotes());
+
+ $votable->vote($user, null, Votable::VOTE_UP);
+
+ $this->assertEquals(1, $votable->getNetScore());
+ $this->assertEquals(1, $votable->getUpvotes());
+ $this->assertEquals(0, $votable->getDownvotes());
+
+ $votable->vote($user, null, Votable::VOTE_DOWN);
+
+ $this->assertEquals(-1, $votable->getNetScore());
+ $this->assertEquals(0, $votable->getUpvotes());
+ $this->assertEquals(1, $votable->getDownvotes());
+ }
+
+ public function testVoteCollectionHasCorrectProperties(): void {
+ $user = $this->createMock(User::class);
+
+ $this->votable->vote($user, null, Votable::VOTE_UP);
+ $this->assertEquals(Votable::USER_UPVOTED, $this->votable->getVotes()->first()->getChoice());
+ $this->assertCount(1, $this->votable->getVotes());
+
+ $this->votable->vote($user, null, Votable::VOTE_DOWN);
+ $this->assertEquals(Votable::USER_DOWNVOTED, $this->votable->getVotes()->first()->getChoice());
+ $this->assertCount(1, $this->votable->getVotes());
+
+ $this->votable->vote($user, null, Votable::VOTE_RETRACT);
+ $this->assertCount(0, $this->votable->getVotes());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testCannotGiveIncorrectVote(): void {
+ $user = $this->createMock(User::class);
+
+ $this->votable->vote($user, null, 69);
+ }
+
+ public function testGetUserVote(): void {
+ $user1 = $this->createMock(User::class);
+ $this->votable->vote($user1, null, Votable::VOTE_UP);
+
+ $user2 = $this->createMock(User::class);
+ $this->votable->vote($user2, null, Votable::VOTE_DOWN);
+
+ $user3 = $this->createMock(User::class);
+
+ $this->assertEquals(Votable::USER_UPVOTED, $this->votable->getUserChoice($user1));
+ $this->assertEquals(Votable::USER_DOWNVOTED, $this->votable->getUserChoice($user2));
+ $this->assertEquals(Votable::USER_NO_VOTE, $this->votable->getUserChoice($user3));
+ }
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function testAcceptsWellFormedIpAddresses(): void {
+ $user = $this->createMock(User::class);
+ $this->votable->vote($user, '127.0.4.20', Votable::VOTE_UP);
+ $this->votable->vote($user, '::69', Votable::VOTE_UP);
+ $this->votable->vote($user, null, Votable::VOTE_UP);
+ }
+
+ public function testThrowsExceptionOnBadIpAddress(): void {
+ $user = $this->createMock(User::class);
+
+ $this->expectException(\InvalidArgumentException::class);
+
+ $this->votable->vote($user, 'poop', Votable::VOTE_UP);
+ }
+
+ /**
+ * @return Votable
+ */
+ private function createVotable(): Votable {
+ return new class() extends Votable {
+ private $votes;
+
+ public function __construct() {
+ $this->votes = new ArrayCollection();
+ }
+
+ public function getVotes(): Collection {
+ return $this->votes;
+ }
+
+ protected function createVote(User $user, ?string $ip, int $choice): Vote {
+ return new class($user, $ip, $choice) extends Vote {};
+ }
+ };
+ }
+}
From 049b378e5ae98330c9f5533cf15466d96bb33e54 Mon Sep 17 00:00:00 2001
From: Emma
Date: Sat, 7 Apr 2018 19:25:50 +0200
Subject: [PATCH 16/24] give AjaxListener a proper unit test
---
tests/EventListener/AjaxListenerTest.php | 112 ++++++++++++++++++++---
1 file changed, 100 insertions(+), 12 deletions(-)
diff --git a/tests/EventListener/AjaxListenerTest.php b/tests/EventListener/AjaxListenerTest.php
index 7bf66d6..1b595ab 100644
--- a/tests/EventListener/AjaxListenerTest.php
+++ b/tests/EventListener/AjaxListenerTest.php
@@ -2,26 +2,114 @@
namespace App\Tests\EventListener;
-use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
+use App\EventListener\AjaxListener;
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\Security\Core\Exception\AccessDeniedException;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+use Symfony\Component\Serializer\Serializer;
/**
* @covers \App\EventListener\AjaxListener
*/
-class AjaxListenerTest extends WebTestCase {
- public function test403sOnAuthenticationFailure() {
- $client = $this->createClient([], [
- 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest',
- ]);
+class AjaxListenerTest extends KernelTestCase {
+ /**
+ * @var Serializer
+ */
+ private $serializer;
- $client->request('POST', '/cv/1.json');
+ /**
+ * @var AjaxListener
+ */
+ private $listener;
- $this->assertSame(403, $client->getResponse()->getStatusCode());
+ protected function setUp() {
+ static::bootKernel();
+
+ $this->serializer = self::$kernel->getContainer()->get('serializer');
+ $this->listener = new AjaxListener($this->serializer);
}
- public function testRedirectsToLoginWithoutXhr() {
- $client = $this->createClient();
- $client->request('POST', '/cv/1.json');
- $this->assertTrue($client->getResponse()->isRedirect());
+ public function testDoesNotSetResponseOnNotXhrRequests(): void {
+ $request = new Request();
+ $event = $this->createEvent($request, new AccessDeniedException());
+
+ $this->listener->onKernelException($event);
+
+ $this->assertNull($event->getResponse());
+ }
+
+ /**
+ * @dataProvider fourOhThreeOnExceptionWithSerializedBodyProvider
+ */
+ public function test403sOnExceptionWithSerializedBody(Request $request, \Exception $e): void {
+ $event = $this->createEvent($request, $e);
+
+ $this->listener->onKernelException($event);
+
+ $this->assertInstanceOf(Response::class, $event->getResponse());
+ $this->assertEquals(403, $event->getResponse()->getStatusCode());
+ $this->assertEquals(
+ $this->serializer->serialize(
+ ['error' => $e->getMessage()],
+ $request->getRequestFormat()
+ ),
+ $event->getResponse()->getContent()
+ );
+ }
+
+ /**
+ * @dataProvider fourOhThreeOnExceptionWithPlainBodyProvider
+ */
+ public function test403sOnExceptionWithPlainBody(Request $request, \Exception $e): void {
+ $event = $this->createEvent($request, $e);
+
+ $this->listener->onKernelException($event);
+
+ $this->assertInstanceOf(Response::class, $event->getResponse());
+ $this->assertEquals(403, $event->getResponse()->getStatusCode());
+ $this->assertEquals($e->getMessage(), $event->getResponse()->getContent());
+ }
+
+ public function fourOhThreeOnExceptionWithSerializedBodyProvider() {
+ $request = new Request();
+ $request->headers->set('X-Requested-With', 'XMLHttpRequest');
+ $request->setRequestFormat('json');
+ $exception = new AuthenticationException('foo');
+
+ yield [$request, $exception];
+
+ $request->setRequestFormat('xml');
+
+ yield [$request, $exception];
+
+ $exception = new AccessDeniedException('aaa');
+
+ yield [$request, $exception];
+ }
+
+ public function fourOhThreeOnExceptionWithPlainBodyProvider() {
+ $request = new Request();
+ $request->headers->set('X-Requested-With', 'XMLHttpRequest');
+ $exception = new AuthenticationException('sheep');
+
+ yield [$request, $exception];
+
+ $exception = new AccessDeniedException('cow');
+
+ yield [$request, $exception];
+ }
+
+ private function createEvent(Request $request, \Exception $e): GetResponseForExceptionEvent {
+ return new GetResponseForExceptionEvent(
+ self::$kernel,
+ $request,
+ HttpKernelInterface::MASTER_REQUEST,
+ $e
+ );
}
}
From d4325c10fb27a8c7e00f747d9e02ebf204cd1b6c Mon Sep 17 00:00:00 2001
From: Emma
Date: Sun, 8 Apr 2018 04:33:54 +0200
Subject: [PATCH 17/24] completely redo the way submissions are fetched
adds:
- keyset pagination (fast!)
- ability to limit to forums/users, and exclude forums/users
- reintroduced top/controversial sort modes
- adds a new 'most commented' sort mode
removes:
- previous button (no one cared anyway)
---
config/app_routes/forum.yaml | 24 +-
config/app_routes/forum_category.yaml | 6 +-
config/app_routes/front.yaml | 43 +--
config/services.yaml | 1 +
src/Controller/AbstractController.php | 6 +
src/Controller/ForumCategoryController.php | 9 +-
src/Controller/ForumController.php | 41 +--
src/Controller/FrontController.php | 81 ++++--
src/Entity/Submission.php | 4 +
.../Submission/NoSubmissionsException.php | 19 ++
src/Repository/Submission/SubmissionPager.php | 109 ++++++++
src/Repository/SubmissionRepository.php | 245 +++++++++++-------
templates/_includes/meta_pagination.html.twig | 12 +-
templates/_includes/pagination.html.twig | 4 +-
templates/_macros/feed_macros.xml.twig | 6 +-
templates/front/base.html.twig | 8 +-
templates/submission/_macros.html.twig | 4 +-
tests/ApplicationAvailabilityTest.php | 67 +++--
translations/messages.en.yml | 1 +
19 files changed, 478 insertions(+), 212 deletions(-)
create mode 100644 src/Repository/Submission/NoSubmissionsException.php
create mode 100644 src/Repository/Submission/SubmissionPager.php
diff --git a/config/app_routes/forum.yaml b/config/app_routes/forum.yaml
index adc578d..90915fc 100644
--- a/config/app_routes/forum.yaml
+++ b/config/app_routes/forum.yaml
@@ -1,24 +1,30 @@
multi:
controller: App\Controller\ForumController::multi
- defaults: { sortBy: hot, page: 1 }
- path: /f/{names}/{sortBy}/{page}
+ defaults: { sortBy: hot }
+ path: /f/{names}/{sortBy}
requirements:
names: '(?:\w{3,25}\+){1,70}\w{3,25}'
- sortBy: hot|new
+ sortBy: "%submission_sort_modes%"
forum:
controller: App\Controller\ForumController::front
- defaults: { sortBy: hot, page: 1 }
- path: /f/{forum_name}/{sortBy}/{page}
+ defaults: { sortBy: hot }
+ path: /f/{forum_name}/{sortBy}
methods: [GET]
- requirements: { sortBy: hot|new, page: \d+ }
+ requirements: { sortBy: "%submission_sort_modes%" }
forum_feed:
controller: App\Controller\ForumController::feed
- defaults: { sortBy: hot, page: 1, _format: xml }
- path: /f/{forum_name}/{sortBy}/{page}.atom
+ defaults: { sortBy: hot, _format: xml }
+ path: /f/{forum_name}/{sortBy}.atom
methods: [GET]
- requirements: { sortBy: hot|new, page: \d+ }
+ requirements: { sortBy: "%submission_sort_modes%" }
+
+forum_feed_legacy_redirect:
+ controller: FrameworkBundle:Redirect:redirect
+ defaults: { route: forum_feed, ignoreAttributes: [page] }
+ path: /f/{forum_name}/{sortBy}/{page}.atom
+ requirements: { page: \d+ }
edit_forum:
controller: App\Controller\ForumController::editForum
diff --git a/config/app_routes/forum_category.yaml b/config/app_routes/forum_category.yaml
index 795d12f..184bf0d 100644
--- a/config/app_routes/forum_category.yaml
+++ b/config/app_routes/forum_category.yaml
@@ -6,10 +6,10 @@ manage_forum_categories:
forum_category:
controller: App\Controller\ForumCategoryController::category
- defaults: { sortBy: hot, page: 1 }
- path: /c/{name}/{sortBy}/{page}
+ defaults: { sortBy: hot }
+ path: /c/{name}/{sortBy}
methods: [GET]
- requirements: { sortBy: hot|new, page: \d+ }
+ requirements: { sortBy: "%submission_sort_modes%" }
create_forum_category:
controller: App\Controller\ForumCategoryController::create
diff --git a/config/app_routes/front.yaml b/config/app_routes/front.yaml
index 625c6ab..bd4d02d 100644
--- a/config/app_routes/front.yaml
+++ b/config/app_routes/front.yaml
@@ -1,41 +1,48 @@
front:
controller: App\Controller\FrontController::front
- defaults: { sortBy: hot, page: 1 }
- path: /{sortBy}/{page}
+ defaults: { sortBy: hot }
+ path: /{sortBy}
methods: [GET]
- requirements: { sortBy: hot|new, page: \d+ }
+ requirements: { sortBy: "%submission_sort_modes%" }
featured:
controller: App\Controller\FrontController::featured
- defaults: { sortBy: hot, page: 1}
- path: /featured/{sortBy}/{page}
+ defaults: { sortBy: hot }
+ path: /featured/{sortBy}
methods: [GET]
- requirements: { sortBy: hot|new, page: \d+ }
+ requirements: { sortBy: "%submission_sort_modes%" }
subscribed:
controller: App\Controller\FrontController::subscribed
- defaults: { sortBy: hot, page: 1 }
- path: /subscribed/{sortBy}/{page}
+ defaults: { sortBy: hot }
+ path: /subscribed/{sortBy}
methods: [GET]
- requirements: { sortBy: hot|new, page: \d+ }
+ requirements: { sortBy: "%submission_sort_modes%" }
all:
controller: App\Controller\FrontController::all
- defaults: { sortBy: hot, page: 1 }
- path: /all/{sortBy}/{page}
+ defaults: { sortBy: hot }
+ path: /all/{sortBy}
methods: [GET]
- requirements: { sortBy: hot|new, page: \d+ }
+ requirements: { sortBy: "%submission_sort_modes%" }
moderated:
controller: App\Controller\FrontController::moderated
- defaults: { sortBy: hot, page: 1 }
- path: /moderated/{sortBy}/{page}
+ defaults: { sortBy: hot }
+ path: /moderated/{sortBy}
methods: [GET]
- requirements: { sortBy: hot|new, page: \d+ }
+ requirements: { sortBy: "%submission_sort_modes%" }
featured_feed:
controller: App\Controller\FrontController::featuredFeed
- defaults: { sortBy: hot, page: 1, _format: xml }
- path: /featured/{sortBy}/{page}.atom
+ defaults: { sortBy: hot, _format: xml }
+ path: /featured/{sortBy}.atom
methods: [GET]
- requirements: { sortBy: hot|new, page: \d+ }
+ requirements: { sortBy: "%submission_sort_modes%" }
+
+featured_feed_legacy_redirect:
+ controller: FrameworkBundle:Redirect:redirect
+ defaults: { route: featured_feed, ignoreAttributes: true }
+ methods: [GET]
+ path: /featured/{sortBy}/{_page}.atom
+ requirements: { sortBy: "%submission_sort_modes%", page: \d+ }
diff --git a/config/services.yaml b/config/services.yaml
index 9d66a0b..96601f0 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -6,6 +6,7 @@ parameters:
uuid_regex: '[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}'
wiki_page_regex: '[A-Za-z][A-Za-z0-9_-]*(/[A-Za-z][A-Za-z0-9_-]*)*'
env(APP_ENABLE_WEBHOOKS): false
+ submission_sort_modes: hot|new|top|controversial|most_commented
services:
_defaults:
diff --git a/src/Controller/AbstractController.php b/src/Controller/AbstractController.php
index ce358a8..4874233 100644
--- a/src/Controller/AbstractController.php
+++ b/src/Controller/AbstractController.php
@@ -2,10 +2,16 @@
namespace App\Controller;
+use App\Repository\Submission\SubmissionPager;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController as BaseAbstractController;
+use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
abstract class AbstractController extends BaseAbstractController {
+ protected function submissionPage(string $sortBy, Request $request): array {
+ return SubmissionPager::getParamsFromRequest($sortBy, $request);
+ }
+
protected function validateCsrf(string $id, string $token) {
if (!$this->isCsrfTokenValid($id, $token)) {
throw new BadRequestHttpException('Invalid CSRF token');
diff --git a/src/Controller/ForumCategoryController.php b/src/Controller/ForumCategoryController.php
index 0af013c..b1f11c1 100644
--- a/src/Controller/ForumCategoryController.php
+++ b/src/Controller/ForumCategoryController.php
@@ -16,13 +16,16 @@
class ForumCategoryController extends AbstractController {
public function category(
ForumCategory $category,
- string $sortBy, int $page,
+ string $sortBy,
ForumRepository $fr,
- SubmissionRepository $sr
+ SubmissionRepository $sr,
+ Request $request
): Response {
$forums = $fr->findForumsInCategory($category);
- $submissions = $sr->findFrontPageSubmissions($forums, $sortBy, $page);
+ $submissions = $sr->findSubmissions($sortBy, [
+ 'forums' => array_keys($forums),
+ ], $this->submissionPage($sortBy, $request));
return $this->render('forum_category/category.html.twig', [
'category' => $category,
diff --git a/src/Controller/ForumController.php b/src/Controller/ForumController.php
index 49efcce..8516aaf 100644
--- a/src/Controller/ForumController.php
+++ b/src/Controller/ForumController.php
@@ -34,27 +34,34 @@
* @Entity("user", expr="repository.findOneOrRedirectToCanonical(username, 'username')")
*/
final class ForumController extends AbstractController {
+ /**
+ * @var SubmissionRepository
+ */
+ private $submissions;
+
/**
* @var bool
*/
private $enableWebhooks;
- public function __construct(bool $enableWebhooks) {
+ public function __construct(SubmissionRepository $submissions, bool $enableWebhooks) {
+ $this->submissions = $submissions;
$this->enableWebhooks = $enableWebhooks;
}
/**
* Show the front page of a given forum.
*
- * @param SubmissionRepository $sr
- * @param Forum $forum
- * @param string $sortBy
- * @param int $page
+ * @param Forum $forum
+ * @param string $sortBy
*
* @return Response
*/
- public function front(SubmissionRepository $sr, Forum $forum, string $sortBy, int $page) {
- $submissions = $sr->findForumSubmissions($forum, $sortBy, $page);
+ public function front(Forum $forum, string $sortBy, Request $request): Response {
+ $submissions = $this->submissions->findSubmissions($sortBy, [
+ 'forums' => [$forum->getId()],
+ 'stickies' => true,
+ ], $this->submissionPage($sortBy, $request));
return $this->render('forum/forum.html.twig', [
'forum' => $forum,
@@ -63,8 +70,7 @@ public function front(SubmissionRepository $sr, Forum $forum, string $sortBy, in
]);
}
- public function multi(ForumRepository $fr, SubmissionRepository $sr,
- string $names, string $sortBy, int $page) {
+ public function multi(ForumRepository $fr, string $names, string $sortBy, Request $request) {
$names = preg_split('/[^\w]+/', $names, -1, PREG_SPLIT_NO_EMPTY);
$names = array_map(Forum::class.'::normalizeName', $names);
$names = $fr->findForumNames($names);
@@ -73,7 +79,9 @@ public function multi(ForumRepository $fr, SubmissionRepository $sr,
throw $this->createNotFoundException('no such forums');
}
- $submissions = $sr->findFrontPageSubmissions($names, $sortBy, $page);
+ $submissions = $this->submissions->findSubmissions($sortBy, [
+ 'forums' => array_keys($names),
+ ], $this->submissionPage($sortBy, $request));
return $this->render('forum/multi.html.twig', [
'forums' => $names,
@@ -148,17 +156,18 @@ public function editForum(Request $request, Forum $forum, EntityManager $em) {
}
/**
- * @param Forum $forum
- * @param SubmissionRepository $sr
- * @param string $sortBy
- * @param int $page
+ * @param Forum $forum
+ * @param string $sortBy
+ * @param Request $request
*
* @return Response
*/
- public function feed(Forum $forum, SubmissionRepository $sr, string $sortBy, int $page) {
+ public function feed(Forum $forum, string $sortBy, Request $request) {
return $this->render('forum/feed.xml.twig', [
'forum' => $forum,
- 'submissions' => $sr->findForumSubmissions($forum, $sortBy, $page),
+ 'submissions' => $this->submissions->findSubmissions($sortBy, [
+ 'forums' => [$forum->getId()],
+ ], $this->submissionPage($sortBy, $request)),
]);
}
diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php
index 549b7e3..34dc192 100644
--- a/src/Controller/FrontController.php
+++ b/src/Controller/FrontController.php
@@ -5,6 +5,8 @@
use App\Entity\User;
use App\Repository\ForumRepository;
use App\Repository\SubmissionRepository;
+use App\Repository\Submission\SubmissionPager;
+use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
@@ -25,7 +27,25 @@
* instead.
*/
final class FrontController extends AbstractController {
- public function front(ForumRepository $fr, SubmissionRepository $sr, string $sortBy, int $page) {
+ /**
+ * @var ForumRepository
+ */
+ private $forums;
+
+ /**
+ * @var SubmissionRepository
+ */
+ private $submissions;
+
+ public function __construct(
+ ForumRepository $forums,
+ SubmissionRepository $submissions
+ ) {
+ $this->forums = $forums;
+ $this->submissions = $submissions;
+ }
+
+ public function front(string $sortBy, Request $request): Response {
$user = $this->getUser();
if (!$user instanceof User) {
@@ -38,21 +58,24 @@ public function front(ForumRepository $fr, SubmissionRepository $sr, string $sor
switch ($listing) {
case User::FRONT_SUBSCRIBED:
- return $this->subscribed($fr, $sr, $sortBy, $page);
+ return $this->subscribed($sortBy, $request);
case User::FRONT_FEATURED:
- return $this->featured($fr, $sr, $sortBy, $page);
+ return $this->featured($sortBy, $request);
case User::FRONT_ALL:
- return $this->all($sr, $sortBy, $page);
+ return $this->all($sortBy, $request);
case User::FRONT_MODERATED:
- return $this->moderated($fr, $sr, $sortBy, $page);
+ return $this->moderated($sortBy, $request);
default:
throw new \InvalidArgumentException('bad front page selection');
}
}
- public function featured(ForumRepository $fr, SubmissionRepository $sr, string $sortBy, int $page) {
- $forums = $fr->findFeaturedForumNames();
- $submissions = $sr->findFrontPageSubmissions($forums, $sortBy, $page);
+ public function featured(string $sortBy, Request $request): Response {
+ $forums = $this->forums->findFeaturedForumNames();
+
+ $submissions = $this->submissions->findSubmissions($sortBy, [
+ 'forums' => array_keys($this->forums->findFeaturedForumNames()),
+ ], $this->submissionPage($sortBy, $request));
return $this->render('front/featured.html.twig', [
'forums' => $forums,
@@ -62,17 +85,19 @@ public function featured(ForumRepository $fr, SubmissionRepository $sr, string $
]);
}
- public function subscribed(ForumRepository $fr, SubmissionRepository $sr, string $sortBy, int $page) {
+ public function subscribed(string $sortBy, Request $request): Response {
$this->denyAccessUnlessGranted('ROLE_USER');
- $forums = $fr->findSubscribedForumNames($this->getUser());
- $hasSubscriptions = count($forums) > 0;
+ $forums = $this->forums->findSubscribedForumNames($this->getUser());
+ $hasSubscriptions = \count($forums) > 0;
if (!$hasSubscriptions) {
- $forums = $fr->findFeaturedForumNames();
+ $forums = $this->forums->findFeaturedForumNames();
}
- $submissions = $sr->findFrontPageSubmissions($forums, $sortBy, $page);
+ $submissions = $this->submissions->findSubmissions($sortBy, [
+ 'forums' => array_keys($forums),
+ ], $this->submissionPage($sortBy, $request));
return $this->render('front/subscribed.html.twig', [
'forums' => $forums,
@@ -83,15 +108,9 @@ public function subscribed(ForumRepository $fr, SubmissionRepository $sr, string
]);
}
- /**
- * @param SubmissionRepository $sr
- * @param string $sortBy
- * @param int $page
- *
- * @return Response
- */
- public function all(SubmissionRepository $sr, string $sortBy, int $page) {
- $submissions = $sr->findAllSubmissions($sortBy, $page);
+ public function all(string $sortBy, Request $request): Response {
+ $submissions = $this->submissions->findSubmissions($sortBy, [],
+ $this->submissionPage($sortBy, $request));
return $this->render('front/all.html.twig', [
'listing' => 'all',
@@ -100,11 +119,14 @@ public function all(SubmissionRepository $sr, string $sortBy, int $page) {
]);
}
- public function moderated(ForumRepository $fr, SubmissionRepository $sr, string $sortBy, int $page) {
+ public function moderated(string $sortBy, Request $request): Response {
$this->denyAccessUnlessGranted('ROLE_USER');
- $forums = $fr->findModeratedForumNames($this->getUser());
- $submissions = $sr->findFrontPageSubmissions($forums, $sortBy, $page);
+ $forums = $this->forums->findModeratedForumNames($this->getUser());
+
+ $submissions = $this->submissions->findSubmissions($sortBy, [
+ 'forums' => array_keys($forums),
+ ], $this->submissionPage($sortBy, $request));
return $this->render('front/moderated.html.twig', [
'forums' => $forums,
@@ -114,9 +136,12 @@ public function moderated(ForumRepository $fr, SubmissionRepository $sr, string
]);
}
- public function featuredFeed(ForumRepository $fr, SubmissionRepository $sr, string $sortBy, int $page = 1) {
- $forums = $fr->findFeaturedForumNames();
- $submissions = $sr->findFrontPageSubmissions($forums, $sortBy, $page);
+ public function featuredFeed(string $sortBy, Request $request): Response {
+ $forums = $this->forums->findFeaturedForumNames();
+
+ $submissions = $this->submissions->findSubmissions($sortBy, [
+ 'forums' => array_keys($forums),
+ ], $this->submissionPage($sortBy, $request));
return $this->render('front/featured.xml.twig', [
'forums' => $forums,
diff --git a/src/Entity/Submission.php b/src/Entity/Submission.php
index 7f82fb4..388160a 100644
--- a/src/Entity/Submission.php
+++ b/src/Entity/Submission.php
@@ -215,6 +215,10 @@ public function getComments(): Collection {
return $this->comments;
}
+ public function getCommentCount(): int {
+ return \count($this->comments);
+ }
+
/**
* Get top-level comments, ordered by descending net score.
*
diff --git a/src/Repository/Submission/NoSubmissionsException.php b/src/Repository/Submission/NoSubmissionsException.php
new file mode 100644
index 0000000..67b498a
--- /dev/null
+++ b/src/Repository/Submission/NoSubmissionsException.php
@@ -0,0 +1,19 @@
+query->get('next_'.$column);
+ $type = SubmissionRepository::SORT_COLUMN_TYPES[$column];
+
+ if ($value === null || !self::valueIsOfType($type, $value)) {
+ // missing columns - no pagination
+ return [];
+ }
+
+ $params[$column] = $value;
+ }
+
+ // complete pager params
+ return $params;
+ }
+
+ /**
+ * @param Submission[]|iterable $submissions List of submissions, including
+ * one more than $maxPerPage to
+ * tell if there's a next page
+ * @param int $maxPerPage
+ * @param string $sortBy property to use for pagination
+ */
+ public function __construct(iterable $submissions, int $maxPerPage, string $sortBy) {
+ if (!isset(SubmissionRepository::SORT_COLUMN_MAP[$sortBy])) {
+ throw new \InvalidArgumentException("Invalid sort mode '$sortBy'");
+ }
+
+ $count = 0;
+
+ foreach ($submissions as $submission) {
+ if (++$count > $maxPerPage) {
+ foreach (SubmissionRepository::SORT_COLUMN_MAP[$sortBy] as $column) {
+ $accessor = $this->columnNameToAccessor($column);
+ $value = $submission->{$accessor}();
+
+ $this->nextPageParams['next_'.$column] = $value;
+ }
+
+ break;
+ }
+
+ $this->submissions[] = $submission;
+ }
+ }
+
+ public function getIterator() {
+ return new \ArrayIterator($this->submissions);
+ }
+
+ public function hasNextPage(): bool {
+ return (bool) $this->nextPageParams;
+ }
+
+ /**
+ * @throws \BadMethodCallException if there is no next page
+ */
+ public function getNextPageParams(): array {
+ if (!$this->hasNextPage()) {
+ throw new \BadMethodCallException('There is no next page');
+ }
+
+ return $this->nextPageParams;
+ }
+
+ private function columnNameToAccessor(string $columnName): string {
+ return 'get'.str_replace('_', '', ucwords($columnName, '_'));
+ }
+
+ private static function valueIsOfType(string $type, $value): bool {
+ switch ($type) {
+ case 'integer':
+ return ctype_digit($value) && \is_int(+$value) &&
+ $value >= 0x80000000 && $value <= 0x7fffffff;
+ case 'bigint':
+ // if this causes problems on 32-bit systems, the site operators
+ // deserved it.
+ return ctype_digit($value) && \is_int(+$value);
+ default:
+ throw new \InvalidArgumentException("Unexpected type '$type'");
+ }
+ }
+}
diff --git a/src/Repository/SubmissionRepository.php b/src/Repository/SubmissionRepository.php
index 5f2eeb6..e52ded7 100644
--- a/src/Repository/SubmissionRepository.php
+++ b/src/Repository/SubmissionRepository.php
@@ -2,114 +2,196 @@
namespace App\Repository;
-use App\Entity\Forum;
use App\Entity\Submission;
-use App\Utils\PrependOrderBy;
+use App\Repository\Submission\NoSubmissionsException;
+use App\Repository\Submission\SubmissionPager;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;
-use Doctrine\ORM\QueryBuilder;
-use Pagerfanta\Adapter\DoctrineORMAdapter;
-use Pagerfanta\Pagerfanta;
+use Doctrine\DBAL\Query\QueryBuilder;
class SubmissionRepository extends ServiceEntityRepository {
- const MAX_PER_PAGE = 25;
+ public const SORT_HOT = 'hot';
+ public const SORT_NEW = 'new';
+ public const SORT_TOP = 'top';
+ public const SORT_CONTROVERSIAL = 'controversial';
+ public const SORT_MOST_COMMENTED = 'most_commented';
+
+ /**
+ * `$sortBy` -> ordered column name mapping
+ *
+ * @var array[]
+ */
+ public const SORT_COLUMN_MAP = [
+ self::SORT_HOT => ['ranking', 'id'],
+ self::SORT_NEW => ['id'],
+ self::SORT_TOP => ['net_score', 'id'],
+ self::SORT_CONTROVERSIAL => ['downvotes', 'id'],
+ self::SORT_MOST_COMMENTED => ['comment_count', 'id'],
+ ];
+
+ public const SORT_COLUMN_TYPES = [
+ 'ranking' => 'bigint',
+ 'id' => 'bigint',
+ 'net_score' => 'integer',
+ 'downvotes' => 'integer',
+ 'comment_count' => 'integer',
+ ];
+
+ private const MAX_PER_PAGE = 25;
+
+ private const NET_SCORE_JOIN = '('.
+ 'SELECT submission_id, '.
+ 'COUNT(*) FILTER (WHERE upvote = TRUE) - '.
+ 'COUNT(*) FILTER (WHERE upvote = FALSE) AS net_score '.
+ 'FROM submission_votes '.
+ 'GROUP BY submission_id'.
+ ')';
+
+ // TODO: implement actually useful controversy metric
+ private const CONTROVERSIAL_JOIN = '('.
+ 'SELECT submission_id, COUNT(*) AS downvotes '.
+ 'FROM submission_votes '.
+ 'WHERE NOT upvote = FALSE '.
+ 'GROUP BY submission_id'.
+ ')';
+
+ private const COMMENT_COUNT_JOIN = '('.
+ 'SELECT submission_id, COUNT(*) AS comment_count '.
+ 'FROM comments '.
+ 'GROUP BY submission_id'.
+ ')';
public function __construct(ManagerRegistry $registry) {
parent::__construct($registry, Submission::class);
}
/**
- * @param string[] $forums array where keys are forum IDs
- * @param string $sortBy
- * @param int $page
+ * The amazing submission finder.
+ *
+ * @param string $sortBy One of SORT_* constants
+ * @param array $options An array with the following keys:
+ *
+ * forums : IDs of forums to restrict
+ * to.
+ * excluded_forums : IDs of excluded
+ * (blacklisted) forums. Blacklisted forums will
+ * not be displayed even if they are included in
+ * forums .
+ * users : IDs of users to restrict to.
+ * excluded_users : IDs of excluded
+ * users. Again, blacklisting takes precedence.
+ * stickies : Put stickies first.
+ * max_per_page : Self-explanatory.
+ *
+ * @param array $pager
+ *
+ * @return Submission[]|SubmissionPager
*
- * @return Pagerfanta|Submission[]
+ * @throws \InvalidArgumentException if $sortBy is bad
+ * @throws NoSubmissionsException if there are no submissions
*/
- public function findFrontPageSubmissions(array $forums, string $sortBy, int $page = 1) {
- if (isset($forums[0])) {
- // make sure $forums is id => forum_name array
- throw new \InvalidArgumentException('Keys in $forums must be IDs');
- }
+ public function findSubmissions(string $sortBy, array $options = [], array $pager = []) {
+ $maxPerPage = $options['max_per_page'] ?? self::MAX_PER_PAGE;
- $qb = $this->findSortedQb($sortBy)
- ->where('IDENTITY(s.forum) IN (:forums)')
- ->setParameter(':forums', array_keys($forums));
+ $rsm = $this->createResultSetMappingBuilder('s');
- $submissions = $this->paginate($qb, $page);
+ $qb = $this->_em->getConnection()->createQueryBuilder()
+ ->select($rsm->generateSelectClause())
+ ->from('submissions', 's')
+ ->setMaxResults($maxPerPage + 1);
- $this->hydrateAssociations($submissions);
+ switch ($sortBy) {
+ case self::SORT_HOT:
+ case self::SORT_NEW:
+ break;
+ case self::SORT_TOP:
+ $qb->join('s', self::NET_SCORE_JOIN, 'ns', 's.id = ns.submission_id');
+ break;
+ case self::SORT_CONTROVERSIAL:
+ $qb->join('s', self::CONTROVERSIAL_JOIN, 'cn', 's.id = cn.submission_id');
+ break;
+ case self::SORT_MOST_COMMENTED:
+ $qb->join('s', self::COMMENT_COUNT_JOIN, 'cc', 's.id = cc.submission_id');
+ break;
+ default:
+ throw new \InvalidArgumentException("Sort mode '$sortBy' not implemented");
+ }
- return $submissions;
- }
+ if (!$pager && !empty($options['stickies'])) {
+ // FIXME: won't work if there are >= $maxPerPage stickies (lol)
+ $qb->orderBy('sticky', 'DESC');
+ }
- /**
- * @param Forum $forum
- * @param string $sortBy
- * @param int $page
- *
- * @return Pagerfanta|Submission[]
- */
- public function findForumSubmissions(Forum $forum, string $sortBy, int $page = 1) {
- $qb = $this->findSortedQb($sortBy)
- ->andWhere('s.forum = :forum')
- ->setParameter('forum', $forum);
+ foreach (self::SORT_COLUMN_MAP[$sortBy] as $column) {
+ $qb->addOrderBy($column, 'DESC');
+ }
+
+ if ($pager) {
+ $qb->andWhere(sprintf('(%s) <= (:next_%s)',
+ implode(', ', self::SORT_COLUMN_MAP[$sortBy]),
+ implode(', :next_', self::SORT_COLUMN_MAP[$sortBy])
+ ));
- if ($sortBy === 'hot') {
- PrependOrderBy::prepend($qb, 's.sticky', 'DESC');
+ foreach (self::SORT_COLUMN_MAP[$sortBy] as $column) {
+ $qb->setParameter('next_'.$column, $pager[$column]);
+ }
}
- $submissions = $this->paginate($qb, $page);
+ self::filterQuery($qb, $options);
- $this->hydrateAssociations($submissions);
+ $results = $this->_em
+ ->createNativeQuery($qb->getSQL(), $rsm)
+ ->setParameters($qb->getParameters())
+ ->execute();
- return $submissions;
- }
+ if ($pager && \count($results) === 0) {
+ throw new NoSubmissionsException();
+ }
- /**
- * @param string $sortBy
- * @param int $page
- *
- * @return Pagerfanta|Submission[]
- */
- public function findAllSubmissions(string $sortBy, int $page = 1) {
- $submissions = $this->paginate($this->findSortedQb($sortBy), $page);
+ $submissions = new SubmissionPager($results, $maxPerPage, $sortBy);
$this->hydrateAssociations($submissions);
return $submissions;
}
- /**
- * @param string $sortType one of 'hot' or 'new'
- *
- * @return QueryBuilder
- */
- public function findSortedQb($sortType) {
- $qb = $this->createQueryBuilder('s');
-
- switch ($sortType) {
- case 'hot':
- $this->sortByHot($qb);
- break;
- case 'new':
- $this->sortByNewest($qb);
- break;
- case 'top':
- throw new \InvalidArgumentException('Sorting by "top" is no longer supported');
- case 'controversial':
- throw new \InvalidArgumentException('Sorting by "controversial" is no longer supported');
- default:
- throw new \InvalidArgumentException('Bad sort type');
+ private static function filterQuery(QueryBuilder $qb, array $options): void {
+ if (!empty($options['forums'])) {
+ /* @noinspection NotOptimalIfConditionsInspection */
+ if (!empty($options['excluded_forums'])) {
+ $options['forums'] = array_diff(
+ $options['forums'],
+ $options['excluded_forums']
+ );
+ }
+
+ $qb->andWhere('s.forum_id IN (:forum_ids)');
+ $qb->setParameter('forum_ids', $options['forums']);
+ } elseif (!empty($options['excluded_forums'])) {
+ $qb->andWhere('s.forum_id NOT IN (:forum_ids)');
+ $qb->setParameter('forum_ids', $options['excluded_forums']);
}
- return $qb;
+ if (!empty($options['users'])) {
+ /* @noinspection NotOptimalIfConditionsInspection */
+ if (!empty($options['excluded_users'])) {
+ $options['users'] = array_diff(
+ $options['users'],
+ $options['excluded_users']
+ );
+ }
+
+ $qb->andWhere('s.user_id IN (:user_ids)');
+ $qb->setParameter('user_ids', $options['users']);
+ } elseif (!empty($options['excluded_users'])) {
+ $qb->andWhere('s.user_id NOT IN (:user_ids)');
+ $qb->setParameter('user_ids', $options['excluded_users']);
+ }
}
- public function hydrateAssociations($submissions) {
+ private function hydrateAssociations(iterable $submissions): void {
if ($submissions instanceof \Traversable) {
$submissions = iterator_to_array($submissions);
- } elseif (!is_array($submissions)) {
- throw new \InvalidArgumentException('$submissions must be iterable');
}
$this->_em->createQueryBuilder()
@@ -144,23 +226,4 @@ public function hydrateAssociations($submissions) {
->getQuery()
->getResult();
}
-
- private function sortByHot(QueryBuilder $qb) {
- $qb->addOrderBy('s.ranking', 'DESC');
- $qb->addOrderBy('s.id', 'DESC');
- }
-
- private function sortByNewest(QueryBuilder $qb) {
- $qb->addOrderBy('s.id', 'DESC');
- }
-
- private function paginate($query, int $page): Pagerfanta {
- // I don't think we need to fetch-join when joined entities aren't
- // included in the result.
- $pager = new Pagerfanta(new DoctrineORMAdapter($query, false, false));
- $pager->setMaxPerPage(self::MAX_PER_PAGE);
- $pager->setCurrentPage($page);
-
- return $pager;
- }
}
diff --git a/templates/_includes/meta_pagination.html.twig b/templates/_includes/meta_pagination.html.twig
index a298d0e..f0327eb 100644
--- a/templates/_includes/meta_pagination.html.twig
+++ b/templates/_includes/meta_pagination.html.twig
@@ -1,9 +1,15 @@
{% with {attr: app.request.attributes, get: app.request.query.all} %}
- {% if pager.hasPreviousPage %}
-
+ {% if pager.hasPreviousPage ?? false %}
+
{% endif %}
{% if pager.hasNextPage %}
-
+
{% endif %}
{% endwith %}
diff --git a/templates/_includes/pagination.html.twig b/templates/_includes/pagination.html.twig
index f903444..9a3f5d4 100644
--- a/templates/_includes/pagination.html.twig
+++ b/templates/_includes/pagination.html.twig
@@ -1,7 +1,7 @@
{% with {
attr: app.request.attributes,
get: app.request.query.all,
- hasPrev: pager.hasPreviousPage,
+ hasPrev: pager.hasPreviousPage ?? false,
hasNext: pager.hasNextPage,
} %}
{% if hasPrev or hasNext %}
@@ -16,7 +16,7 @@
{% endif %}
{% if hasNext %}
-
+
{{ 'nav.next'|trans }}
diff --git a/templates/_macros/feed_macros.xml.twig b/templates/_macros/feed_macros.xml.twig
index da7c31d..62258f5 100644
--- a/templates/_macros/feed_macros.xml.twig
+++ b/templates/_macros/feed_macros.xml.twig
@@ -1,11 +1,7 @@
{% macro pagination(pager) %}
{% with { route: app.request.attributes.get('_route'), params: app.request.attributes.get('_route_params') ?? {} } %}
{% if pager.hasNextPage %}
-
- {% endif %}
-
- {% if pager.hasPreviousPage %}
-
+
{% endif %}
{% endwith %}
{% endmacro %}
diff --git a/templates/front/base.html.twig b/templates/front/base.html.twig
index 595ef00..cc5fd93 100644
--- a/templates/front/base.html.twig
+++ b/templates/front/base.html.twig
@@ -33,7 +33,7 @@
{%- macro submission_filter(choice, sort_by) -%}
{% with { active: choice == 'featured' } %}
-
+
{{- 'front.featured'|trans -}}
@@ -42,7 +42,7 @@
{% if is_granted('ROLE_USER') %}
{% with { active: choice == 'subscribed' } %}
-
+
{{- 'front.subscribed'|trans -}}
@@ -51,7 +51,7 @@
{% with { active: choice == 'all' } %}
-
+
{{- 'front.all'|trans -}}
@@ -60,7 +60,7 @@
{% if choice == 'moderated' or app.user and app.user.moderatorTokens|length > 0 %}
{% with { active: choice == 'moderated' } %}
-
+
{{- 'nav.moderated'|trans -}}
diff --git a/templates/submission/_macros.html.twig b/templates/submission/_macros.html.twig
index dff6112..4c1b14a 100644
--- a/templates/submission/_macros.html.twig
+++ b/templates/submission/_macros.html.twig
@@ -10,9 +10,9 @@
{%- macro submission_sort(current) -%}
{%- set attr = app.request.attributes -%}
{%- spaceless -%}
- {%- for type in ['hot', 'new'] -%}
+ {%- for type in ['hot', 'new', 'top', 'controversial', 'most_commented'] -%}
-
+
{{- ('submissions.sort_by_'~type)|trans -}}
diff --git a/tests/ApplicationAvailabilityTest.php b/tests/ApplicationAvailabilityTest.php
index db35da9..e8a09ac 100644
--- a/tests/ApplicationAvailabilityTest.php
+++ b/tests/ApplicationAvailabilityTest.php
@@ -14,7 +14,7 @@ class ApplicationAvailabilityTest extends WebTestCase {
* @param string $url
*/
public function testCanAccessPublicPages($url) {
- $client = $this->createClient();
+ $client = self::createClient();
$client->request('GET', $url);
$this->assertTrue($client->getResponse()->isSuccessful());
@@ -26,7 +26,7 @@ public function testCanAccessPublicPages($url) {
* @param string $url
*/
public function testCanAccessPagesThatNeedAuthentication($url) {
- $client = $this->createClient([], [
+ $client = self::createClient([], [
'PHP_AUTH_USER' => 'emma',
'PHP_AUTH_PW' => 'goodshit',
]);
@@ -41,7 +41,7 @@ public function testCanAccessPagesThatNeedAuthentication($url) {
* @param string $url
*/
public function testCannotAccessPagesThatNeedAuthenticationWhenNotAuthenticated($url) {
- $client = $this->createClient();
+ $client = self::createClient();
$client->request('GET', $url);
$this->assertTrue($client->getResponse()->isRedirect());
@@ -55,10 +55,12 @@ public function testCannotAccessPagesThatNeedAuthenticationWhenNotAuthenticated(
* @param string $url
*/
public function testRedirectedUrlsGoToExpectedLocation($expectedLocation, $url) {
- $client = $this->createClient();
+ $client = self::createClient();
$client->followRedirects();
$client->request('GET', $url);
+ $this->assertTrue($client->getResponse()->isSuccessful());
+
$this->assertEquals(
"http://localhost{$expectedLocation}",
$client->getCrawler()->getUri()
@@ -73,29 +75,37 @@ public function publicUrlProvider() {
yield ['/'];
yield ['/hot'];
yield ['/new'];
- yield ['/hot/1'];
- yield ['/new/1'];
+ yield ['/top'];
+ yield ['/controversial'];
+ yield ['/most_commented'];
yield ['/all/hot'];
yield ['/all/new'];
- yield ['/all/hot/1'];
- yield ['/all/new/1'];
+ yield ['/all/top'];
+ yield ['/all/controversial'];
+ yield ['/all/most_commented'];
yield ['/featured/hot'];
yield ['/featured/new'];
- yield ['/featured/hot/1'];
- yield ['/featured/new/1'];
- yield ['/featured/hot/1.atom'];
- yield ['/featured/new/1.atom'];
+ yield ['/featured/top'];
+ yield ['/featured/controversial'];
+ yield ['/featured/most_commented'];
+ yield ['/featured/hot.atom'];
+ yield ['/featured/new.atom'];
+ yield ['/featured/top.atom'];
+ yield ['/featured/controversial.atom'];
+ yield ['/featured/most_commented.atom'];
yield ['/f/news/hot'];
yield ['/f/news/new'];
- yield ['/f/news/hot/1'];
- yield ['/f/news/new/1'];
- yield ['/f/news/hot/1.atom'];
- yield ['/f/news/new/1.atom'];
- yield ['/f/news/1'];
+ yield ['/f/news/top'];
+ yield ['/f/news/controversial'];
+ yield ['/f/news/most_commented'];
+ yield ['/f/news/hot.atom'];
+ yield ['/f/news/new.atom'];
+ yield ['/f/news/top.atom'];
+ yield ['/f/news/controversial.atom'];
+ yield ['/f/news/most_commented.atom'];
yield ['/f/news/1/comment/1'];
yield ['/f/news/bans'];
yield ['/f/news/moderation_log'];
- yield ['/f/cats/2'];
yield ['/forums'];
yield ['/forums/by_name'];
yield ['/forums/by_title'];
@@ -112,16 +122,15 @@ public function publicUrlProvider() {
}
public function redirectUrlProvider() {
- yield ['/f/cats/2', '/f/cats/2/'];
yield ['/f/cats', '/f/cats/'];
- yield ['/f/cats/2', '/f/CATS/2/'];
- yield ['/f/cats/2', '/f/CATS/2'];
yield ['/f/news', '/f/NeWs/hot'];
yield ['/f/news/new', '/f/NeWs/new'];
- yield ['/f/news', '/f/NeWs/hot/1'];
- yield ['/f/news/new', '/f/NeWs/new/1'];
- yield ['/f/news/1', '/f/NeWs/1'];
+ yield ['/f/news/top', '/f/NeWs/top'];
+ yield ['/f/news/controversial', '/f/NeWs/controversial'];
+ yield ['/f/news/most_commented', '/f/NeWs/most_commented'];
yield ['/f/news/1/comment/1', '/f/NeWs/1/comment/1'];
+ yield ['/f/news/hot.atom', '/f/news/hot/1.atom'];
+ yield ['/f/news/new.atom', '/f/news/new/1.atom'];
}
/**
@@ -130,12 +139,14 @@ public function redirectUrlProvider() {
public function authUrlProvider() {
yield ['/subscribed/hot'];
yield ['/subscribed/new'];
- yield ['/subscribed/hot/1'];
- yield ['/subscribed/new/1'];
+ yield ['/subscribed/top'];
+ yield ['/subscribed/controversial'];
+ yield ['/subscribed/most_commented'];
yield ['/moderated/hot'];
yield ['/moderated/new'];
- yield ['/moderated/hot/1'];
- yield ['/moderated/new/1'];
+ yield ['/moderated/top'];
+ yield ['/moderated/controversial'];
+ yield ['/moderated/most_commented'];
yield ['/create_forum'];
yield ['/f/news/edit'];
yield ['/f/news/appearance'];
diff --git a/translations/messages.en.yml b/translations/messages.en.yml
index 552675e..0d39e93 100644
--- a/translations/messages.en.yml
+++ b/translations/messages.en.yml
@@ -418,6 +418,7 @@ submissions:
sort_by_new: New
sort_by_top: Top
sort_by_controversial: Controversial
+ sort_by_most_commented: Most commented
total_votes: '{1} %count% point|[0,Inf[ %count% points'
vote_stats: (+%up%, −%down%)
edit_info: (edited %edited_at%)
From 8568c41a7eaa20147c8209b72d2bd4a0c76141a7 Mon Sep 17 00:00:00 2001
From: Emma
Date: Wed, 11 Apr 2018 01:35:43 +0200
Subject: [PATCH 18/24] fix typo that broke 'controversial'
---
src/Repository/SubmissionRepository.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Repository/SubmissionRepository.php b/src/Repository/SubmissionRepository.php
index e52ded7..91a8591 100644
--- a/src/Repository/SubmissionRepository.php
+++ b/src/Repository/SubmissionRepository.php
@@ -51,7 +51,7 @@ class SubmissionRepository extends ServiceEntityRepository {
private const CONTROVERSIAL_JOIN = '('.
'SELECT submission_id, COUNT(*) AS downvotes '.
'FROM submission_votes '.
- 'WHERE NOT upvote = FALSE '.
+ 'WHERE upvote = FALSE '.
'GROUP BY submission_id'.
')';
From 0a1e653813112392f75298b66edcb1a41e003866 Mon Sep 17 00:00:00 2001
From: Emma
Date: Wed, 11 Apr 2018 12:32:17 +0200
Subject: [PATCH 19/24] rearrange some classes, remove dead code
---
config/services.yaml | 18 +++----
.../CachedMarkdownConverter.php | 2 +-
src/{Utils => CommonMark}/MarkdownContext.php | 2 +-
.../MarkdownConverter.php | 3 +-
src/Controller/AjaxController.php | 4 +-
src/Controller/UserController.php | 2 +-
.../AuthenticationHelper.php | 2 +-
src/Twig/FormattingExtension.php | 6 +--
src/Utils/PrependOrderBy.php | 51 -------------------
tests/Utils/CachedMarkdownConverterTest.php | 8 +--
tests/Utils/MarkdownContextTest.php | 2 +-
tests/Utils/MarkdownConverterTest.php | 2 +-
12 files changed, 25 insertions(+), 77 deletions(-)
rename src/{Utils => CommonMark}/CachedMarkdownConverter.php (97%)
rename src/{Utils => CommonMark}/MarkdownContext.php (98%)
rename src/{Utils => CommonMark}/MarkdownConverter.php (97%)
rename src/{Utils => Security}/AuthenticationHelper.php (98%)
delete mode 100644 src/Utils/PrependOrderBy.php
diff --git a/config/services.yaml b/config/services.yaml
index 96601f0..642d318 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -38,6 +38,10 @@ services:
arguments:
$defaultLocale: "%env(APP_LOCALE)%"
+ App\CommonMark\CachedMarkdownConverter:
+ arguments:
+ $cacheItemPool: "@cache.markdown"
+
App\Controller\ForumController:
arguments:
$enableWebhooks: "%env(bool:APP_ENABLE_WEBHOOKS)%"
@@ -77,6 +81,11 @@ services:
$noReplyAddress: "%env(NO_REPLY_ADDRESS)%"
$salt: "%env(APP_SECRET)%"
+ App\Security\AuthenticationHelper:
+ arguments:
+ $rememberMeServices: "@security.authentication.rememberme.services.simplehash.main"
+ $secret: "%env(APP_SECRET)%"
+
App\Security\Voter\TokenVoter:
arguments:
$forumCreationInterval: "%user_forum_creation_interval%"
@@ -86,15 +95,6 @@ services:
$siteName: "%env(SITE_NAME)%"
$enableWebhooks: "%env(bool:APP_ENABLE_WEBHOOKS)%"
- App\Utils\CachedMarkdownConverter:
- arguments:
- $cacheItemPool: "@cache.markdown"
-
- App\Utils\AuthenticationHelper:
- arguments:
- $rememberMeServices: "@security.authentication.rememberme.services.simplehash.main"
- $secret: "%env(APP_SECRET)%"
-
## Misc
diff --git a/src/Utils/CachedMarkdownConverter.php b/src/CommonMark/CachedMarkdownConverter.php
similarity index 97%
rename from src/Utils/CachedMarkdownConverter.php
rename to src/CommonMark/CachedMarkdownConverter.php
index 0b48a2d..f67cea7 100644
--- a/src/Utils/CachedMarkdownConverter.php
+++ b/src/CommonMark/CachedMarkdownConverter.php
@@ -1,6 +1,6 @@
getQueryPart('orderBy');
-
- $qb->orderBy($clause, $order);
-
- foreach ($orderBy as $clause) {
- preg_match('/^(.*) (ASC|DESC)$/', $clause, $matches);
- $qb->addOrderBy($matches[1], $matches[2]);
- }
- }
-
- private static function prependWithDqlBuilder(DQLQueryBuilder $qb, string $clause, string $order) {
- $orderBy = $qb->getDQLPart('orderBy');
- $qb->orderBy($clause, $order);
-
- foreach ($orderBy as $clause) {
- $qb->addOrderBy($clause);
- }
- }
-}
diff --git a/tests/Utils/CachedMarkdownConverterTest.php b/tests/Utils/CachedMarkdownConverterTest.php
index 0587fed..d5ae841 100644
--- a/tests/Utils/CachedMarkdownConverterTest.php
+++ b/tests/Utils/CachedMarkdownConverterTest.php
@@ -2,8 +2,8 @@
namespace App\Tests\Utils;
-use App\Utils\CachedMarkdownConverter;
-use App\Utils\MarkdownConverter;
+use App\CommonMark\CachedMarkdownConverter;
+use App\CommonMark\MarkdownConverter;
use PHPUnit\Framework\TestCase;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
@@ -42,7 +42,7 @@ public function testLoadFromCache() {
->expects($this->never())
->method('convertToHtml');
- /* @var \App\Utils\CachedMarkdownConverter $cachedConverter */
+ /* @var \App\CommonMark\CachedMarkdownConverter $cachedConverter */
$cachedConverter = $this->getMockBuilder(CachedMarkdownConverter::class)
->setConstructorArgs([$cacheItemPool, $converter])
->enableProxyingToOriginalMethods()
@@ -96,7 +96,7 @@ public function testSaveToCache() {
)
->willReturn('html output');
- /* @var \App\Utils\CachedMarkdownConverter $cachedConverter */
+ /* @var \App\CommonMark\CachedMarkdownConverter $cachedConverter */
$cachedConverter = $this->getMockBuilder(CachedMarkdownConverter::class)
->setConstructorArgs([$cacheItemPool, $converter])
->enableProxyingToOriginalMethods()
diff --git a/tests/Utils/MarkdownContextTest.php b/tests/Utils/MarkdownContextTest.php
index 9bf63f6..ee388a2 100644
--- a/tests/Utils/MarkdownContextTest.php
+++ b/tests/Utils/MarkdownContextTest.php
@@ -2,8 +2,8 @@
namespace App\Tests\Utils;
+use App\CommonMark\MarkdownContext;
use App\Entity\User;
-use App\Utils\MarkdownContext;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
diff --git a/tests/Utils/MarkdownConverterTest.php b/tests/Utils/MarkdownConverterTest.php
index aefb3b8..be0d07c 100644
--- a/tests/Utils/MarkdownConverterTest.php
+++ b/tests/Utils/MarkdownConverterTest.php
@@ -2,7 +2,7 @@
namespace App\Tests\Utils;
-use App\Utils\MarkdownConverter;
+use App\CommonMark\MarkdownConverter;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
From 64caf4761537a05e419b8fff28b36defc006c140 Mon Sep 17 00:00:00 2001
From: Emma
Date: Wed, 11 Apr 2018 16:07:02 +0200
Subject: [PATCH 20/24] fix validation in SubmissionPager
---
src/Repository/Submission/SubmissionPager.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/Repository/Submission/SubmissionPager.php b/src/Repository/Submission/SubmissionPager.php
index 5071a78..529e4ee 100644
--- a/src/Repository/Submission/SubmissionPager.php
+++ b/src/Repository/Submission/SubmissionPager.php
@@ -28,7 +28,7 @@ public static function getParamsFromRequest(string $sortBy, Request $request): a
$value = $request->query->get('next_'.$column);
$type = SubmissionRepository::SORT_COLUMN_TYPES[$column];
- if ($value === null || !self::valueIsOfType($type, $value)) {
+ if (!\is_string($value) || !self::valueIsOfType($type, $value)) {
// missing columns - no pagination
return [];
}
@@ -93,11 +93,11 @@ private function columnNameToAccessor(string $columnName): string {
return 'get'.str_replace('_', '', ucwords($columnName, '_'));
}
- private static function valueIsOfType(string $type, $value): bool {
+ private static function valueIsOfType(string $type, string $value): bool {
switch ($type) {
case 'integer':
return ctype_digit($value) && \is_int(+$value) &&
- $value >= 0x80000000 && $value <= 0x7fffffff;
+ $value >= -0x80000000 && $value <= 0x7fffffff;
case 'bigint':
// if this causes problems on 32-bit systems, the site operators
// deserved it.
From 8a7f61ab1dc83559fff5e06cb7584d03472a1f5f Mon Sep 17 00:00:00 2001
From: Emma
Date: Thu, 12 Apr 2018 00:56:16 +0200
Subject: [PATCH 21/24] remove deprecated css classes
---
templates/submission/base.html.twig | 19 +++++++------------
templates/submission/submission.html.twig | 4 +---
2 files changed, 8 insertions(+), 15 deletions(-)
diff --git a/templates/submission/base.html.twig b/templates/submission/base.html.twig
index 7ce799f..8066b9e 100644
--- a/templates/submission/base.html.twig
+++ b/templates/submission/base.html.twig
@@ -4,24 +4,19 @@
{{ submission.title }}
{% endblock %}
-{# deprecated classes:
- - sidebar__section--submission-meta
- - sidebar-submission-meta
- - sidebar-submission-meta-score
- - vote-total
- - vote-stats
-#}
{% block sidebar %}
-