浏览代码

event system and plugins

Sebastian 7 年之前
父节点
当前提交
f3f2c0cb07
共有 45 个文件被更改,包括 3654 次插入130 次删除
  1. 1 4
      .gitignore
  2. 3 1
      composer.json
  3. 72 6
      composer.lock
  4. 11 5
      system/Controllers/Controller.php
  5. 64 26
      system/Controllers/PageController.php
  6. 29 0
      system/Events/LoadMarkdownEvent.php
  7. 29 0
      system/Events/LoadStructureEvent.php
  8. 29 0
      system/Events/ParseHtmlEvent.php
  9. 29 0
      system/Events/RenderSiteEvent.php
  10. 1 1
      system/Models/VersionCheck.php
  11. 2 2
      system/Models/WriteCache.php
  12. 43 0
      system/Plugin.php
  13. 93 0
      system/Plugins.php
  14. 9 0
      system/Routes/web.php
  15. 54 18
      system/settings.php
  16. 43 22
      system/system.php
  17. 2 0
      system/vendor/composer/autoload_psr4.php
  18. 10 0
      system/vendor/composer/autoload_static.php
  19. 112 44
      system/vendor/composer/installed.json
  20. 1 1
      system/vendor/erusev/parsedown
  21. 3 0
      system/vendor/symfony/event-dispatcher/.gitignore
  22. 37 0
      system/vendor/symfony/event-dispatcher/CHANGELOG.md
  23. 197 0
      system/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php
  24. 317 0
      system/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
  25. 34 0
      system/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php
  26. 114 0
      system/vendor/symfony/event-dispatcher/Debug/WrappedListener.php
  27. 120 0
      system/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php
  28. 58 0
      system/vendor/symfony/event-dispatcher/Event.php
  29. 236 0
      system/vendor/symfony/event-dispatcher/EventDispatcher.php
  30. 93 0
      system/vendor/symfony/event-dispatcher/EventDispatcherInterface.php
  31. 46 0
      system/vendor/symfony/event-dispatcher/EventSubscriberInterface.php
  32. 175 0
      system/vendor/symfony/event-dispatcher/GenericEvent.php
  33. 91 0
      system/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php
  34. 19 0
      system/vendor/symfony/event-dispatcher/LICENSE
  35. 15 0
      system/vendor/symfony/event-dispatcher/README.md
  36. 442 0
      system/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php
  37. 210 0
      system/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php
  38. 242 0
      system/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php
  39. 167 0
      system/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php
  40. 22 0
      system/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php
  41. 55 0
      system/vendor/symfony/event-dispatcher/Tests/EventTest.php
  42. 140 0
      system/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php
  43. 106 0
      system/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php
  44. 47 0
      system/vendor/symfony/event-dispatcher/composer.json
  45. 31 0
      system/vendor/symfony/event-dispatcher/phpunit.xml.dist

+ 1 - 4
.gitignore

@@ -4,8 +4,5 @@ cache/lastSitemap.txt
 cache/lastCache.txt
 settings/settings.yaml
 plugins
-typemill.zip
-typemill-1.0.1.zip
-typemill-1.0.2.zip
-typemill-1.0.3.zip
+zips
 zip.php

+ 3 - 1
composer.json

@@ -14,13 +14,15 @@
 		"slim/twig-view": "~2.3",
 		"slim/flash": "~0.4",
         "symfony/yaml": "~2.8",
+		"symfony/event-dispatcher": "~3.3",		
 		"erusev/parsedown": "~1.4",
 		"erusev/parsedown-extra": "dev-master",
 		"jbroadway/urlify": "~1.1"
     },
 	"autoload": {
 		"psr-4": {
-			"Typemill\\": "system"
+			"Typemill\\": "system",
+			"Plugins\\": "plugins"
 		}
 	}
 }

+ 72 - 6
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": "d5756d6121a1cf74d8c17d9ba55ae0d5",
+    "content-hash": "f4e4110212e9000bba2745d405bae5b2",
     "packages": [
         {
             "name": "container-interop/container-interop",
@@ -39,21 +39,24 @@
         },
         {
             "name": "erusev/parsedown",
-            "version": "1.6.3",
+            "version": "1.6.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/erusev/parsedown.git",
-                "reference": "728952b90a333b5c6f77f06ea9422b94b585878d"
+                "reference": "fbe3fe878f4fe69048bb8a52783a09802004f548"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/erusev/parsedown/zipball/728952b90a333b5c6f77f06ea9422b94b585878d",
-                "reference": "728952b90a333b5c6f77f06ea9422b94b585878d",
+                "url": "https://api.github.com/repos/erusev/parsedown/zipball/fbe3fe878f4fe69048bb8a52783a09802004f548",
+                "reference": "fbe3fe878f4fe69048bb8a52783a09802004f548",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.0"
             },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35"
+            },
             "type": "library",
             "autoload": {
                 "psr-0": {
@@ -77,7 +80,7 @@
                 "markdown",
                 "parser"
             ],
-            "time": "2017-05-14T14:47:48+00:00"
+            "time": "2017-11-14T20:44:03+00:00"
         },
         {
             "name": "erusev/parsedown-extra",
@@ -538,6 +541,69 @@
             ],
             "time": "2017-09-20T19:47:37+00:00"
         },
+        {
+            "name": "symfony/event-dispatcher",
+            "version": "v3.3.12",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/event-dispatcher.git",
+                "reference": "271d8c27c3ec5ecee6e2ac06016232e249d638d9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/271d8c27c3ec5ecee6e2ac06016232e249d638d9",
+                "reference": "271d8c27c3ec5ecee6e2ac06016232e249d638d9",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.5.9|>=7.0.8"
+            },
+            "conflict": {
+                "symfony/dependency-injection": "<3.3"
+            },
+            "require-dev": {
+                "psr/log": "~1.0",
+                "symfony/config": "~2.8|~3.0",
+                "symfony/dependency-injection": "~3.3",
+                "symfony/expression-language": "~2.8|~3.0",
+                "symfony/stopwatch": "~2.8|~3.0"
+            },
+            "suggest": {
+                "symfony/dependency-injection": "",
+                "symfony/http-kernel": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\EventDispatcher\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony EventDispatcher Component",
+            "homepage": "https://symfony.com",
+            "time": "2017-11-05T15:47:03+00:00"
+        },
         {
             "name": "symfony/yaml",
             "version": "v2.8.30",

+ 11 - 5
system/Controllers/Controller.php

@@ -4,6 +4,7 @@ namespace Typemill\Controllers;
 
 /* Use the slim-container */
 use Interop\Container\ContainerInterface;
+use Typemill\Events\RenderSiteEvent;
 
 abstract class Controller
 {
@@ -14,10 +15,15 @@ abstract class Controller
 		$this->c = $c;
 	}
 	
-	protected function render404($response, $content = NULL)
+	protected function render($response, $route, $data)
 	{
-		return $this->c->view->render($response->withStatus(404), '/404.twig', $content);
+		$data = $this->c->dispatcher->dispatch('beforeRenderSite', new RenderSiteEvent($data))->getData();
+		
+		return $this->c->view->render($response, $route, $data);
 	}
-}
-
-?>
+	
+	protected function render404($response, $data = NULL)
+	{
+		return $this->c->view->render($response->withStatus(404), '/404.twig', $data);
+	}
+}

+ 64 - 26
system/Controllers/PageController.php

@@ -9,12 +9,14 @@ use Typemill\Models\WriteYaml;
 use \Symfony\Component\Yaml\Yaml;
 use Typemill\Models\VersionCheck;
 use Typemill\Models\Helpers;
+use Typemill\Events\LoadStructureEvent;
+use Typemill\Events\LoadMarkdownEvent;
+use Typemill\Events\ParseHtmlEvent;
 
 class PageController extends Controller
 {
 	public function index($request, $response, $args)
-	{
-		
+	{		
 		/* Initiate Variables */
 		$structure		= false;
 		$contentHTML	= false;
@@ -29,38 +31,39 @@ class PageController extends Controller
 
 		try
 		{
+			/* if the cached structure is still valid, use it */
 			if($cache->validate('cache', 'lastCache.txt',600))
-			{				
+			{
 				$structure	= $this->getCachedStructure($cache);
 				$cached 	= true;
 			}
 			else
 			{
+				/* if not, get a fresh structure of the content folder */
 				$structure 	= $this->getFreshStructure($pathToContent, $cache, $uri);
-				$cached		= false;
 				
+				$cached		= false;
+
+				/* if there is no structure at all, the content folder is probably empty */
 				if(!$structure)
-				{ 
+				{
 					$content = '<h1>No Content</h1><p>Your content folder is empty.</p>'; 
-					$this->c->view->render($response, '/index.twig', [ 'content' => $content ]);
+
+					$this->render($response, '/index.twig', [ 'content' => $content ]);
 				}
 				elseif(!$cache->validate('cache', 'lastSitemap.txt', 86400))
-				{
+				{					
 					/* update sitemap */
 					$sitemap = new WriteSitemap();
 					$sitemap->updateSitemap('cache', 'sitemap.xml', 'lastSitemap.txt', $structure, $uri->getBaseUrl());
 					
-					$version = new VersionCheck();
-					$latestVersion = $version->checkVersion($uri->getBaseUrl());
-					if($latestVersion)
-					{
-						$yaml = new WriteYaml();
-						$yamlContent = $yaml->getYaml('settings', 'settings.yaml');
-						$yamlContent['latestVersion'] = $latestVersion;
-						$yaml->updateYaml('settings', 'settings.yaml', $yamlContent);
-					}
+					/* check and update the typemill-version in the user settings */
+					$this->updateVersion($uri->getBaseUrl());					
 				}
 			}
+			
+			/* dispatch event and let others manipulate the structure */
+			$structure = $this->c->dispatcher->dispatch('onStructureLoaded', new LoadStructureEvent($structure))->getData();
 		}
 		catch (Exception $e)
 		{
@@ -81,21 +84,28 @@ class PageController extends Controller
 			
 			/* find the url in the content-item-tree and return the item-object for the file */
 			$item = Folder::getItemForUrl($structure, $urlRel);
-
-			if(!$item && $cached)
+			
+			/* if structure is cached and there is no item 
+			if($cached && !$item)
 			{
+				/* get a fresh structure and search for the item again 
 				$structure = $this->getFreshStructure($pathToContent, $cache, $uri); 
 				$item = Folder::getItemForUrl($structure, $urlRel);
 			}
-			if(!$item){	return $this->render404($response, array( 'navigation' => $structure, 'settings' => $settings,  'base_url' => $base_url )); }
+			
+			/* if there is still no item, return a 404-page */
+			if(!$item)
+			{
+				return $this->render404($response, array( 'navigation' => $structure, 'settings' => $settings,  'base_url' => $base_url )); 
+			}
 			
 			/* get breadcrumb for page */
 			$breadcrumb = Folder::getBreadcrumb($structure, $item->keyPathArray);
-
+			
 			/* add the paging to the item */
 			$item = Folder::getPagingForItem($structure, $item);
-
-			/* check if url is a folder. If so, check if there is an index-file for the folder */
+			
+			/* check if url is a folder. If so, check if there is an index-file in that folder */
 			if($item->elementType == 'folder' && $item->index)
 			{
 				$filePath = $pathToContent . $item->path . DIRECTORY_SEPARATOR . 'index.md';
@@ -104,16 +114,20 @@ class PageController extends Controller
 			{
 				$filePath = $pathToContent . $item->path;
 			}
-
+			
 			/* read the content of the file */
 			$contentMD = isset($filePath) ? file_get_contents($filePath) : false;
 		}
 		
+		$contentMD = $this->c->dispatcher->dispatch('onMarkdownLoaded', new LoadMarkdownEvent($contentMD))->getData();
+		
 		/* initialize parsedown */
 		$Parsedown = new \ParsedownExtra();
-
+		
 		/* parse markdown-file to html-string */
 		$contentHTML 	= $Parsedown->text($contentMD);
+		$contentHTML 	= $this->c->dispatcher->dispatch('onHtmlParsed', new ParseHtmlEvent($contentHTML))->getData();
+
 		$excerpt		= substr($contentHTML,0,200);
 		$excerpt		= explode("</h1>", $excerpt);
 		$title			= isset($excerpt[0]) ? strip_tags($excerpt[0]) : $settings['title'];
@@ -127,8 +141,8 @@ class PageController extends Controller
 		*/
 		
 		$route = empty($args) && $settings['startpage'] ? '/cover.twig' : '/index.twig';
-
-		$this->c->view->render($response, $route, array('navigation' => $structure, 'content' => $contentHTML, 'item' => $item, 'breadcrumb' => $breadcrumb, 'settings' => $settings, 'title' => $title, 'description' => $description, 'base_url' => $base_url ));
+				
+		$this->render($response, $route, array('navigation' => $structure, 'content' => $contentHTML, 'item' => $item, 'breadcrumb' => $breadcrumb, 'settings' => $settings, 'title' => $title, 'description' => $description, 'base_url' => $base_url ));
 	}
 	
 	protected function getCachedStructure($cache)
@@ -155,4 +169,28 @@ class PageController extends Controller
 		
 		return $structure;
 	}
+	
+	protected function updateVersion($baseUrl)
+	{
+		/* check the latest public typemill version */
+		$version 		= new VersionCheck();
+		$latestVersion 	= $version->checkVersion($baseUrl);
+
+		if($latestVersion)
+		{
+			/* check, if user-settings exist */
+			$yaml 			= new WriteYaml();
+			$userSettings 	= $yaml->getYaml('settings', 'settings.yaml');
+			if($userSettings)
+			{
+				/* if there is no version info in the settings or if the version info is outdated */
+				if(!isset($userSettings['latestVersion']) || $userSettings['latestVersion'] != $latestVersion)
+				{
+					/* write the latest version into the user-settings */
+					$userSettings['latestVersion'] = $latestVersion;
+					$yaml->updateYaml('settings', 'settings.yaml', $userSettings);									
+				}
+			}
+		}	
+	}
 }

+ 29 - 0
system/Events/LoadMarkdownEvent.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace Typemill\Events;
+
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Event for the pure content.
+ */
+
+class LoadMarkdownEvent extends Event
+{
+    protected $data;
+
+    public function __construct($data)
+    {
+        $this->data = $data;
+    }
+
+    public function getData()
+    {
+        return $this->data;		
+    }
+	
+	public function setData($data)
+	{
+		$this->data = $data;
+	}
+}

+ 29 - 0
system/Events/LoadStructureEvent.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace Typemill\Events;
+
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Event for the folder structure.
+ */
+ 
+class LoadStructureEvent extends Event
+{
+    protected $data;
+
+    public function __construct($data)
+    {
+        $this->data = $data;
+    }
+
+    public function getData()
+    {
+        return $this->data;		
+    }
+	
+	public function setData($data)
+	{
+		$this->data = $data;
+	}
+}

+ 29 - 0
system/Events/ParseHtmlEvent.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace Typemill\Events;
+
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Event for the pure content.
+ */
+
+class ParseHtmlEvent extends Event
+{
+    protected $data;
+
+    public function __construct($data)
+    {
+        $this->data = $data;
+    }
+
+    public function getData()
+    {
+        return $this->data;		
+    }
+	
+	public function setData($data)
+	{
+		$this->data = $data;
+	}
+}

+ 29 - 0
system/Events/RenderSiteEvent.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace Typemill\Events;
+
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Event for the pure content.
+ */
+
+class RenderSiteEvent extends Event
+{
+    protected $data;
+
+    public function __construct($data)
+    {
+        $this->data = $data;
+    }
+
+    public function getData()
+    {
+        return $this->data;		
+    }
+	
+	public function setData($data)
+	{
+		$this->data = $data;
+	}
+}

+ 1 - 1
system/Models/VersionCheck.php

@@ -18,7 +18,7 @@ class VersionCheck
 		try {
 			$version = file_get_contents('http://typemill.net/tma1/checkversion', false, $context);
 
-			if ($version) 
+			if ($version)
 			{
 				$version = json_decode($version);			
 				return $version->version;

+ 2 - 2
system/Models/WriteCache.php

@@ -10,7 +10,7 @@ class WriteCache extends Write
 	 * @return boolean for an invalid cache (false) and for a valid cache (true).
 	 */
 	public function validate($folderName, $fileName, $duration)
-	{
+	{		
 		if(isset($_SERVER['HTTP_CACHE_CONTROL']) && $_SERVER['HTTP_CACHE_CONTROL'] == 'max-age=0')
 		{
 			return false;
@@ -26,7 +26,7 @@ class WriteCache extends Write
 			$this->writeFile($folderName, $fileName, time());
 			return false;
 		}
-				
+		
 		$lastRefresh = file_get_contents($folderName . DIRECTORY_SEPARATOR . $fileName);
 		
 		if(time() - $lastRefresh > $duration)

+ 43 - 0
system/Plugin.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace Typemill;
+
+use \Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+abstract class Plugin implements EventSubscriberInterface
+{
+	
+	protected $app;
+	
+	protected $container;
+
+    /**
+     * Constructor.
+     *
+     * @param string $name
+     * @param Grav   $grav
+     * @param Config $config
+     */
+    public function __construct($container, $app)
+    {
+		$this->container 	= $container;
+		$this->app			= $app;
+    }
+
+	protected function getRoute()
+	{
+		return $this->container['request']->getUri();
+	}
+				
+	protected function getPath()
+	{
+		$route = $this->container['request']->getUri();
+		return $route->getPath();
+	}
+	
+	protected function getDispatcher($dispatcher)
+	{
+		return $dispatcher;
+	}
+	
+}

+ 93 - 0
system/Plugins.php

@@ -0,0 +1,93 @@
+<?php
+
+namespace Typemill;
+
+class Plugins
+{	
+	public function load()
+	{
+		$pluginFolder = $this->scanPluginFolder();
+		$classNames = array();
+
+		/* iterate over plugin folders */
+		foreach($pluginFolder as $plugin)
+		{
+
+			$className = DIRECTORY_SEPARATOR . 'Plugins' . DIRECTORY_SEPARATOR . $plugin . DIRECTORY_SEPARATOR . $plugin;
+						
+			/* if plugin-class and subscribe-method exists, add classname to array */			
+			if(class_exists($className) /* && method_exists($className, 'getSubscribedEvents') */)
+			{
+				$classNames[] = $className;				
+			}
+		}
+		return $classNames;
+	}
+	
+	public function getNewRoutes($className, $routes)
+	{
+		
+		/* if route-method exists in plugin-class */
+		if(method_exists($className, 'addNewRoutes'))
+		{
+			/* add the routes */
+			$pluginRoutes = $className::addNewRoutes();
+			
+			/* multi-dimensional or simple array of routes */
+			if(isset($pluginRoutes[0]))
+			{
+				/* if they are properly formatted, add them to routes array */
+				foreach($pluginRoutes as $pluginRoute)
+				{
+					if($this->checkRouteArray($pluginRoute))
+					{
+						$routes[] = $pluginRoute;
+					}
+				}
+			}
+			elseif(is_array($routes))
+			{
+				if($this->checkRouteArray($pluginRoutes))
+				{
+					$routes[] = $pluginRoutes;
+				}
+			}
+		}
+		
+		return $routes;
+	}
+	
+	public function getNewMiddleware($className)
+	{
+		if(method_exists($className, 'addNewMiddleware'))
+		{
+			/* check array */
+			return $className::addNewMiddleware();
+		}
+	}
+	
+	private function checkRouteArray($route)
+	{
+		if( 
+			isset($route['httpMethod']) AND in_array($route['httpMethod'], array('get','post','put','delete','head','patch','options'))
+			AND isset($route['route']) AND is_string($route['route'])
+			AND isset($route['class']) AND is_string($route['class']))
+		{
+			return true;
+		}
+		return false;
+	}
+	
+	private function scanPluginFolder()
+	{
+		$pluginsDir = __DIR__ . '/../plugins';
+		
+		/* check if plugins directory exists */
+		if(!is_dir($pluginsDir)){ return array(); }
+		
+		/* get all plugins folder */
+		$plugins = array_diff(scandir($pluginsDir), array('..', '.'));
+		
+		return $plugins;
+	}
+}

+ 9 - 0
system/Routes/web.php

@@ -9,4 +9,13 @@ if(!isset($userSettings))
 	$app->post('/setup', SetupController::class . ':save')->setName('save');
 }
 
+foreach($routes as $pluginRoute)
+{
+	$method = $pluginRoute['httpMethod'];
+	$route	= $pluginRoute['route'];
+	$class	= $pluginRoute['class'];
+
+	$app->{$method}($route, $class);		
+}
+
 $app->get('/[{params:.*}]', PageController::class . ':index');

+ 54 - 18
system/settings.php

@@ -1,22 +1,58 @@
 <?php 
 
-DEFINE('DS', DIRECTORY_SEPARATOR);
+namespace Typemill;
 
-return [
-	'title'					=> 'TYPEMILL',
-	'author'				=> 'Unknown',
-	'copyright'				=> 'Copyright',
-	'startpage'				=> true,
-	'rootPath'				=> __DIR__ . DS .  '..' . DS,
-	'theme'					=> ($theme = 'typemill'),
-	'themeFolder'			=> ($themeFolder = 'themes'),
-	'themeBasePath'			=> __DIR__ . DS . '..' . DS,
-	'themePath'				=> __DIR__ . DS . '..' . DS . $themeFolder . DS . $theme,
-	'settingsPath'			=> __DIR__ . DS . '..' . DS . 'settings',
-	'authorPath'			=> __DIR__ . DS . 'author' . DS,
-	'contentFolder'			=> 'content',
-	'displayErrorDetails' 	=> false,
-	'version'				=> '1.0.3'
-];
+class Settings
+{	
+	public static function loadSettings()
+	{
+		$settings 			= self::getDefaultSettings();
+		$userSettings 		= self::getUserSettings($settings['settingsPath']);
+		
+		if($userSettings)
+		{
+			$settings = array_merge($settings, $userSettings);
+		}
+		$settings['themePath'] = $settings['rootPath'] . $settings['themeFolder'] . DIRECTORY_SEPARATOR . $settings['theme'];
+		return array('settings' => $settings);
+	}
+	
+	private function getDefaultSettings()
+	{
+		$rootPath = __DIR__ . DIRECTORY_SEPARATOR .  '..' . DIRECTORY_SEPARATOR;
+		
+		return [
+			'determineRouteBeforeAppMiddleware' 	=> true,
+			'displayErrorDetails' 					=> true,
+			'title'									=> 'TYPEMILL',
+			'author'								=> 'Unknown',
+			'copyright'								=> 'Copyright',
+			'startpage'								=> true,
+			'rootPath'								=> $rootPath,
+			'theme'									=> ($theme = 'typemill'),
+			'themeFolder'							=> ($themeFolder = 'themes'),
+			'themeBasePath'							=> $rootPath,
+			'themePath'								=> $rootPath . $themeFolder . DIRECTORY_SEPARATOR . $theme,
+			'settingsPath'							=> $rootPath . 'settings',
+			'authorPath'							=> __DIR__ . DIRECTORY_SEPARATOR . 'author' . DIRECTORY_SEPARATOR,
+			'contentFolder'							=> 'content',
+			'version'								=> '1.0.3'
+		];
+	}
+	
+	private function getUserSettings($settingsPath)
+	{
+		if(file_exists($settingsPath . DIRECTORY_SEPARATOR . 'settings.yaml'))
+		{
+			$yaml = new \Symfony\Component\Yaml\Parser();
 
-?>
+			try {
+				$userSettings 	= $yaml->parse( file_get_contents($settingsPath . DIRECTORY_SEPARATOR . 'settings.yaml' ) );
+			} catch (ParseException $e) {
+				printf("Unable to parse the YAML string: %s", $e->getMessage());
+			}
+			return $userSettings;
+		}
+		return false;
+	}
+}

+ 43 - 22
system/system.php

@@ -1,5 +1,8 @@
 <?php
 
+use Typemill\Models\Helpers;
+$timer['before session start']=microtime(true);
+
 /************************
 * START SESSION			*
 ************************/
@@ -10,23 +13,7 @@ session_start();
 * LOAD SETTINGS			*
 ************************/
 
-$settings = require_once( __DIR__ . '/settings.php');
-
-if(file_exists($settings['settingsPath'] . DIRECTORY_SEPARATOR . 'settings.yaml'))
-{
-	$yaml = new \Symfony\Component\Yaml\Parser();
-
-	try {
-		$userSettings 	= $yaml->parse( file_get_contents($settings['settingsPath'] . DIRECTORY_SEPARATOR . 'settings.yaml' ) );
-	} catch (ParseException $e) {
-		printf("Unable to parse the YAML string: %s", $e->getMessage());
-	}
-	
-	$settings = array_merge($settings, $userSettings);
-	$settings['themePath'] = $settings['themeBasePath'] . $settings['themeFolder'] . DIRECTORY_SEPARATOR . $settings['theme'];
-}
-
-$settings['settings'] = $settings;
+$settings = Typemill\Settings::loadSettings();
 
 /************************
 * INITIATE SLIM 		*
@@ -35,14 +22,15 @@ $settings['settings'] = $settings;
 $app = new \Slim\App($settings);
 
 /************************
-* 	SLIM CONTAINER		*
+*  GET SLIM CONTAINER	*
 ************************/
 
 $container = $app->getContainer();
 
 /************************
-* 		LOAD TWIG		*
+* 	LOAD TWIG VIEW		*
 ************************/
+
 $container['view'] = function ($container) use ($settings){
 	$path = array($settings['settings']['themePath'], $settings['settings']['authorPath']);
 	
@@ -66,8 +54,37 @@ $container['flash'] = function () {
     return new \Slim\Flash\Messages();
 };
 
+/****************************
+* CREATE EVENT DISPATCHER	*
+****************************/
+
+$dispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher();
+
 /************************
-* 	NOT FOUND HANDLER	*
+* LOAD PLUGINS 			*
+************************/
+
+$plugins 				= new Typemill\Plugins();
+$pluginClassNames		= $plugins->load();
+$routes = $middleware	= array();
+
+foreach($pluginClassNames as $pluginClassName)
+{
+	$routes 			= $plugins->getNewRoutes($pluginClassName, $routes);
+	$middleware[]		= $plugins->getNewMiddleware($pluginClassName);
+	
+	$dispatcher->addSubscriber(new $pluginClassName($container, $app));	
+}
+
+$dispatcher->dispatch('onPluginsInitialized');	
+
+$container['dispatcher'] = function($container) use ($dispatcher)
+{
+	return $dispatcher;
+};
+
+/************************
+* 	ADD MIDDLEWARE		*
 ************************/
 
 $container['notFoundHandler'] = function($c)
@@ -75,7 +92,11 @@ $container['notFoundHandler'] = function($c)
 	return new \Typemill\Handlers\NotFoundHandler($c['view']);
 };
 
+/************************
+* 	ADD ROUTES			*
+************************/
+
+$timer['before router']=microtime(true);
+
 require __DIR__ . '/Routes/api.php';
 require __DIR__ . '/Routes/web.php';
-
-?>

+ 2 - 0
system/vendor/composer/autoload_psr4.php

@@ -9,11 +9,13 @@ return array(
     'Typemill\\' => array($baseDir . '/system'),
     'Twig\\' => array($vendorDir . '/twig/twig/src'),
     'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
+    'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
     'Slim\\Views\\' => array($vendorDir . '/slim/twig-view/src'),
     'Slim\\Flash\\' => array($vendorDir . '/slim/flash/src'),
     'Slim\\' => array($vendorDir . '/slim/slim/Slim'),
     'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
     'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
+    'Plugins\\' => array($baseDir . '/plugins'),
     'Interop\\Container\\' => array($vendorDir . '/container-interop/container-interop/src/Interop/Container'),
     'FastRoute\\' => array($vendorDir . '/nikic/fast-route/src'),
 );

+ 10 - 0
system/vendor/composer/autoload_static.php

@@ -19,6 +19,7 @@ class ComposerStaticInit836351be733ecbf4741aea17e1973480
         'S' => 
         array (
             'Symfony\\Component\\Yaml\\' => 23,
+            'Symfony\\Component\\EventDispatcher\\' => 34,
             'Slim\\Views\\' => 11,
             'Slim\\Flash\\' => 11,
             'Slim\\' => 5,
@@ -27,6 +28,7 @@ class ComposerStaticInit836351be733ecbf4741aea17e1973480
         array (
             'Psr\\Http\\Message\\' => 17,
             'Psr\\Container\\' => 14,
+            'Plugins\\' => 8,
         ),
         'I' => 
         array (
@@ -51,6 +53,10 @@ class ComposerStaticInit836351be733ecbf4741aea17e1973480
         array (
             0 => __DIR__ . '/..' . '/symfony/yaml',
         ),
+        'Symfony\\Component\\EventDispatcher\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/event-dispatcher',
+        ),
         'Slim\\Views\\' => 
         array (
             0 => __DIR__ . '/..' . '/slim/twig-view/src',
@@ -71,6 +77,10 @@ class ComposerStaticInit836351be733ecbf4741aea17e1973480
         array (
             0 => __DIR__ . '/..' . '/psr/container/src',
         ),
+        'Plugins\\' => 
+        array (
+            0 => __DIR__ . '/../../..' . '/plugins',
+        ),
         'Interop\\Container\\' => 
         array (
             0 => __DIR__ . '/..' . '/container-interop/container-interop/src/Interop/Container',

+ 112 - 44
system/vendor/composer/installed.json

@@ -423,50 +423,6 @@
         "description": "Symfony Yaml Component",
         "homepage": "https://symfony.com"
     },
-    {
-        "name": "erusev/parsedown",
-        "version": "1.6.3",
-        "version_normalized": "1.6.3.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/erusev/parsedown.git",
-            "reference": "728952b90a333b5c6f77f06ea9422b94b585878d"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/erusev/parsedown/zipball/728952b90a333b5c6f77f06ea9422b94b585878d",
-            "reference": "728952b90a333b5c6f77f06ea9422b94b585878d",
-            "shasum": ""
-        },
-        "require": {
-            "php": ">=5.3.0"
-        },
-        "time": "2017-05-14T14:47:48+00:00",
-        "type": "library",
-        "installation-source": "source",
-        "autoload": {
-            "psr-0": {
-                "Parsedown": ""
-            }
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "Emanuil Rusev",
-                "email": "hello@erusev.com",
-                "homepage": "http://erusev.com"
-            }
-        ],
-        "description": "Parser for Markdown.",
-        "homepage": "http://parsedown.org",
-        "keywords": [
-            "markdown",
-            "parser"
-        ]
-    },
     {
         "name": "slim/twig-view",
         "version": "2.3.0",
@@ -670,5 +626,117 @@
             "parsedown",
             "parser"
         ]
+    },
+    {
+        "name": "erusev/parsedown",
+        "version": "1.6.4",
+        "version_normalized": "1.6.4.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/erusev/parsedown.git",
+            "reference": "fbe3fe878f4fe69048bb8a52783a09802004f548"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/erusev/parsedown/zipball/fbe3fe878f4fe69048bb8a52783a09802004f548",
+            "reference": "fbe3fe878f4fe69048bb8a52783a09802004f548",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.0"
+        },
+        "require-dev": {
+            "phpunit/phpunit": "^4.8.35"
+        },
+        "time": "2017-11-14T20:44:03+00:00",
+        "type": "library",
+        "installation-source": "source",
+        "autoload": {
+            "psr-0": {
+                "Parsedown": ""
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Emanuil Rusev",
+                "email": "hello@erusev.com",
+                "homepage": "http://erusev.com"
+            }
+        ],
+        "description": "Parser for Markdown.",
+        "homepage": "http://parsedown.org",
+        "keywords": [
+            "markdown",
+            "parser"
+        ]
+    },
+    {
+        "name": "symfony/event-dispatcher",
+        "version": "v3.3.12",
+        "version_normalized": "3.3.12.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/symfony/event-dispatcher.git",
+            "reference": "271d8c27c3ec5ecee6e2ac06016232e249d638d9"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/271d8c27c3ec5ecee6e2ac06016232e249d638d9",
+            "reference": "271d8c27c3ec5ecee6e2ac06016232e249d638d9",
+            "shasum": ""
+        },
+        "require": {
+            "php": "^5.5.9|>=7.0.8"
+        },
+        "conflict": {
+            "symfony/dependency-injection": "<3.3"
+        },
+        "require-dev": {
+            "psr/log": "~1.0",
+            "symfony/config": "~2.8|~3.0",
+            "symfony/dependency-injection": "~3.3",
+            "symfony/expression-language": "~2.8|~3.0",
+            "symfony/stopwatch": "~2.8|~3.0"
+        },
+        "suggest": {
+            "symfony/dependency-injection": "",
+            "symfony/http-kernel": ""
+        },
+        "time": "2017-11-05T15:47:03+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "3.3-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Symfony\\Component\\EventDispatcher\\": ""
+            },
+            "exclude-from-classmap": [
+                "/Tests/"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Fabien Potencier",
+                "email": "fabien@symfony.com"
+            },
+            {
+                "name": "Symfony Community",
+                "homepage": "https://symfony.com/contributors"
+            }
+        ],
+        "description": "Symfony EventDispatcher Component",
+        "homepage": "https://symfony.com"
     }
 ]

+ 1 - 1
system/vendor/erusev/parsedown

@@ -1 +1 @@
-Subproject commit 728952b90a333b5c6f77f06ea9422b94b585878d
+Subproject commit fbe3fe878f4fe69048bb8a52783a09802004f548

+ 3 - 0
system/vendor/symfony/event-dispatcher/.gitignore

@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml

+ 37 - 0
system/vendor/symfony/event-dispatcher/CHANGELOG.md

@@ -0,0 +1,37 @@
+CHANGELOG
+=========
+
+3.3.0
+-----
+
+  * The ContainerAwareEventDispatcher class has been deprecated. Use EventDispatcher with closure factories instead.
+
+3.0.0
+-----
+
+  * The method `getListenerPriority($eventName, $listener)` has been added to the
+    `EventDispatcherInterface`.
+  * The methods `Event::setDispatcher()`, `Event::getDispatcher()`, `Event::setName()`
+    and `Event::getName()` have been removed.
+    The event dispatcher and the event name are passed to the listener call.
+
+2.5.0
+-----
+
+ * added Debug\TraceableEventDispatcher (originally in HttpKernel)
+ * changed Debug\TraceableEventDispatcherInterface to extend EventDispatcherInterface
+ * added RegisterListenersPass (originally in HttpKernel)
+
+2.1.0
+-----
+
+ * added TraceableEventDispatcherInterface
+ * added ContainerAwareEventDispatcher
+ * added a reference to the EventDispatcher on the Event
+ * added a reference to the Event name on the event
+ * added fluid interface to the dispatch() method which now returns the Event
+   object
+ * added GenericEvent event class
+ * added the possibility for subscribers to subscribe several times for the
+   same event
+ * added ImmutableEventDispatcher

+ 197 - 0
system/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php

@@ -0,0 +1,197 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Lazily loads listeners and subscribers from the dependency injection
+ * container.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ * @author Jordan Alliot <jordan.alliot@gmail.com>
+ *
+ * @deprecated since 3.3, to be removed in 4.0. Use EventDispatcher with closure factories instead.
+ */
+class ContainerAwareEventDispatcher extends EventDispatcher
+{
+    private $container;
+
+    /**
+     * The service IDs of the event listeners and subscribers.
+     */
+    private $listenerIds = array();
+
+    /**
+     * The services registered as listeners.
+     */
+    private $listeners = array();
+
+    public function __construct(ContainerInterface $container)
+    {
+        $this->container = $container;
+
+        $class = get_class($this);
+        if ($this instanceof \PHPUnit_Framework_MockObject_MockObject || $this instanceof \Prophecy\Doubler\DoubleInterface) {
+            $class = get_parent_class($class);
+        }
+        if (__CLASS__ !== $class) {
+            @trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED);
+        }
+    }
+
+    /**
+     * Adds a service as event listener.
+     *
+     * @param string $eventName Event for which the listener is added
+     * @param array  $callback  The service ID of the listener service & the method
+     *                          name that has to be called
+     * @param int    $priority  The higher this value, the earlier an event listener
+     *                          will be triggered in the chain.
+     *                          Defaults to 0.
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function addListenerService($eventName, $callback, $priority = 0)
+    {
+        @trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED);
+
+        if (!is_array($callback) || 2 !== count($callback)) {
+            throw new \InvalidArgumentException('Expected an array("service", "method") argument');
+        }
+
+        $this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority);
+    }
+
+    public function removeListener($eventName, $listener)
+    {
+        $this->lazyLoad($eventName);
+
+        if (isset($this->listenerIds[$eventName])) {
+            foreach ($this->listenerIds[$eventName] as $i => list($serviceId, $method, $priority)) {
+                $key = $serviceId.'.'.$method;
+                if (isset($this->listeners[$eventName][$key]) && $listener === array($this->listeners[$eventName][$key], $method)) {
+                    unset($this->listeners[$eventName][$key]);
+                    if (empty($this->listeners[$eventName])) {
+                        unset($this->listeners[$eventName]);
+                    }
+                    unset($this->listenerIds[$eventName][$i]);
+                    if (empty($this->listenerIds[$eventName])) {
+                        unset($this->listenerIds[$eventName]);
+                    }
+                }
+            }
+        }
+
+        parent::removeListener($eventName, $listener);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasListeners($eventName = null)
+    {
+        if (null === $eventName) {
+            return $this->listenerIds || $this->listeners || parent::hasListeners();
+        }
+
+        if (isset($this->listenerIds[$eventName])) {
+            return true;
+        }
+
+        return parent::hasListeners($eventName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getListeners($eventName = null)
+    {
+        if (null === $eventName) {
+            foreach ($this->listenerIds as $serviceEventName => $args) {
+                $this->lazyLoad($serviceEventName);
+            }
+        } else {
+            $this->lazyLoad($eventName);
+        }
+
+        return parent::getListeners($eventName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getListenerPriority($eventName, $listener)
+    {
+        $this->lazyLoad($eventName);
+
+        return parent::getListenerPriority($eventName, $listener);
+    }
+
+    /**
+     * Adds a service as event subscriber.
+     *
+     * @param string $serviceId The service ID of the subscriber service
+     * @param string $class     The service's class name (which must implement EventSubscriberInterface)
+     */
+    public function addSubscriberService($serviceId, $class)
+    {
+        @trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED);
+
+        foreach ($class::getSubscribedEvents() as $eventName => $params) {
+            if (is_string($params)) {
+                $this->listenerIds[$eventName][] = array($serviceId, $params, 0);
+            } elseif (is_string($params[0])) {
+                $this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0);
+            } else {
+                foreach ($params as $listener) {
+                    $this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0);
+                }
+            }
+        }
+    }
+
+    public function getContainer()
+    {
+        @trigger_error('The '.__METHOD__.'() method is deprecated since version 3.3 as its class will be removed in 4.0. Inject the container or the services you need in your listeners/subscribers instead.', E_USER_DEPRECATED);
+
+        return $this->container;
+    }
+
+    /**
+     * Lazily loads listeners for this event from the dependency injection
+     * container.
+     *
+     * @param string $eventName The name of the event to dispatch. The name of
+     *                          the event is the name of the method that is
+     *                          invoked on listeners.
+     */
+    protected function lazyLoad($eventName)
+    {
+        if (isset($this->listenerIds[$eventName])) {
+            foreach ($this->listenerIds[$eventName] as list($serviceId, $method, $priority)) {
+                $listener = $this->container->get($serviceId);
+
+                $key = $serviceId.'.'.$method;
+                if (!isset($this->listeners[$eventName][$key])) {
+                    $this->addListener($eventName, array($listener, $method), $priority);
+                } elseif ($this->listeners[$eventName][$key] !== $listener) {
+                    parent::removeListener($eventName, array($this->listeners[$eventName][$key], $method));
+                    $this->addListener($eventName, array($listener, $method), $priority);
+                }
+
+                $this->listeners[$eventName][$key] = $listener;
+            }
+        }
+    }
+}

+ 317 - 0
system/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php

@@ -0,0 +1,317 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Debug;
+
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\Stopwatch\Stopwatch;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Collects some data about event listeners.
+ *
+ * This event dispatcher delegates the dispatching to another one.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class TraceableEventDispatcher implements TraceableEventDispatcherInterface
+{
+    protected $logger;
+    protected $stopwatch;
+
+    private $called;
+    private $dispatcher;
+    private $wrappedListeners;
+
+    public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null)
+    {
+        $this->dispatcher = $dispatcher;
+        $this->stopwatch = $stopwatch;
+        $this->logger = $logger;
+        $this->called = array();
+        $this->wrappedListeners = array();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addListener($eventName, $listener, $priority = 0)
+    {
+        $this->dispatcher->addListener($eventName, $listener, $priority);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addSubscriber(EventSubscriberInterface $subscriber)
+    {
+        $this->dispatcher->addSubscriber($subscriber);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function removeListener($eventName, $listener)
+    {
+        if (isset($this->wrappedListeners[$eventName])) {
+            foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
+                if ($wrappedListener->getWrappedListener() === $listener) {
+                    $listener = $wrappedListener;
+                    unset($this->wrappedListeners[$eventName][$index]);
+                    break;
+                }
+            }
+        }
+
+        return $this->dispatcher->removeListener($eventName, $listener);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function removeSubscriber(EventSubscriberInterface $subscriber)
+    {
+        return $this->dispatcher->removeSubscriber($subscriber);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getListeners($eventName = null)
+    {
+        return $this->dispatcher->getListeners($eventName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getListenerPriority($eventName, $listener)
+    {
+        // we might have wrapped listeners for the event (if called while dispatching)
+        // in that case get the priority by wrapper
+        if (isset($this->wrappedListeners[$eventName])) {
+            foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
+                if ($wrappedListener->getWrappedListener() === $listener) {
+                    return $this->dispatcher->getListenerPriority($eventName, $wrappedListener);
+                }
+            }
+        }
+
+        return $this->dispatcher->getListenerPriority($eventName, $listener);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasListeners($eventName = null)
+    {
+        return $this->dispatcher->hasListeners($eventName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dispatch($eventName, Event $event = null)
+    {
+        if (null === $event) {
+            $event = new Event();
+        }
+
+        if (null !== $this->logger && $event->isPropagationStopped()) {
+            $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
+        }
+
+        $this->preProcess($eventName);
+        $this->preDispatch($eventName, $event);
+
+        $e = $this->stopwatch->start($eventName, 'section');
+
+        $this->dispatcher->dispatch($eventName, $event);
+
+        if ($e->isStarted()) {
+            $e->stop();
+        }
+
+        $this->postDispatch($eventName, $event);
+        $this->postProcess($eventName);
+
+        return $event;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getCalledListeners()
+    {
+        $called = array();
+        foreach ($this->called as $eventName => $listeners) {
+            foreach ($listeners as $listener) {
+                $called[$eventName.'.'.$listener->getPretty()] = $listener->getInfo($eventName);
+            }
+        }
+
+        return $called;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getNotCalledListeners()
+    {
+        try {
+            $allListeners = $this->getListeners();
+        } catch (\Exception $e) {
+            if (null !== $this->logger) {
+                $this->logger->info('An exception was thrown while getting the uncalled listeners.', array('exception' => $e));
+            }
+
+            // unable to retrieve the uncalled listeners
+            return array();
+        }
+
+        $notCalled = array();
+        foreach ($allListeners as $eventName => $listeners) {
+            foreach ($listeners as $listener) {
+                $called = false;
+                if (isset($this->called[$eventName])) {
+                    foreach ($this->called[$eventName] as $l) {
+                        if ($l->getWrappedListener() === $listener) {
+                            $called = true;
+
+                            break;
+                        }
+                    }
+                }
+
+                if (!$called) {
+                    if (!$listener instanceof WrappedListener) {
+                        $listener = new WrappedListener($listener, null, $this->stopwatch, $this);
+                    }
+                    $notCalled[$eventName.'.'.$listener->getPretty()] = $listener->getInfo($eventName);
+                }
+            }
+        }
+
+        uasort($notCalled, array($this, 'sortListenersByPriority'));
+
+        return $notCalled;
+    }
+
+    /**
+     * Proxies all method calls to the original event dispatcher.
+     *
+     * @param string $method    The method name
+     * @param array  $arguments The method arguments
+     *
+     * @return mixed
+     */
+    public function __call($method, $arguments)
+    {
+        return call_user_func_array(array($this->dispatcher, $method), $arguments);
+    }
+
+    /**
+     * Called before dispatching the event.
+     *
+     * @param string $eventName The event name
+     * @param Event  $event     The event
+     */
+    protected function preDispatch($eventName, Event $event)
+    {
+    }
+
+    /**
+     * Called after dispatching the event.
+     *
+     * @param string $eventName The event name
+     * @param Event  $event     The event
+     */
+    protected function postDispatch($eventName, Event $event)
+    {
+    }
+
+    private function preProcess($eventName)
+    {
+        foreach ($this->dispatcher->getListeners($eventName) as $listener) {
+            $priority = $this->getListenerPriority($eventName, $listener);
+            $wrappedListener = new WrappedListener($listener, null, $this->stopwatch, $this);
+            $this->wrappedListeners[$eventName][] = $wrappedListener;
+            $this->dispatcher->removeListener($eventName, $listener);
+            $this->dispatcher->addListener($eventName, $wrappedListener, $priority);
+        }
+    }
+
+    private function postProcess($eventName)
+    {
+        unset($this->wrappedListeners[$eventName]);
+        $skipped = false;
+        foreach ($this->dispatcher->getListeners($eventName) as $listener) {
+            if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
+                continue;
+            }
+            // Unwrap listener
+            $priority = $this->getListenerPriority($eventName, $listener);
+            $this->dispatcher->removeListener($eventName, $listener);
+            $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority);
+
+            if (null !== $this->logger) {
+                $context = array('event' => $eventName, 'listener' => $listener->getPretty());
+            }
+
+            if ($listener->wasCalled()) {
+                if (null !== $this->logger) {
+                    $this->logger->debug('Notified event "{event}" to listener "{listener}".', $context);
+                }
+
+                if (!isset($this->called[$eventName])) {
+                    $this->called[$eventName] = new \SplObjectStorage();
+                }
+
+                $this->called[$eventName]->attach($listener);
+            }
+
+            if (null !== $this->logger && $skipped) {
+                $this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context);
+            }
+
+            if ($listener->stoppedPropagation()) {
+                if (null !== $this->logger) {
+                    $this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context);
+                }
+
+                $skipped = true;
+            }
+        }
+    }
+
+    private function sortListenersByPriority($a, $b)
+    {
+        if (is_int($a['priority']) && !is_int($b['priority'])) {
+            return 1;
+        }
+
+        if (!is_int($a['priority']) && is_int($b['priority'])) {
+            return -1;
+        }
+
+        if ($a['priority'] === $b['priority']) {
+            return 0;
+        }
+
+        if ($a['priority'] > $b['priority']) {
+            return -1;
+        }
+
+        return 1;
+    }
+}

+ 34 - 0
system/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php

@@ -0,0 +1,34 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Debug;
+
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+interface TraceableEventDispatcherInterface extends EventDispatcherInterface
+{
+    /**
+     * Gets the called listeners.
+     *
+     * @return array An array of called listeners
+     */
+    public function getCalledListeners();
+
+    /**
+     * Gets the not called listeners.
+     *
+     * @return array An array of not called listeners
+     */
+    public function getNotCalledListeners();
+}

+ 114 - 0
system/vendor/symfony/event-dispatcher/Debug/WrappedListener.php

@@ -0,0 +1,114 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Debug;
+
+use Symfony\Component\Stopwatch\Stopwatch;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\VarDumper\Caster\ClassStub;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class WrappedListener
+{
+    private $listener;
+    private $name;
+    private $called;
+    private $stoppedPropagation;
+    private $stopwatch;
+    private $dispatcher;
+    private $pretty;
+    private $stub;
+    private static $hasClassStub;
+
+    public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null)
+    {
+        $this->listener = $listener;
+        $this->name = $name;
+        $this->stopwatch = $stopwatch;
+        $this->dispatcher = $dispatcher;
+        $this->called = false;
+        $this->stoppedPropagation = false;
+
+        if (is_array($listener)) {
+            $this->name = is_object($listener[0]) ? get_class($listener[0]) : $listener[0];
+            $this->pretty = $this->name.'::'.$listener[1];
+        } elseif ($listener instanceof \Closure) {
+            $this->pretty = $this->name = 'closure';
+        } elseif (is_string($listener)) {
+            $this->pretty = $this->name = $listener;
+        } else {
+            $this->name = get_class($listener);
+            $this->pretty = $this->name.'::__invoke';
+        }
+
+        if (null !== $name) {
+            $this->name = $name;
+        }
+
+        if (null === self::$hasClassStub) {
+            self::$hasClassStub = class_exists(ClassStub::class);
+        }
+    }
+
+    public function getWrappedListener()
+    {
+        return $this->listener;
+    }
+
+    public function wasCalled()
+    {
+        return $this->called;
+    }
+
+    public function stoppedPropagation()
+    {
+        return $this->stoppedPropagation;
+    }
+
+    public function getPretty()
+    {
+        return $this->pretty;
+    }
+
+    public function getInfo($eventName)
+    {
+        if (null === $this->stub) {
+            $this->stub = self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->listener) : $this->pretty.'()';
+        }
+
+        return array(
+            'event' => $eventName,
+            'priority' => null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null,
+            'pretty' => $this->pretty,
+            'stub' => $this->stub,
+        );
+    }
+
+    public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher)
+    {
+        $this->called = true;
+
+        $e = $this->stopwatch->start($this->name, 'event_listener');
+
+        call_user_func($this->listener, $event, $eventName, $this->dispatcher ?: $dispatcher);
+
+        if ($e->isStarted()) {
+            $e->stop();
+        }
+
+        if ($event->isPropagationStopped()) {
+            $this->stoppedPropagation = true;
+        }
+    }
+}

+ 120 - 0
system/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php

@@ -0,0 +1,120 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Compiler pass to register tagged services for an event dispatcher.
+ */
+class RegisterListenersPass implements CompilerPassInterface
+{
+    protected $dispatcherService;
+    protected $listenerTag;
+    protected $subscriberTag;
+
+    /**
+     * @param string $dispatcherService Service name of the event dispatcher in processed container
+     * @param string $listenerTag       Tag name used for listener
+     * @param string $subscriberTag     Tag name used for subscribers
+     */
+    public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'kernel.event_listener', $subscriberTag = 'kernel.event_subscriber')
+    {
+        $this->dispatcherService = $dispatcherService;
+        $this->listenerTag = $listenerTag;
+        $this->subscriberTag = $subscriberTag;
+    }
+
+    public function process(ContainerBuilder $container)
+    {
+        if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) {
+            return;
+        }
+
+        $definition = $container->findDefinition($this->dispatcherService);
+
+        foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) {
+            foreach ($events as $event) {
+                $priority = isset($event['priority']) ? $event['priority'] : 0;
+
+                if (!isset($event['event'])) {
+                    throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
+                }
+
+                if (!isset($event['method'])) {
+                    $event['method'] = 'on'.preg_replace_callback(array(
+                        '/(?<=\b)[a-z]/i',
+                        '/[^a-z0-9]/i',
+                    ), function ($matches) { return strtoupper($matches[0]); }, $event['event']);
+                    $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']);
+                }
+
+                $definition->addMethodCall('addListener', array($event['event'], array(new ServiceClosureArgument(new Reference($id)), $event['method']), $priority));
+            }
+        }
+
+        $extractingDispatcher = new ExtractingEventDispatcher();
+
+        foreach ($container->findTaggedServiceIds($this->subscriberTag, true) as $id => $attributes) {
+            $def = $container->getDefinition($id);
+
+            // We must assume that the class value has been correctly filled, even if the service is created by a factory
+            $class = $container->getParameterBag()->resolveValue($def->getClass());
+            $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface';
+
+            if (!is_subclass_of($class, $interface)) {
+                if (!class_exists($class, false)) {
+                    throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
+                }
+
+                throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
+            }
+            $container->addObjectResource($class);
+
+            ExtractingEventDispatcher::$subscriber = $class;
+            $extractingDispatcher->addSubscriber($extractingDispatcher);
+            foreach ($extractingDispatcher->listeners as $args) {
+                $args[1] = array(new ServiceClosureArgument(new Reference($id)), $args[1]);
+                $definition->addMethodCall('addListener', $args);
+            }
+            $extractingDispatcher->listeners = array();
+        }
+    }
+}
+
+/**
+ * @internal
+ */
+class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface
+{
+    public $listeners = array();
+
+    public static $subscriber;
+
+    public function addListener($eventName, $listener, $priority = 0)
+    {
+        $this->listeners[] = array($eventName, $listener[1], $priority);
+    }
+
+    public static function getSubscribedEvents()
+    {
+        $callback = array(self::$subscriber, 'getSubscribedEvents');
+
+        return $callback();
+    }
+}

+ 58 - 0
system/vendor/symfony/event-dispatcher/Event.php

@@ -0,0 +1,58 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * Event is the base class for classes containing event data.
+ *
+ * This class contains no event data. It is used by events that do not pass
+ * state information to an event handler when an event is raised.
+ *
+ * You can call the method stopPropagation() to abort the execution of
+ * further listeners in your event listener.
+ *
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class Event
+{
+    /**
+     * @var bool Whether no further event listeners should be triggered
+     */
+    private $propagationStopped = false;
+
+    /**
+     * Returns whether further event listeners should be triggered.
+     *
+     * @see Event::stopPropagation()
+     *
+     * @return bool Whether propagation was already stopped for this event
+     */
+    public function isPropagationStopped()
+    {
+        return $this->propagationStopped;
+    }
+
+    /**
+     * Stops the propagation of the event to further event listeners.
+     *
+     * If multiple event listeners are connected to the same event, no
+     * further event listener will be triggered once any trigger calls
+     * stopPropagation().
+     */
+    public function stopPropagation()
+    {
+        $this->propagationStopped = true;
+    }
+}

+ 236 - 0
system/vendor/symfony/event-dispatcher/EventDispatcher.php

@@ -0,0 +1,236 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * The EventDispatcherInterface is the central point of Symfony's event listener system.
+ *
+ * Listeners are registered on the manager and events are dispatched through the
+ * manager.
+ *
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @author Jordan Alliot <jordan.alliot@gmail.com>
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class EventDispatcher implements EventDispatcherInterface
+{
+    private $listeners = array();
+    private $sorted = array();
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dispatch($eventName, Event $event = null)
+    {
+        if (null === $event) {
+            $event = new Event();
+        }
+
+        if ($listeners = $this->getListeners($eventName)) {
+            $this->doDispatch($listeners, $eventName, $event);
+        }
+
+        return $event;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getListeners($eventName = null)
+    {
+        if (null !== $eventName) {
+            if (empty($this->listeners[$eventName])) {
+                return array();
+            }
+
+            if (!isset($this->sorted[$eventName])) {
+                $this->sortListeners($eventName);
+            }
+
+            return $this->sorted[$eventName];
+        }
+
+        foreach ($this->listeners as $eventName => $eventListeners) {
+            if (!isset($this->sorted[$eventName])) {
+                $this->sortListeners($eventName);
+            }
+        }
+
+        return array_filter($this->sorted);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getListenerPriority($eventName, $listener)
+    {
+        if (empty($this->listeners[$eventName])) {
+            return;
+        }
+
+        if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
+            $listener[0] = $listener[0]();
+        }
+
+        foreach ($this->listeners[$eventName] as $priority => $listeners) {
+            foreach ($listeners as $k => $v) {
+                if ($v !== $listener && is_array($v) && isset($v[0]) && $v[0] instanceof \Closure) {
+                    $v[0] = $v[0]();
+                    $this->listeners[$eventName][$priority][$k] = $v;
+                }
+                if ($v === $listener) {
+                    return $priority;
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasListeners($eventName = null)
+    {
+        if (null !== $eventName) {
+            return !empty($this->listeners[$eventName]);
+        }
+
+        foreach ($this->listeners as $eventListeners) {
+            if ($eventListeners) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addListener($eventName, $listener, $priority = 0)
+    {
+        $this->listeners[$eventName][$priority][] = $listener;
+        unset($this->sorted[$eventName]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function removeListener($eventName, $listener)
+    {
+        if (empty($this->listeners[$eventName])) {
+            return;
+        }
+
+        if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
+            $listener[0] = $listener[0]();
+        }
+
+        foreach ($this->listeners[$eventName] as $priority => $listeners) {
+            foreach ($listeners as $k => $v) {
+                if ($v !== $listener && is_array($v) && isset($v[0]) && $v[0] instanceof \Closure) {
+                    $v[0] = $v[0]();
+                }
+                if ($v === $listener) {
+                    unset($listeners[$k], $this->sorted[$eventName]);
+                } else {
+                    $listeners[$k] = $v;
+                }
+            }
+
+            if ($listeners) {
+                $this->listeners[$eventName][$priority] = $listeners;
+            } else {
+                unset($this->listeners[$eventName][$priority]);
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addSubscriber(EventSubscriberInterface $subscriber)
+    {
+        foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
+            if (is_string($params)) {
+                $this->addListener($eventName, array($subscriber, $params));
+            } elseif (is_string($params[0])) {
+                $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0);
+            } else {
+                foreach ($params as $listener) {
+                    $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0);
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function removeSubscriber(EventSubscriberInterface $subscriber)
+    {
+        foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
+            if (is_array($params) && is_array($params[0])) {
+                foreach ($params as $listener) {
+                    $this->removeListener($eventName, array($subscriber, $listener[0]));
+                }
+            } else {
+                $this->removeListener($eventName, array($subscriber, is_string($params) ? $params : $params[0]));
+            }
+        }
+    }
+
+    /**
+     * Triggers the listeners of an event.
+     *
+     * This method can be overridden to add functionality that is executed
+     * for each listener.
+     *
+     * @param callable[] $listeners The event listeners
+     * @param string     $eventName The name of the event to dispatch
+     * @param Event      $event     The event object to pass to the event handlers/listeners
+     */
+    protected function doDispatch($listeners, $eventName, Event $event)
+    {
+        foreach ($listeners as $listener) {
+            if ($event->isPropagationStopped()) {
+                break;
+            }
+            call_user_func($listener, $event, $eventName, $this);
+        }
+    }
+
+    /**
+     * Sorts the internal list of listeners for the given event by priority.
+     *
+     * @param string $eventName The name of the event
+     */
+    private function sortListeners($eventName)
+    {
+        krsort($this->listeners[$eventName]);
+        $this->sorted[$eventName] = array();
+
+        foreach ($this->listeners[$eventName] as $priority => $listeners) {
+            foreach ($listeners as $k => $listener) {
+                if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
+                    $listener[0] = $listener[0]();
+                    $this->listeners[$eventName][$priority][$k] = $listener;
+                }
+                $this->sorted[$eventName][] = $listener;
+            }
+        }
+    }
+}

+ 93 - 0
system/vendor/symfony/event-dispatcher/EventDispatcherInterface.php

@@ -0,0 +1,93 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * The EventDispatcherInterface is the central point of Symfony's event listener system.
+ * Listeners are registered on the manager and events are dispatched through the
+ * manager.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+interface EventDispatcherInterface
+{
+    /**
+     * Dispatches an event to all registered listeners.
+     *
+     * @param string $eventName The name of the event to dispatch. The name of
+     *                          the event is the name of the method that is
+     *                          invoked on listeners.
+     * @param Event  $event     The event to pass to the event handlers/listeners
+     *                          If not supplied, an empty Event instance is created
+     *
+     * @return Event
+     */
+    public function dispatch($eventName, Event $event = null);
+
+    /**
+     * Adds an event listener that listens on the specified events.
+     *
+     * @param string   $eventName The event to listen on
+     * @param callable $listener  The listener
+     * @param int      $priority  The higher this value, the earlier an event
+     *                            listener will be triggered in the chain (defaults to 0)
+     */
+    public function addListener($eventName, $listener, $priority = 0);
+
+    /**
+     * Adds an event subscriber.
+     *
+     * The subscriber is asked for all the events he is
+     * interested in and added as a listener for these events.
+     */
+    public function addSubscriber(EventSubscriberInterface $subscriber);
+
+    /**
+     * Removes an event listener from the specified events.
+     *
+     * @param string   $eventName The event to remove a listener from
+     * @param callable $listener  The listener to remove
+     */
+    public function removeListener($eventName, $listener);
+
+    public function removeSubscriber(EventSubscriberInterface $subscriber);
+
+    /**
+     * Gets the listeners of a specific event or all listeners sorted by descending priority.
+     *
+     * @param string $eventName The name of the event
+     *
+     * @return array The event listeners for the specified event, or all event listeners by event name
+     */
+    public function getListeners($eventName = null);
+
+    /**
+     * Gets the listener priority for a specific event.
+     *
+     * Returns null if the event or the listener does not exist.
+     *
+     * @param string   $eventName The name of the event
+     * @param callable $listener  The listener
+     *
+     * @return int|null The event listener priority
+     */
+    public function getListenerPriority($eventName, $listener);
+
+    /**
+     * Checks whether an event has any registered listeners.
+     *
+     * @param string $eventName The name of the event
+     *
+     * @return bool true if the specified event has any listeners, false otherwise
+     */
+    public function hasListeners($eventName = null);
+}

+ 46 - 0
system/vendor/symfony/event-dispatcher/EventSubscriberInterface.php

@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * An EventSubscriber knows himself what events he is interested in.
+ * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes
+ * {@link getSubscribedEvents} and registers the subscriber as a listener for all
+ * returned events.
+ *
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+interface EventSubscriberInterface
+{
+    /**
+     * Returns an array of event names this subscriber wants to listen to.
+     *
+     * The array keys are event names and the value can be:
+     *
+     *  * The method name to call (priority defaults to 0)
+     *  * An array composed of the method name to call and the priority
+     *  * An array of arrays composed of the method names to call and respective
+     *    priorities, or 0 if unset
+     *
+     * For instance:
+     *
+     *  * array('eventName' => 'methodName')
+     *  * array('eventName' => array('methodName', $priority))
+     *  * array('eventName' => array(array('methodName1', $priority), array('methodName2')))
+     *
+     * @return array The event names to listen to
+     */
+    public static function getSubscribedEvents();
+}

+ 175 - 0
system/vendor/symfony/event-dispatcher/GenericEvent.php

@@ -0,0 +1,175 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * Event encapsulation class.
+ *
+ * Encapsulates events thus decoupling the observer from the subject they encapsulate.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
+{
+    protected $subject;
+    protected $arguments;
+
+    /**
+     * Encapsulate an event with $subject and $args.
+     *
+     * @param mixed $subject   The subject of the event, usually an object or a callable
+     * @param array $arguments Arguments to store in the event
+     */
+    public function __construct($subject = null, array $arguments = array())
+    {
+        $this->subject = $subject;
+        $this->arguments = $arguments;
+    }
+
+    /**
+     * Getter for subject property.
+     *
+     * @return mixed $subject The observer subject
+     */
+    public function getSubject()
+    {
+        return $this->subject;
+    }
+
+    /**
+     * Get argument by key.
+     *
+     * @param string $key Key
+     *
+     * @return mixed Contents of array key
+     *
+     * @throws \InvalidArgumentException if key is not found
+     */
+    public function getArgument($key)
+    {
+        if ($this->hasArgument($key)) {
+            return $this->arguments[$key];
+        }
+
+        throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key));
+    }
+
+    /**
+     * Add argument to event.
+     *
+     * @param string $key   Argument name
+     * @param mixed  $value Value
+     *
+     * @return $this
+     */
+    public function setArgument($key, $value)
+    {
+        $this->arguments[$key] = $value;
+
+        return $this;
+    }
+
+    /**
+     * Getter for all arguments.
+     *
+     * @return array
+     */
+    public function getArguments()
+    {
+        return $this->arguments;
+    }
+
+    /**
+     * Set args property.
+     *
+     * @param array $args Arguments
+     *
+     * @return $this
+     */
+    public function setArguments(array $args = array())
+    {
+        $this->arguments = $args;
+
+        return $this;
+    }
+
+    /**
+     * Has argument.
+     *
+     * @param string $key Key of arguments array
+     *
+     * @return bool
+     */
+    public function hasArgument($key)
+    {
+        return array_key_exists($key, $this->arguments);
+    }
+
+    /**
+     * ArrayAccess for argument getter.
+     *
+     * @param string $key Array key
+     *
+     * @return mixed
+     *
+     * @throws \InvalidArgumentException if key does not exist in $this->args
+     */
+    public function offsetGet($key)
+    {
+        return $this->getArgument($key);
+    }
+
+    /**
+     * ArrayAccess for argument setter.
+     *
+     * @param string $key   Array key to set
+     * @param mixed  $value Value
+     */
+    public function offsetSet($key, $value)
+    {
+        $this->setArgument($key, $value);
+    }
+
+    /**
+     * ArrayAccess for unset argument.
+     *
+     * @param string $key Array key
+     */
+    public function offsetUnset($key)
+    {
+        if ($this->hasArgument($key)) {
+            unset($this->arguments[$key]);
+        }
+    }
+
+    /**
+     * ArrayAccess has argument.
+     *
+     * @param string $key Array key
+     *
+     * @return bool
+     */
+    public function offsetExists($key)
+    {
+        return $this->hasArgument($key);
+    }
+
+    /**
+     * IteratorAggregate for iterating over the object like an array.
+     *
+     * @return \ArrayIterator
+     */
+    public function getIterator()
+    {
+        return new \ArrayIterator($this->arguments);
+    }
+}

+ 91 - 0
system/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php

@@ -0,0 +1,91 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * A read-only proxy for an event dispatcher.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class ImmutableEventDispatcher implements EventDispatcherInterface
+{
+    private $dispatcher;
+
+    public function __construct(EventDispatcherInterface $dispatcher)
+    {
+        $this->dispatcher = $dispatcher;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dispatch($eventName, Event $event = null)
+    {
+        return $this->dispatcher->dispatch($eventName, $event);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addListener($eventName, $listener, $priority = 0)
+    {
+        throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addSubscriber(EventSubscriberInterface $subscriber)
+    {
+        throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function removeListener($eventName, $listener)
+    {
+        throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function removeSubscriber(EventSubscriberInterface $subscriber)
+    {
+        throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getListeners($eventName = null)
+    {
+        return $this->dispatcher->getListeners($eventName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getListenerPriority($eventName, $listener)
+    {
+        return $this->dispatcher->getListenerPriority($eventName, $listener);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasListeners($eventName = null)
+    {
+        return $this->dispatcher->hasListeners($eventName);
+    }
+}

+ 19 - 0
system/vendor/symfony/event-dispatcher/LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2004-2017 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 15 - 0
system/vendor/symfony/event-dispatcher/README.md

@@ -0,0 +1,15 @@
+EventDispatcher Component
+=========================
+
+The EventDispatcher component provides tools that allow your application
+components to communicate with each other by dispatching events and listening to
+them.
+
+Resources
+---------
+
+  * [Documentation](https://symfony.com/doc/current/components/event_dispatcher/index.html)
+  * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+  * [Report issues](https://github.com/symfony/symfony/issues) and
+    [send Pull Requests](https://github.com/symfony/symfony/pulls)
+    in the [main Symfony repository](https://github.com/symfony/symfony)

+ 442 - 0
system/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php

@@ -0,0 +1,442 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+abstract class AbstractEventDispatcherTest extends TestCase
+{
+    /* Some pseudo events */
+    const preFoo = 'pre.foo';
+    const postFoo = 'post.foo';
+    const preBar = 'pre.bar';
+    const postBar = 'post.bar';
+
+    /**
+     * @var EventDispatcher
+     */
+    private $dispatcher;
+
+    private $listener;
+
+    protected function setUp()
+    {
+        $this->dispatcher = $this->createEventDispatcher();
+        $this->listener = new TestEventListener();
+    }
+
+    protected function tearDown()
+    {
+        $this->dispatcher = null;
+        $this->listener = null;
+    }
+
+    abstract protected function createEventDispatcher();
+
+    public function testInitialState()
+    {
+        $this->assertEquals(array(), $this->dispatcher->getListeners());
+        $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
+    }
+
+    public function testAddListener()
+    {
+        $this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo'));
+        $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'));
+        $this->assertTrue($this->dispatcher->hasListeners());
+        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
+        $this->assertCount(1, $this->dispatcher->getListeners(self::preFoo));
+        $this->assertCount(1, $this->dispatcher->getListeners(self::postFoo));
+        $this->assertCount(2, $this->dispatcher->getListeners());
+    }
+
+    public function testGetListenersSortsByPriority()
+    {
+        $listener1 = new TestEventListener();
+        $listener2 = new TestEventListener();
+        $listener3 = new TestEventListener();
+        $listener1->name = '1';
+        $listener2->name = '2';
+        $listener3->name = '3';
+
+        $this->dispatcher->addListener('pre.foo', array($listener1, 'preFoo'), -10);
+        $this->dispatcher->addListener('pre.foo', array($listener2, 'preFoo'), 10);
+        $this->dispatcher->addListener('pre.foo', array($listener3, 'preFoo'));
+
+        $expected = array(
+            array($listener2, 'preFoo'),
+            array($listener3, 'preFoo'),
+            array($listener1, 'preFoo'),
+        );
+
+        $this->assertSame($expected, $this->dispatcher->getListeners('pre.foo'));
+    }
+
+    public function testGetAllListenersSortsByPriority()
+    {
+        $listener1 = new TestEventListener();
+        $listener2 = new TestEventListener();
+        $listener3 = new TestEventListener();
+        $listener4 = new TestEventListener();
+        $listener5 = new TestEventListener();
+        $listener6 = new TestEventListener();
+
+        $this->dispatcher->addListener('pre.foo', $listener1, -10);
+        $this->dispatcher->addListener('pre.foo', $listener2);
+        $this->dispatcher->addListener('pre.foo', $listener3, 10);
+        $this->dispatcher->addListener('post.foo', $listener4, -10);
+        $this->dispatcher->addListener('post.foo', $listener5);
+        $this->dispatcher->addListener('post.foo', $listener6, 10);
+
+        $expected = array(
+            'pre.foo' => array($listener3, $listener2, $listener1),
+            'post.foo' => array($listener6, $listener5, $listener4),
+        );
+
+        $this->assertSame($expected, $this->dispatcher->getListeners());
+    }
+
+    public function testGetListenerPriority()
+    {
+        $listener1 = new TestEventListener();
+        $listener2 = new TestEventListener();
+
+        $this->dispatcher->addListener('pre.foo', $listener1, -10);
+        $this->dispatcher->addListener('pre.foo', $listener2);
+
+        $this->assertSame(-10, $this->dispatcher->getListenerPriority('pre.foo', $listener1));
+        $this->assertSame(0, $this->dispatcher->getListenerPriority('pre.foo', $listener2));
+        $this->assertNull($this->dispatcher->getListenerPriority('pre.bar', $listener2));
+        $this->assertNull($this->dispatcher->getListenerPriority('pre.foo', function () {}));
+    }
+
+    public function testDispatch()
+    {
+        $this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo'));
+        $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'));
+        $this->dispatcher->dispatch(self::preFoo);
+        $this->assertTrue($this->listener->preFooInvoked);
+        $this->assertFalse($this->listener->postFooInvoked);
+        $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch('noevent'));
+        $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(self::preFoo));
+        $event = new Event();
+        $return = $this->dispatcher->dispatch(self::preFoo, $event);
+        $this->assertSame($event, $return);
+    }
+
+    public function testDispatchForClosure()
+    {
+        $invoked = 0;
+        $listener = function () use (&$invoked) {
+            ++$invoked;
+        };
+        $this->dispatcher->addListener('pre.foo', $listener);
+        $this->dispatcher->addListener('post.foo', $listener);
+        $this->dispatcher->dispatch(self::preFoo);
+        $this->assertEquals(1, $invoked);
+    }
+
+    public function testStopEventPropagation()
+    {
+        $otherListener = new TestEventListener();
+
+        // postFoo() stops the propagation, so only one listener should
+        // be executed
+        // Manually set priority to enforce $this->listener to be called first
+        $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'), 10);
+        $this->dispatcher->addListener('post.foo', array($otherListener, 'preFoo'));
+        $this->dispatcher->dispatch(self::postFoo);
+        $this->assertTrue($this->listener->postFooInvoked);
+        $this->assertFalse($otherListener->postFooInvoked);
+    }
+
+    public function testDispatchByPriority()
+    {
+        $invoked = array();
+        $listener1 = function () use (&$invoked) {
+            $invoked[] = '1';
+        };
+        $listener2 = function () use (&$invoked) {
+            $invoked[] = '2';
+        };
+        $listener3 = function () use (&$invoked) {
+            $invoked[] = '3';
+        };
+        $this->dispatcher->addListener('pre.foo', $listener1, -10);
+        $this->dispatcher->addListener('pre.foo', $listener2);
+        $this->dispatcher->addListener('pre.foo', $listener3, 10);
+        $this->dispatcher->dispatch(self::preFoo);
+        $this->assertEquals(array('3', '2', '1'), $invoked);
+    }
+
+    public function testRemoveListener()
+    {
+        $this->dispatcher->addListener('pre.bar', $this->listener);
+        $this->assertTrue($this->dispatcher->hasListeners(self::preBar));
+        $this->dispatcher->removeListener('pre.bar', $this->listener);
+        $this->assertFalse($this->dispatcher->hasListeners(self::preBar));
+        $this->dispatcher->removeListener('notExists', $this->listener);
+    }
+
+    public function testAddSubscriber()
+    {
+        $eventSubscriber = new TestEventSubscriber();
+        $this->dispatcher->addSubscriber($eventSubscriber);
+        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
+    }
+
+    public function testAddSubscriberWithPriorities()
+    {
+        $eventSubscriber = new TestEventSubscriber();
+        $this->dispatcher->addSubscriber($eventSubscriber);
+
+        $eventSubscriber = new TestEventSubscriberWithPriorities();
+        $this->dispatcher->addSubscriber($eventSubscriber);
+
+        $listeners = $this->dispatcher->getListeners('pre.foo');
+        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertCount(2, $listeners);
+        $this->assertInstanceOf('Symfony\Component\EventDispatcher\Tests\TestEventSubscriberWithPriorities', $listeners[0][0]);
+    }
+
+    public function testAddSubscriberWithMultipleListeners()
+    {
+        $eventSubscriber = new TestEventSubscriberWithMultipleListeners();
+        $this->dispatcher->addSubscriber($eventSubscriber);
+
+        $listeners = $this->dispatcher->getListeners('pre.foo');
+        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertCount(2, $listeners);
+        $this->assertEquals('preFoo2', $listeners[0][1]);
+    }
+
+    public function testRemoveSubscriber()
+    {
+        $eventSubscriber = new TestEventSubscriber();
+        $this->dispatcher->addSubscriber($eventSubscriber);
+        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
+        $this->dispatcher->removeSubscriber($eventSubscriber);
+        $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
+    }
+
+    public function testRemoveSubscriberWithPriorities()
+    {
+        $eventSubscriber = new TestEventSubscriberWithPriorities();
+        $this->dispatcher->addSubscriber($eventSubscriber);
+        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+        $this->dispatcher->removeSubscriber($eventSubscriber);
+        $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+    }
+
+    public function testRemoveSubscriberWithMultipleListeners()
+    {
+        $eventSubscriber = new TestEventSubscriberWithMultipleListeners();
+        $this->dispatcher->addSubscriber($eventSubscriber);
+        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertCount(2, $this->dispatcher->getListeners(self::preFoo));
+        $this->dispatcher->removeSubscriber($eventSubscriber);
+        $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+    }
+
+    public function testEventReceivesTheDispatcherInstanceAsArgument()
+    {
+        $listener = new TestWithDispatcher();
+        $this->dispatcher->addListener('test', array($listener, 'foo'));
+        $this->assertNull($listener->name);
+        $this->assertNull($listener->dispatcher);
+        $this->dispatcher->dispatch('test');
+        $this->assertEquals('test', $listener->name);
+        $this->assertSame($this->dispatcher, $listener->dispatcher);
+    }
+
+    /**
+     * @see https://bugs.php.net/bug.php?id=62976
+     *
+     * This bug affects:
+     *  - The PHP 5.3 branch for versions < 5.3.18
+     *  - The PHP 5.4 branch for versions < 5.4.8
+     *  - The PHP 5.5 branch is not affected
+     */
+    public function testWorkaroundForPhpBug62976()
+    {
+        $dispatcher = $this->createEventDispatcher();
+        $dispatcher->addListener('bug.62976', new CallableClass());
+        $dispatcher->removeListener('bug.62976', function () {});
+        $this->assertTrue($dispatcher->hasListeners('bug.62976'));
+    }
+
+    public function testHasListenersWhenAddedCallbackListenerIsRemoved()
+    {
+        $listener = function () {};
+        $this->dispatcher->addListener('foo', $listener);
+        $this->dispatcher->removeListener('foo', $listener);
+        $this->assertFalse($this->dispatcher->hasListeners());
+    }
+
+    public function testGetListenersWhenAddedCallbackListenerIsRemoved()
+    {
+        $listener = function () {};
+        $this->dispatcher->addListener('foo', $listener);
+        $this->dispatcher->removeListener('foo', $listener);
+        $this->assertSame(array(), $this->dispatcher->getListeners());
+    }
+
+    public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled()
+    {
+        $this->assertFalse($this->dispatcher->hasListeners('foo'));
+        $this->assertFalse($this->dispatcher->hasListeners());
+    }
+
+    public function testHasListenersIsLazy()
+    {
+        $called = 0;
+        $listener = array(function () use (&$called) { ++$called; }, 'onFoo');
+        $this->dispatcher->addListener('foo', $listener);
+        $this->assertTrue($this->dispatcher->hasListeners());
+        $this->assertTrue($this->dispatcher->hasListeners('foo'));
+        $this->assertSame(0, $called);
+    }
+
+    public function testDispatchLazyListener()
+    {
+        $called = 0;
+        $factory = function () use (&$called) {
+            ++$called;
+
+            return new TestWithDispatcher();
+        };
+        $this->dispatcher->addListener('foo', array($factory, 'foo'));
+        $this->assertSame(0, $called);
+        $this->dispatcher->dispatch('foo', new Event());
+        $this->dispatcher->dispatch('foo', new Event());
+        $this->assertSame(1, $called);
+    }
+
+    public function testRemoveFindsLazyListeners()
+    {
+        $test = new TestWithDispatcher();
+        $factory = function () use ($test) { return $test; };
+
+        $this->dispatcher->addListener('foo', array($factory, 'foo'));
+        $this->assertTrue($this->dispatcher->hasListeners('foo'));
+        $this->dispatcher->removeListener('foo', array($test, 'foo'));
+        $this->assertFalse($this->dispatcher->hasListeners('foo'));
+
+        $this->dispatcher->addListener('foo', array($test, 'foo'));
+        $this->assertTrue($this->dispatcher->hasListeners('foo'));
+        $this->dispatcher->removeListener('foo', array($factory, 'foo'));
+        $this->assertFalse($this->dispatcher->hasListeners('foo'));
+    }
+
+    public function testPriorityFindsLazyListeners()
+    {
+        $test = new TestWithDispatcher();
+        $factory = function () use ($test) { return $test; };
+
+        $this->dispatcher->addListener('foo', array($factory, 'foo'), 3);
+        $this->assertSame(3, $this->dispatcher->getListenerPriority('foo', array($test, 'foo')));
+        $this->dispatcher->removeListener('foo', array($factory, 'foo'));
+
+        $this->dispatcher->addListener('foo', array($test, 'foo'), 5);
+        $this->assertSame(5, $this->dispatcher->getListenerPriority('foo', array($factory, 'foo')));
+    }
+
+    public function testGetLazyListeners()
+    {
+        $test = new TestWithDispatcher();
+        $factory = function () use ($test) { return $test; };
+
+        $this->dispatcher->addListener('foo', array($factory, 'foo'), 3);
+        $this->assertSame(array(array($test, 'foo')), $this->dispatcher->getListeners('foo'));
+
+        $this->dispatcher->removeListener('foo', array($test, 'foo'));
+        $this->dispatcher->addListener('bar', array($factory, 'foo'), 3);
+        $this->assertSame(array('bar' => array(array($test, 'foo'))), $this->dispatcher->getListeners());
+    }
+}
+
+class CallableClass
+{
+    public function __invoke()
+    {
+    }
+}
+
+class TestEventListener
+{
+    public $preFooInvoked = false;
+    public $postFooInvoked = false;
+
+    /* Listener methods */
+
+    public function preFoo(Event $e)
+    {
+        $this->preFooInvoked = true;
+    }
+
+    public function postFoo(Event $e)
+    {
+        $this->postFooInvoked = true;
+
+        $e->stopPropagation();
+    }
+}
+
+class TestWithDispatcher
+{
+    public $name;
+    public $dispatcher;
+
+    public function foo(Event $e, $name, $dispatcher)
+    {
+        $this->name = $name;
+        $this->dispatcher = $dispatcher;
+    }
+}
+
+class TestEventSubscriber implements EventSubscriberInterface
+{
+    public static function getSubscribedEvents()
+    {
+        return array('pre.foo' => 'preFoo', 'post.foo' => 'postFoo');
+    }
+}
+
+class TestEventSubscriberWithPriorities implements EventSubscriberInterface
+{
+    public static function getSubscribedEvents()
+    {
+        return array(
+            'pre.foo' => array('preFoo', 10),
+            'post.foo' => array('postFoo'),
+            );
+    }
+}
+
+class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface
+{
+    public static function getSubscribedEvents()
+    {
+        return array('pre.foo' => array(
+            array('preFoo1'),
+            array('preFoo2', 10),
+        ));
+    }
+}

+ 210 - 0
system/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php

@@ -0,0 +1,210 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests;
+
+use Symfony\Component\DependencyInjection\Container;
+use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * @group legacy
+ */
+class ContainerAwareEventDispatcherTest extends AbstractEventDispatcherTest
+{
+    protected function createEventDispatcher()
+    {
+        $container = new Container();
+
+        return new ContainerAwareEventDispatcher($container);
+    }
+
+    public function testAddAListenerService()
+    {
+        $event = new Event();
+
+        $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+        $service
+            ->expects($this->once())
+            ->method('onEvent')
+            ->with($event)
+        ;
+
+        $container = new Container();
+        $container->set('service.listener', $service);
+
+        $dispatcher = new ContainerAwareEventDispatcher($container);
+        $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+        $dispatcher->dispatch('onEvent', $event);
+    }
+
+    public function testAddASubscriberService()
+    {
+        $event = new Event();
+
+        $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\SubscriberService')->getMock();
+
+        $service
+            ->expects($this->once())
+            ->method('onEvent')
+            ->with($event)
+        ;
+
+        $service
+            ->expects($this->once())
+            ->method('onEventWithPriority')
+            ->with($event)
+        ;
+
+        $service
+            ->expects($this->once())
+            ->method('onEventNested')
+            ->with($event)
+        ;
+
+        $container = new Container();
+        $container->set('service.subscriber', $service);
+
+        $dispatcher = new ContainerAwareEventDispatcher($container);
+        $dispatcher->addSubscriberService('service.subscriber', 'Symfony\Component\EventDispatcher\Tests\SubscriberService');
+
+        $dispatcher->dispatch('onEvent', $event);
+        $dispatcher->dispatch('onEventWithPriority', $event);
+        $dispatcher->dispatch('onEventNested', $event);
+    }
+
+    public function testPreventDuplicateListenerService()
+    {
+        $event = new Event();
+
+        $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+        $service
+            ->expects($this->once())
+            ->method('onEvent')
+            ->with($event)
+        ;
+
+        $container = new Container();
+        $container->set('service.listener', $service);
+
+        $dispatcher = new ContainerAwareEventDispatcher($container);
+        $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 5);
+        $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 10);
+
+        $dispatcher->dispatch('onEvent', $event);
+    }
+
+    public function testHasListenersOnLazyLoad()
+    {
+        $event = new Event();
+
+        $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+        $container = new Container();
+        $container->set('service.listener', $service);
+
+        $dispatcher = new ContainerAwareEventDispatcher($container);
+        $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+        $service
+            ->expects($this->once())
+            ->method('onEvent')
+            ->with($event)
+        ;
+
+        $this->assertTrue($dispatcher->hasListeners());
+
+        if ($dispatcher->hasListeners('onEvent')) {
+            $dispatcher->dispatch('onEvent');
+        }
+    }
+
+    public function testGetListenersOnLazyLoad()
+    {
+        $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+        $container = new Container();
+        $container->set('service.listener', $service);
+
+        $dispatcher = new ContainerAwareEventDispatcher($container);
+        $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+        $listeners = $dispatcher->getListeners();
+
+        $this->assertTrue(isset($listeners['onEvent']));
+
+        $this->assertCount(1, $dispatcher->getListeners('onEvent'));
+    }
+
+    public function testRemoveAfterDispatch()
+    {
+        $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+        $container = new Container();
+        $container->set('service.listener', $service);
+
+        $dispatcher = new ContainerAwareEventDispatcher($container);
+        $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+        $dispatcher->dispatch('onEvent', new Event());
+        $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent'));
+        $this->assertFalse($dispatcher->hasListeners('onEvent'));
+    }
+
+    public function testRemoveBeforeDispatch()
+    {
+        $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+        $container = new Container();
+        $container->set('service.listener', $service);
+
+        $dispatcher = new ContainerAwareEventDispatcher($container);
+        $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+        $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent'));
+        $this->assertFalse($dispatcher->hasListeners('onEvent'));
+    }
+}
+
+class Service
+{
+    public function onEvent(Event $e)
+    {
+    }
+}
+
+class SubscriberService implements EventSubscriberInterface
+{
+    public static function getSubscribedEvents()
+    {
+        return array(
+            'onEvent' => 'onEvent',
+            'onEventWithPriority' => array('onEventWithPriority', 10),
+            'onEventNested' => array(array('onEventNested')),
+        );
+    }
+
+    public function onEvent(Event $e)
+    {
+    }
+
+    public function onEventWithPriority(Event $e)
+    {
+    }
+
+    public function onEventNested(Event $e)
+    {
+    }
+}

+ 242 - 0
system/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php

@@ -0,0 +1,242 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests\Debug;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\Stopwatch\Stopwatch;
+
+class TraceableEventDispatcherTest extends TestCase
+{
+    public function testAddRemoveListener()
+    {
+        $dispatcher = new EventDispatcher();
+        $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+        $tdispatcher->addListener('foo', $listener = function () {});
+        $listeners = $dispatcher->getListeners('foo');
+        $this->assertCount(1, $listeners);
+        $this->assertSame($listener, $listeners[0]);
+
+        $tdispatcher->removeListener('foo', $listener);
+        $this->assertCount(0, $dispatcher->getListeners('foo'));
+    }
+
+    public function testGetListeners()
+    {
+        $dispatcher = new EventDispatcher();
+        $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+        $tdispatcher->addListener('foo', $listener = function () {});
+        $this->assertSame($dispatcher->getListeners('foo'), $tdispatcher->getListeners('foo'));
+    }
+
+    public function testHasListeners()
+    {
+        $dispatcher = new EventDispatcher();
+        $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+        $this->assertFalse($dispatcher->hasListeners('foo'));
+        $this->assertFalse($tdispatcher->hasListeners('foo'));
+
+        $tdispatcher->addListener('foo', $listener = function () {});
+        $this->assertTrue($dispatcher->hasListeners('foo'));
+        $this->assertTrue($tdispatcher->hasListeners('foo'));
+    }
+
+    public function testGetListenerPriority()
+    {
+        $dispatcher = new EventDispatcher();
+        $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+        $tdispatcher->addListener('foo', function () {}, 123);
+
+        $listeners = $dispatcher->getListeners('foo');
+        $this->assertSame(123, $tdispatcher->getListenerPriority('foo', $listeners[0]));
+
+        // Verify that priority is preserved when listener is removed and re-added
+        // in preProcess() and postProcess().
+        $tdispatcher->dispatch('foo', new Event());
+        $listeners = $dispatcher->getListeners('foo');
+        $this->assertSame(123, $tdispatcher->getListenerPriority('foo', $listeners[0]));
+    }
+
+    public function testGetListenerPriorityWhileDispatching()
+    {
+        $tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $priorityWhileDispatching = null;
+
+        $listener = function () use ($tdispatcher, &$priorityWhileDispatching, &$listener) {
+            $priorityWhileDispatching = $tdispatcher->getListenerPriority('bar', $listener);
+        };
+
+        $tdispatcher->addListener('bar', $listener, 5);
+        $tdispatcher->dispatch('bar');
+        $this->assertSame(5, $priorityWhileDispatching);
+    }
+
+    public function testAddRemoveSubscriber()
+    {
+        $dispatcher = new EventDispatcher();
+        $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+        $subscriber = new EventSubscriber();
+
+        $tdispatcher->addSubscriber($subscriber);
+        $listeners = $dispatcher->getListeners('foo');
+        $this->assertCount(1, $listeners);
+        $this->assertSame(array($subscriber, 'call'), $listeners[0]);
+
+        $tdispatcher->removeSubscriber($subscriber);
+        $this->assertCount(0, $dispatcher->getListeners('foo'));
+    }
+
+    public function testGetCalledListeners()
+    {
+        $tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $tdispatcher->addListener('foo', function () {}, 5);
+
+        $listeners = $tdispatcher->getNotCalledListeners();
+        $this->assertArrayHasKey('stub', $listeners['foo.closure']);
+        unset($listeners['foo.closure']['stub']);
+        $this->assertEquals(array(), $tdispatcher->getCalledListeners());
+        $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'pretty' => 'closure', 'priority' => 5)), $listeners);
+
+        $tdispatcher->dispatch('foo');
+
+        $listeners = $tdispatcher->getCalledListeners();
+        $this->assertArrayHasKey('stub', $listeners['foo.closure']);
+        unset($listeners['foo.closure']['stub']);
+        $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'pretty' => 'closure', 'priority' => 5)), $listeners);
+        $this->assertEquals(array(), $tdispatcher->getNotCalledListeners());
+    }
+
+    public function testGetCalledListenersNested()
+    {
+        $tdispatcher = null;
+        $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $dispatcher->addListener('foo', function (Event $event, $eventName, $dispatcher) use (&$tdispatcher) {
+            $tdispatcher = $dispatcher;
+            $dispatcher->dispatch('bar');
+        });
+        $dispatcher->addListener('bar', function (Event $event) {});
+        $dispatcher->dispatch('foo');
+        $this->assertSame($dispatcher, $tdispatcher);
+        $this->assertCount(2, $dispatcher->getCalledListeners());
+    }
+
+    public function testLogger()
+    {
+        $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+
+        $dispatcher = new EventDispatcher();
+        $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger);
+        $tdispatcher->addListener('foo', $listener1 = function () {});
+        $tdispatcher->addListener('foo', $listener2 = function () {});
+
+        $logger->expects($this->at(0))->method('debug')->with('Notified event "{event}" to listener "{listener}".', array('event' => 'foo', 'listener' => 'closure'));
+        $logger->expects($this->at(1))->method('debug')->with('Notified event "{event}" to listener "{listener}".', array('event' => 'foo', 'listener' => 'closure'));
+
+        $tdispatcher->dispatch('foo');
+    }
+
+    public function testLoggerWithStoppedEvent()
+    {
+        $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+
+        $dispatcher = new EventDispatcher();
+        $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger);
+        $tdispatcher->addListener('foo', $listener1 = function (Event $event) { $event->stopPropagation(); });
+        $tdispatcher->addListener('foo', $listener2 = function () {});
+
+        $logger->expects($this->at(0))->method('debug')->with('Notified event "{event}" to listener "{listener}".', array('event' => 'foo', 'listener' => 'closure'));
+        $logger->expects($this->at(1))->method('debug')->with('Listener "{listener}" stopped propagation of the event "{event}".', array('event' => 'foo', 'listener' => 'closure'));
+        $logger->expects($this->at(2))->method('debug')->with('Listener "{listener}" was not called for event "{event}".', array('event' => 'foo', 'listener' => 'closure'));
+
+        $tdispatcher->dispatch('foo');
+    }
+
+    public function testDispatchCallListeners()
+    {
+        $called = array();
+
+        $dispatcher = new EventDispatcher();
+        $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+        $tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo1'; }, 10);
+        $tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo2'; }, 20);
+
+        $tdispatcher->dispatch('foo');
+
+        $this->assertSame(array('foo2', 'foo1'), $called);
+    }
+
+    public function testDispatchNested()
+    {
+        $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $loop = 1;
+        $dispatchedEvents = 0;
+        $dispatcher->addListener('foo', $listener1 = function () use ($dispatcher, &$loop) {
+            ++$loop;
+            if (2 == $loop) {
+                $dispatcher->dispatch('foo');
+            }
+        });
+        $dispatcher->addListener('foo', function () use (&$dispatchedEvents) {
+            ++$dispatchedEvents;
+        });
+
+        $dispatcher->dispatch('foo');
+
+        $this->assertSame(2, $dispatchedEvents);
+    }
+
+    public function testDispatchReusedEventNested()
+    {
+        $nestedCall = false;
+        $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $dispatcher->addListener('foo', function (Event $e) use ($dispatcher) {
+            $dispatcher->dispatch('bar', $e);
+        });
+        $dispatcher->addListener('bar', function (Event $e) use (&$nestedCall) {
+            $nestedCall = true;
+        });
+
+        $this->assertFalse($nestedCall);
+        $dispatcher->dispatch('foo');
+        $this->assertTrue($nestedCall);
+    }
+
+    public function testListenerCanRemoveItselfWhenExecuted()
+    {
+        $eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $listener1 = function ($event, $eventName, EventDispatcherInterface $dispatcher) use (&$listener1) {
+            $dispatcher->removeListener('foo', $listener1);
+        };
+        $eventDispatcher->addListener('foo', $listener1);
+        $eventDispatcher->addListener('foo', function () {});
+        $eventDispatcher->dispatch('foo');
+
+        $this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed');
+    }
+}
+
+class EventSubscriber implements EventSubscriberInterface
+{
+    public static function getSubscribedEvents()
+    {
+        return array('foo' => 'call');
+    }
+}

+ 167 - 0
system/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php

@@ -0,0 +1,167 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests\DependencyInjection;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
+
+class RegisterListenersPassTest extends TestCase
+{
+    /**
+     * Tests that event subscribers not implementing EventSubscriberInterface
+     * trigger an exception.
+     *
+     * @expectedException \InvalidArgumentException
+     */
+    public function testEventSubscriberWithoutInterface()
+    {
+        // one service, not implementing any interface
+        $services = array(
+            'my_event_subscriber' => array(0 => array()),
+        );
+
+        $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock();
+        $definition->expects($this->atLeastOnce())
+            ->method('getClass')
+            ->will($this->returnValue('stdClass'));
+
+        $builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition'))->getMock();
+        $builder->expects($this->any())
+            ->method('hasDefinition')
+            ->will($this->returnValue(true));
+
+        // We don't test kernel.event_listener here
+        $builder->expects($this->atLeastOnce())
+            ->method('findTaggedServiceIds')
+            ->will($this->onConsecutiveCalls(array(), $services));
+
+        $builder->expects($this->atLeastOnce())
+            ->method('getDefinition')
+            ->will($this->returnValue($definition));
+
+        $registerListenersPass = new RegisterListenersPass();
+        $registerListenersPass->process($builder);
+    }
+
+    public function testValidEventSubscriber()
+    {
+        $services = array(
+            'my_event_subscriber' => array(0 => array()),
+        );
+
+        $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock();
+        $definition->expects($this->atLeastOnce())
+            ->method('getClass')
+            ->will($this->returnValue('Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService'));
+
+        $builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition', 'findDefinition'))->getMock();
+        $builder->expects($this->any())
+            ->method('hasDefinition')
+            ->will($this->returnValue(true));
+
+        // We don't test kernel.event_listener here
+        $builder->expects($this->atLeastOnce())
+            ->method('findTaggedServiceIds')
+            ->will($this->onConsecutiveCalls(array(), $services));
+
+        $builder->expects($this->atLeastOnce())
+            ->method('getDefinition')
+            ->will($this->returnValue($definition));
+
+        $builder->expects($this->atLeastOnce())
+            ->method('findDefinition')
+            ->will($this->returnValue($definition));
+
+        $registerListenersPass = new RegisterListenersPass();
+        $registerListenersPass->process($builder);
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     * @expectedExceptionMessage The service "foo" tagged "kernel.event_listener" must not be abstract.
+     */
+    public function testAbstractEventListener()
+    {
+        $container = new ContainerBuilder();
+        $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_listener', array());
+        $container->register('event_dispatcher', 'stdClass');
+
+        $registerListenersPass = new RegisterListenersPass();
+        $registerListenersPass->process($container);
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     * @expectedExceptionMessage The service "foo" tagged "kernel.event_subscriber" must not be abstract.
+     */
+    public function testAbstractEventSubscriber()
+    {
+        $container = new ContainerBuilder();
+        $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_subscriber', array());
+        $container->register('event_dispatcher', 'stdClass');
+
+        $registerListenersPass = new RegisterListenersPass();
+        $registerListenersPass->process($container);
+    }
+
+    public function testEventSubscriberResolvableClassName()
+    {
+        $container = new ContainerBuilder();
+
+        $container->setParameter('subscriber.class', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService');
+        $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array());
+        $container->register('event_dispatcher', 'stdClass');
+
+        $registerListenersPass = new RegisterListenersPass();
+        $registerListenersPass->process($container);
+
+        $definition = $container->getDefinition('event_dispatcher');
+        $expectedCalls = array(
+            array(
+                'addListener',
+                array(
+                    'event',
+                    array(new ServiceClosureArgument(new Reference('foo')), 'onEvent'),
+                    0,
+                ),
+            ),
+        );
+        $this->assertEquals($expectedCalls, $definition->getMethodCalls());
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     * @expectedExceptionMessage You have requested a non-existent parameter "subscriber.class"
+     */
+    public function testEventSubscriberUnresolvableClassName()
+    {
+        $container = new ContainerBuilder();
+        $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array());
+        $container->register('event_dispatcher', 'stdClass');
+
+        $registerListenersPass = new RegisterListenersPass();
+        $registerListenersPass->process($container);
+    }
+}
+
+class SubscriberService implements \Symfony\Component\EventDispatcher\EventSubscriberInterface
+{
+    public static function getSubscribedEvents()
+    {
+        return array(
+            'event' => 'onEvent',
+        );
+    }
+}

+ 22 - 0
system/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php

@@ -0,0 +1,22 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests;
+
+use Symfony\Component\EventDispatcher\EventDispatcher;
+
+class EventDispatcherTest extends AbstractEventDispatcherTest
+{
+    protected function createEventDispatcher()
+    {
+        return new EventDispatcher();
+    }
+}

+ 55 - 0
system/vendor/symfony/event-dispatcher/Tests/EventTest.php

@@ -0,0 +1,55 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Test class for Event.
+ */
+class EventTest extends TestCase
+{
+    /**
+     * @var \Symfony\Component\EventDispatcher\Event
+     */
+    protected $event;
+
+    /**
+     * Sets up the fixture, for example, opens a network connection.
+     * This method is called before a test is executed.
+     */
+    protected function setUp()
+    {
+        $this->event = new Event();
+    }
+
+    /**
+     * Tears down the fixture, for example, closes a network connection.
+     * This method is called after a test is executed.
+     */
+    protected function tearDown()
+    {
+        $this->event = null;
+    }
+
+    public function testIsPropagationStopped()
+    {
+        $this->assertFalse($this->event->isPropagationStopped());
+    }
+
+    public function testStopPropagationAndIsPropagationStopped()
+    {
+        $this->event->stopPropagation();
+        $this->assertTrue($this->event->isPropagationStopped());
+    }
+}

+ 140 - 0
system/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php

@@ -0,0 +1,140 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\GenericEvent;
+
+/**
+ * Test class for Event.
+ */
+class GenericEventTest extends TestCase
+{
+    /**
+     * @var GenericEvent
+     */
+    private $event;
+
+    private $subject;
+
+    /**
+     * Prepares the environment before running a test.
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $this->subject = new \stdClass();
+        $this->event = new GenericEvent($this->subject, array('name' => 'Event'));
+    }
+
+    /**
+     * Cleans up the environment after running a test.
+     */
+    protected function tearDown()
+    {
+        $this->subject = null;
+        $this->event = null;
+
+        parent::tearDown();
+    }
+
+    public function testConstruct()
+    {
+        $this->assertEquals($this->event, new GenericEvent($this->subject, array('name' => 'Event')));
+    }
+
+    /**
+     * Tests Event->getArgs().
+     */
+    public function testGetArguments()
+    {
+        // test getting all
+        $this->assertSame(array('name' => 'Event'), $this->event->getArguments());
+    }
+
+    public function testSetArguments()
+    {
+        $result = $this->event->setArguments(array('foo' => 'bar'));
+        $this->assertAttributeSame(array('foo' => 'bar'), 'arguments', $this->event);
+        $this->assertSame($this->event, $result);
+    }
+
+    public function testSetArgument()
+    {
+        $result = $this->event->setArgument('foo2', 'bar2');
+        $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event);
+        $this->assertEquals($this->event, $result);
+    }
+
+    public function testGetArgument()
+    {
+        // test getting key
+        $this->assertEquals('Event', $this->event->getArgument('name'));
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testGetArgException()
+    {
+        $this->event->getArgument('nameNotExist');
+    }
+
+    public function testOffsetGet()
+    {
+        // test getting key
+        $this->assertEquals('Event', $this->event['name']);
+
+        // test getting invalid arg
+        $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException');
+        $this->assertFalse($this->event['nameNotExist']);
+    }
+
+    public function testOffsetSet()
+    {
+        $this->event['foo2'] = 'bar2';
+        $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event);
+    }
+
+    public function testOffsetUnset()
+    {
+        unset($this->event['name']);
+        $this->assertAttributeSame(array(), 'arguments', $this->event);
+    }
+
+    public function testOffsetIsset()
+    {
+        $this->assertTrue(isset($this->event['name']));
+        $this->assertFalse(isset($this->event['nameNotExist']));
+    }
+
+    public function testHasArgument()
+    {
+        $this->assertTrue($this->event->hasArgument('name'));
+        $this->assertFalse($this->event->hasArgument('nameNotExist'));
+    }
+
+    public function testGetSubject()
+    {
+        $this->assertSame($this->subject, $this->event->getSubject());
+    }
+
+    public function testHasIterator()
+    {
+        $data = array();
+        foreach ($this->event as $key => $value) {
+            $data[$key] = $value;
+        }
+        $this->assertEquals(array('name' => 'Event'), $data);
+    }
+}

+ 106 - 0
system/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php

@@ -0,0 +1,106 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
+
+/**
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class ImmutableEventDispatcherTest extends TestCase
+{
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    private $innerDispatcher;
+
+    /**
+     * @var ImmutableEventDispatcher
+     */
+    private $dispatcher;
+
+    protected function setUp()
+    {
+        $this->innerDispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
+        $this->dispatcher = new ImmutableEventDispatcher($this->innerDispatcher);
+    }
+
+    public function testDispatchDelegates()
+    {
+        $event = new Event();
+
+        $this->innerDispatcher->expects($this->once())
+            ->method('dispatch')
+            ->with('event', $event)
+            ->will($this->returnValue('result'));
+
+        $this->assertSame('result', $this->dispatcher->dispatch('event', $event));
+    }
+
+    public function testGetListenersDelegates()
+    {
+        $this->innerDispatcher->expects($this->once())
+            ->method('getListeners')
+            ->with('event')
+            ->will($this->returnValue('result'));
+
+        $this->assertSame('result', $this->dispatcher->getListeners('event'));
+    }
+
+    public function testHasListenersDelegates()
+    {
+        $this->innerDispatcher->expects($this->once())
+            ->method('hasListeners')
+            ->with('event')
+            ->will($this->returnValue('result'));
+
+        $this->assertSame('result', $this->dispatcher->hasListeners('event'));
+    }
+
+    /**
+     * @expectedException \BadMethodCallException
+     */
+    public function testAddListenerDisallowed()
+    {
+        $this->dispatcher->addListener('event', function () { return 'foo'; });
+    }
+
+    /**
+     * @expectedException \BadMethodCallException
+     */
+    public function testAddSubscriberDisallowed()
+    {
+        $subscriber = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventSubscriberInterface')->getMock();
+
+        $this->dispatcher->addSubscriber($subscriber);
+    }
+
+    /**
+     * @expectedException \BadMethodCallException
+     */
+    public function testRemoveListenerDisallowed()
+    {
+        $this->dispatcher->removeListener('event', function () { return 'foo'; });
+    }
+
+    /**
+     * @expectedException \BadMethodCallException
+     */
+    public function testRemoveSubscriberDisallowed()
+    {
+        $subscriber = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventSubscriberInterface')->getMock();
+
+        $this->dispatcher->removeSubscriber($subscriber);
+    }
+}

+ 47 - 0
system/vendor/symfony/event-dispatcher/composer.json

@@ -0,0 +1,47 @@
+{
+    "name": "symfony/event-dispatcher",
+    "type": "library",
+    "description": "Symfony EventDispatcher Component",
+    "keywords": [],
+    "homepage": "https://symfony.com",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Fabien Potencier",
+            "email": "fabien@symfony.com"
+        },
+        {
+            "name": "Symfony Community",
+            "homepage": "https://symfony.com/contributors"
+        }
+    ],
+    "require": {
+        "php": "^5.5.9|>=7.0.8"
+    },
+    "require-dev": {
+        "symfony/dependency-injection": "~3.3",
+        "symfony/expression-language": "~2.8|~3.0",
+        "symfony/config": "~2.8|~3.0",
+        "symfony/stopwatch": "~2.8|~3.0",
+        "psr/log": "~1.0"
+    },
+    "conflict": {
+        "symfony/dependency-injection": "<3.3"
+    },
+    "suggest": {
+        "symfony/dependency-injection": "",
+        "symfony/http-kernel": ""
+    },
+    "autoload": {
+        "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" },
+        "exclude-from-classmap": [
+            "/Tests/"
+        ]
+    },
+    "minimum-stability": "dev",
+    "extra": {
+        "branch-alias": {
+            "dev-master": "3.3-dev"
+        }
+    }
+}

+ 31 - 0
system/vendor/symfony/event-dispatcher/phpunit.xml.dist

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
+         backupGlobals="false"
+         colors="true"
+         bootstrap="vendor/autoload.php"
+         failOnRisky="true"
+         failOnWarning="true"
+>
+    <php>
+        <ini name="error_reporting" value="-1" />
+    </php>
+
+    <testsuites>
+        <testsuite name="Symfony EventDispatcher Component Test Suite">
+            <directory>./Tests/</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory>./</directory>
+            <exclude>
+                <directory>./Resources</directory>
+                <directory>./Tests</directory>
+                <directory>./vendor</directory>
+            </exclude>
+        </whitelist>
+    </filter>
+</phpunit>