diff --git a/.travis.yml b/.travis.yml
index f048b3b79ea28146f4b28f77c82760a61991f9a7..377391ea3806c1fd9e17ab5d2e41d8c63b2a6e42 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -13,6 +13,7 @@ matrix:
 before_script:
 - composer update
 - if [[ "$TRAVIS_PHP_VERSION" == "7.0" ]]; then composer require --dev vimeo/psalm; fi
+- if [[ "$TRAVIS_PHP_VERSION" == "5.6" ]]; then composer require --dev php-coveralls/php-coveralls; fi
 script:
 - bin/check-syntax.sh
 - if [[ "$TRAVIS_PHP_VERSION" == "5.6" ]]; then php vendor/phpunit/phpunit/phpunit --configuration tools/phpunit; else php vendor/phpunit/phpunit/phpunit --configuration tools/phpunit --no-coverage; fi
diff --git a/TESTING.md b/TESTING.md
index 3dbb738178f63caceddc528f7b3978ede726c504..e0c4fd0e94ef4cffec6addb03fe9a0f3edec5eb3 100644
--- a/TESTING.md
+++ b/TESTING.md
@@ -27,13 +27,13 @@ definition:
 namespace SimpleSAML\Test\Utils;
 ```
 
-The test classes need to extend `PHPUnit_Framework_TestCase`, and inside
+The test classes need to extend `PHPUnit\Framework\TestCase`, and inside
 you can implement as many methods as you want. `phpunit` will only run
 the ones prefixed with "test".
 
 You will usually make use of the `assert*()` methods provided by
-`PHPUnit_Framework_TestCase`, but you can also tell `phpunit` to expect
-an exception to be thrown using *phpdoc*. For example, if you want to 
+`PHPUnit\Framework\TestCase`, but you can also tell `phpunit` to expect
+an exception to be thrown using *phpdoc*. For example, if you want to
 ensure that the `SimpleSAML\Utils\HTTP::addURLParameters()` method
 throws an exception in a specific situation:
 
@@ -47,7 +47,7 @@ throws an exception in a specific situation:
 ```
 
 Refer to [the `phpunit 4.8` documentation](https://phpunit.de/manual/4.8/en/installation.html)
-for more information on how to write tests. We currently use the `phpunit 4.8` 
+for more information on how to write tests. We currently use the `phpunit 4.8`
 since it is the last version to support php 5.3.
 
 Once you have implemented your tests, you can run them locally. First,
@@ -66,7 +66,7 @@ the old version installed by composer
 ./vendor/bin/phpunit -c tools/phpunit/phpunit.xml
 ```
 
-All the tests are run by our *continuous integration* platform, 
+All the tests are run by our *continuous integration* platform,
 [travis](https://travis-ci.org/simplesamlphp/simplesamlphp). If you are
 submitting a pull request, Travis will run your tests and notify whether
 your code builds or not according to them.
diff --git a/composer.json b/composer.json
index 60fdcf5e9f2b8239b0267b0073c8b7be315d60bb..6424ead7122481cf61bc15cf284e0f0e27802bad 100644
--- a/composer.json
+++ b/composer.json
@@ -45,8 +45,7 @@
     },
     "require-dev": {
         "ext-pdo_sqlite": "*",
-        "phpunit/phpunit": "~4.8",
-        "satooshi/php-coveralls": "^1.0",
+        "phpunit/phpunit": "~4.8.35",
         "mikey179/vfsStream": "~1.6",
         "friendsofphp/php-cs-fixer": "^2.2"
     },
diff --git a/composer.lock b/composer.lock
index c0044bcb81c14687ff74413fecd8402d203e4cd1..d889ae0365f64648906802db8eb296da1245809b 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "content-hash": "6dd0f53bb5ddf24e08cefb49b9b57330",
+    "content-hash": "b4321ba6aa21f1dfd919f942037f2692",
     "packages": [
         {
             "name": "gettext/gettext",
@@ -845,102 +845,6 @@
             ],
             "time": "2017-08-23T07:39:54+00:00"
         },
-        {
-            "name": "guzzle/guzzle",
-            "version": "v3.9.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/guzzle/guzzle3.git",
-                "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9",
-                "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9",
-                "shasum": ""
-            },
-            "require": {
-                "ext-curl": "*",
-                "php": ">=5.3.3",
-                "symfony/event-dispatcher": "~2.1"
-            },
-            "replace": {
-                "guzzle/batch": "self.version",
-                "guzzle/cache": "self.version",
-                "guzzle/common": "self.version",
-                "guzzle/http": "self.version",
-                "guzzle/inflection": "self.version",
-                "guzzle/iterator": "self.version",
-                "guzzle/log": "self.version",
-                "guzzle/parser": "self.version",
-                "guzzle/plugin": "self.version",
-                "guzzle/plugin-async": "self.version",
-                "guzzle/plugin-backoff": "self.version",
-                "guzzle/plugin-cache": "self.version",
-                "guzzle/plugin-cookie": "self.version",
-                "guzzle/plugin-curlauth": "self.version",
-                "guzzle/plugin-error-response": "self.version",
-                "guzzle/plugin-history": "self.version",
-                "guzzle/plugin-log": "self.version",
-                "guzzle/plugin-md5": "self.version",
-                "guzzle/plugin-mock": "self.version",
-                "guzzle/plugin-oauth": "self.version",
-                "guzzle/service": "self.version",
-                "guzzle/stream": "self.version"
-            },
-            "require-dev": {
-                "doctrine/cache": "~1.3",
-                "monolog/monolog": "~1.0",
-                "phpunit/phpunit": "3.7.*",
-                "psr/log": "~1.0",
-                "symfony/class-loader": "~2.1",
-                "zendframework/zend-cache": "2.*,<2.3",
-                "zendframework/zend-log": "2.*,<2.3"
-            },
-            "suggest": {
-                "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated."
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "3.9-dev"
-                }
-            },
-            "autoload": {
-                "psr-0": {
-                    "Guzzle": "src/",
-                    "Guzzle\\Tests": "tests/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Michael Dowling",
-                    "email": "mtdowling@gmail.com",
-                    "homepage": "https://github.com/mtdowling"
-                },
-                {
-                    "name": "Guzzle Community",
-                    "homepage": "https://github.com/guzzle/guzzle/contributors"
-                }
-            ],
-            "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle",
-            "homepage": "http://guzzlephp.org/",
-            "keywords": [
-                "client",
-                "curl",
-                "framework",
-                "http",
-                "http client",
-                "rest",
-                "web service"
-            ],
-            "abandoned": "guzzlehttp/guzzle",
-            "time": "2015-03-18T18:23:50+00:00"
-        },
         {
             "name": "ircmaxell/password-compat",
             "version": "v1.0.4",
@@ -1662,64 +1566,6 @@
             ],
             "time": "2015-10-02T06:51:40+00:00"
         },
-        {
-            "name": "satooshi/php-coveralls",
-            "version": "v1.0.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/satooshi/php-coveralls.git",
-                "reference": "da51d304fe8622bf9a6da39a8446e7afd432115c"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/satooshi/php-coveralls/zipball/da51d304fe8622bf9a6da39a8446e7afd432115c",
-                "reference": "da51d304fe8622bf9a6da39a8446e7afd432115c",
-                "shasum": ""
-            },
-            "require": {
-                "ext-json": "*",
-                "ext-simplexml": "*",
-                "guzzle/guzzle": "^2.8|^3.0",
-                "php": ">=5.3.3",
-                "psr/log": "^1.0",
-                "symfony/config": "^2.1|^3.0",
-                "symfony/console": "^2.1|^3.0",
-                "symfony/stopwatch": "^2.0|^3.0",
-                "symfony/yaml": "^2.0|^3.0"
-            },
-            "suggest": {
-                "symfony/http-kernel": "Allows Symfony integration"
-            },
-            "bin": [
-                "bin/coveralls"
-            ],
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Satooshi\\": "src/Satooshi/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Kitamura Satoshi",
-                    "email": "with.no.parachute@gmail.com",
-                    "homepage": "https://www.facebook.com/satooshi.jp"
-                }
-            ],
-            "description": "PHP client library for Coveralls API",
-            "homepage": "https://github.com/satooshi/php-coveralls",
-            "keywords": [
-                "ci",
-                "coverage",
-                "github",
-                "test"
-            ],
-            "time": "2016-01-20T17:35:46+00:00"
-        },
         {
             "name": "sebastian/comparator",
             "version": "1.2.4",
@@ -2092,84 +1938,22 @@
             "homepage": "https://github.com/sebastianbergmann/version",
             "time": "2015-06-21T13:59:46+00:00"
         },
-        {
-            "name": "symfony/config",
-            "version": "v3.3.6",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/config.git",
-                "reference": "54ee12b0dd60f294132cabae6f5da9573d2e5297"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/config/zipball/54ee12b0dd60f294132cabae6f5da9573d2e5297",
-                "reference": "54ee12b0dd60f294132cabae6f5da9573d2e5297",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.5.9",
-                "symfony/filesystem": "~2.8|~3.0"
-            },
-            "conflict": {
-                "symfony/dependency-injection": "<3.3",
-                "symfony/finder": "<3.3"
-            },
-            "require-dev": {
-                "symfony/dependency-injection": "~3.3",
-                "symfony/finder": "~3.3",
-                "symfony/yaml": "~3.0"
-            },
-            "suggest": {
-                "symfony/yaml": "To use the yaml reference dumper"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "3.3-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\Config\\": ""
-                },
-                "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 Config Component",
-            "homepage": "https://symfony.com",
-            "time": "2017-07-19T07:37:29+00:00"
-        },
         {
             "name": "symfony/console",
-            "version": "v3.3.6",
+            "version": "v3.3.10",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "b0878233cb5c4391347e5495089c7af11b8e6201"
+                "reference": "116bc56e45a8e5572e51eb43ab58c769a352366c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/b0878233cb5c4391347e5495089c7af11b8e6201",
-                "reference": "b0878233cb5c4391347e5495089c7af11b8e6201",
+                "url": "https://api.github.com/repos/symfony/console/zipball/116bc56e45a8e5572e51eb43ab58c769a352366c",
+                "reference": "116bc56e45a8e5572e51eb43ab58c769a352366c",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9",
+                "php": "^5.5.9|>=7.0.8",
                 "symfony/debug": "~2.8|~3.0",
                 "symfony/polyfill-mbstring": "~1.0"
             },
@@ -2182,7 +1966,6 @@
                 "symfony/dependency-injection": "~3.3",
                 "symfony/event-dispatcher": "~2.8|~3.0",
                 "symfony/filesystem": "~2.8|~3.0",
-                "symfony/http-kernel": "~2.8|~3.0",
                 "symfony/process": "~2.8|~3.0"
             },
             "suggest": {
@@ -2221,24 +2004,24 @@
             ],
             "description": "Symfony Console Component",
             "homepage": "https://symfony.com",
-            "time": "2017-07-29T21:27:59+00:00"
+            "time": "2017-10-02T06:42:24+00:00"
         },
         {
             "name": "symfony/debug",
-            "version": "v3.3.6",
+            "version": "v3.3.10",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/debug.git",
-                "reference": "7c13ae8ce1e2adbbd574fc39de7be498e1284e13"
+                "reference": "eb95d9ce8f18dcc1b3dfff00cb624c402be78ffd"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/debug/zipball/7c13ae8ce1e2adbbd574fc39de7be498e1284e13",
-                "reference": "7c13ae8ce1e2adbbd574fc39de7be498e1284e13",
+                "url": "https://api.github.com/repos/symfony/debug/zipball/eb95d9ce8f18dcc1b3dfff00cb624c402be78ffd",
+                "reference": "eb95d9ce8f18dcc1b3dfff00cb624c402be78ffd",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9",
+                "php": "^5.5.9|>=7.0.8",
                 "psr/log": "~1.0"
             },
             "conflict": {
@@ -2277,31 +2060,34 @@
             ],
             "description": "Symfony Debug Component",
             "homepage": "https://symfony.com",
-            "time": "2017-07-28T15:27:31+00:00"
+            "time": "2017-10-02T06:42:24+00:00"
         },
         {
             "name": "symfony/event-dispatcher",
-            "version": "v2.8.26",
+            "version": "v3.3.10",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/event-dispatcher.git",
-                "reference": "1377400fd641d7d1935981546aaef780ecd5bf6d"
+                "reference": "d7ba037e4b8221956ab1e221c73c9e27e05dd423"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1377400fd641d7d1935981546aaef780ecd5bf6d",
-                "reference": "1377400fd641d7d1935981546aaef780ecd5bf6d",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d7ba037e4b8221956ab1e221c73c9e27e05dd423",
+                "reference": "d7ba037e4b8221956ab1e221c73c9e27e05dd423",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.9"
+                "php": "^5.5.9|>=7.0.8"
+            },
+            "conflict": {
+                "symfony/dependency-injection": "<3.3"
             },
             "require-dev": {
                 "psr/log": "~1.0",
-                "symfony/config": "^2.0.5|~3.0.0",
-                "symfony/dependency-injection": "~2.6|~3.0.0",
-                "symfony/expression-language": "~2.6|~3.0.0",
-                "symfony/stopwatch": "~2.3|~3.0.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": "",
@@ -2310,7 +2096,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.8-dev"
+                    "dev-master": "3.3-dev"
                 }
             },
             "autoload": {
@@ -2337,24 +2123,24 @@
             ],
             "description": "Symfony EventDispatcher Component",
             "homepage": "https://symfony.com",
-            "time": "2017-06-02T07:47:27+00:00"
+            "time": "2017-10-02T06:42:24+00:00"
         },
         {
             "name": "symfony/filesystem",
-            "version": "v3.3.6",
+            "version": "v3.3.10",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "427987eb4eed764c3b6e38d52a0f87989e010676"
+                "reference": "90bc45abf02ae6b7deb43895c1052cb0038506f1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/427987eb4eed764c3b6e38d52a0f87989e010676",
-                "reference": "427987eb4eed764c3b6e38d52a0f87989e010676",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/90bc45abf02ae6b7deb43895c1052cb0038506f1",
+                "reference": "90bc45abf02ae6b7deb43895c1052cb0038506f1",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9"
+                "php": "^5.5.9|>=7.0.8"
             },
             "type": "library",
             "extra": {
@@ -2386,7 +2172,7 @@
             ],
             "description": "Symfony Filesystem Component",
             "homepage": "https://symfony.com",
-            "time": "2017-07-11T07:17:58+00:00"
+            "time": "2017-10-03T13:33:10+00:00"
         },
         {
             "name": "symfony/finder",
@@ -2493,16 +2279,16 @@
         },
         {
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.4.0",
+            "version": "v1.6.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "f29dca382a6485c3cbe6379f0c61230167681937"
+                "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937",
-                "reference": "f29dca382a6485c3cbe6379f0c61230167681937",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296",
+                "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296",
                 "shasum": ""
             },
             "require": {
@@ -2514,7 +2300,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.4-dev"
+                    "dev-master": "1.6-dev"
                 }
             },
             "autoload": {
@@ -2548,7 +2334,7 @@
                 "portable",
                 "shim"
             ],
-            "time": "2017-06-09T14:24:12+00:00"
+            "time": "2017-10-11T12:05:26+00:00"
         },
         {
             "name": "symfony/polyfill-php54",
@@ -2829,20 +2615,20 @@
         },
         {
             "name": "symfony/stopwatch",
-            "version": "v3.3.6",
+            "version": "v3.3.10",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/stopwatch.git",
-                "reference": "602a15299dc01556013b07167d4f5d3a60e90d15"
+                "reference": "170edf8b3247d7b6779eb6fa7428f342702ca184"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/stopwatch/zipball/602a15299dc01556013b07167d4f5d3a60e90d15",
-                "reference": "602a15299dc01556013b07167d4f5d3a60e90d15",
+                "url": "https://api.github.com/repos/symfony/stopwatch/zipball/170edf8b3247d7b6779eb6fa7428f342702ca184",
+                "reference": "170edf8b3247d7b6779eb6fa7428f342702ca184",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9"
+                "php": "^5.5.9|>=7.0.8"
             },
             "type": "library",
             "extra": {
@@ -2874,24 +2660,24 @@
             ],
             "description": "Symfony Stopwatch Component",
             "homepage": "https://symfony.com",
-            "time": "2017-04-12T14:14:56+00:00"
+            "time": "2017-10-02T06:42:24+00:00"
         },
         {
             "name": "symfony/yaml",
-            "version": "v3.3.6",
+            "version": "v3.3.10",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/yaml.git",
-                "reference": "ddc23324e6cfe066f3dd34a37ff494fa80b617ed"
+                "reference": "8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/yaml/zipball/ddc23324e6cfe066f3dd34a37ff494fa80b617ed",
-                "reference": "ddc23324e6cfe066f3dd34a37ff494fa80b617ed",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46",
+                "reference": "8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9"
+                "php": "^5.5.9|>=7.0.8"
             },
             "require-dev": {
                 "symfony/console": "~2.8|~3.0"
@@ -2929,7 +2715,7 @@
             ],
             "description": "Symfony Yaml Component",
             "homepage": "https://symfony.com",
-            "time": "2017-07-23T12:43:26+00:00"
+            "time": "2017-10-05T14:43:42+00:00"
         },
         {
             "name": "webmozart/assert",
diff --git a/config-templates/config.php b/config-templates/config.php
index bc94a9d504685a10f60f6c585e78f95340919e94..ec3f97d3fe870db10189e8623af2da76679b2b32 100644
--- a/config-templates/config.php
+++ b/config-templates/config.php
@@ -965,7 +965,7 @@ $config = array(
      * - 'validateFingerprint': The fingerprint of the certificate used to sign the metadata. You don't need this
      *                          option if you don't want to validate the signature on the metadata. Optional.
      * - 'cachedir': Directory where metadata can be cached. Optional.
-     * - 'cachelength': Maximum time metadata cah be cached, in seconds. Default to 24
+     * - 'cachelength': Maximum time metadata can be cached, in seconds. Defaults to 24
      *                  hours (86400 seconds). Optional.
      *
      * PDO metadata handler:
@@ -1038,7 +1038,7 @@ $config = array(
     'metadata.sign.privatekey' => null,
     'metadata.sign.privatekey_pass' => null,
     'metadata.sign.certificate' => null,
-
+    'metadata.sign.algorithm' => null,
 
 
     /****************************
diff --git a/docs/simplesamlphp-changelog.md b/docs/simplesamlphp-changelog.md
index fa5a4b3d15212d5fafee1bb55eb561b74f379d51..9c4184c6e5ed668fd16cadd947d361ca157b13be 100644
--- a/docs/simplesamlphp-changelog.md
+++ b/docs/simplesamlphp-changelog.md
@@ -8,7 +8,7 @@ See the upgrade notes for specific information about upgrading.
 
 ## Version 1.15.0
 
-Released TBD
+Released 2017-11-20
 
 ### New features
   * Added support for authenticated web proxies with the `proxy.auth` setting.
@@ -46,6 +46,8 @@ Released TBD
   * Make redirections more resilient.
   * Fixed empty protocolSupportEnumeration in AttributeAuthorityDescriptor.
   * Other bug fixes and numerous documentation enhancements.
+  * Fixed a bug in the Redis store that could lead to incorrect
+    _duplicate assertion_ errors.
 
 ### API and user interface
   * Updated to Xmlseclibs 3.0.
diff --git a/lib/SimpleSAML/Module.php b/lib/SimpleSAML/Module.php
index 0492b0aaee5d4a3bc7303aadfd9eb59277e83cb0..6c9b3c6e0e9f7052f1ce9ea2105ee7fa2e5364fb 100644
--- a/lib/SimpleSAML/Module.php
+++ b/lib/SimpleSAML/Module.php
@@ -27,89 +27,6 @@ class Module
     public static $module_info = array();
 
 
-    /**
-     * Autoload function for SimpleSAMLphp modules following PSR-0.
-     *
-     * @param string $className Name of the class.
-     *
-     * @deprecated This method will be removed in SSP 2.0.
-     *
-     * TODO: this autoloader should be removed once everything has been migrated to namespaces.
-     */
-    public static function autoloadPSR0($className)
-    {
-        $modulePrefixLength = strlen('sspmod_');
-        $classPrefix = substr($className, 0, $modulePrefixLength);
-        if ($classPrefix !== 'sspmod_') {
-            return;
-        }
-
-        $modNameEnd = strpos($className, '_', $modulePrefixLength);
-        $module = substr($className, $modulePrefixLength, $modNameEnd - $modulePrefixLength);
-        $path = explode('_', substr($className, $modNameEnd + 1));
-
-        if (!self::isModuleEnabled($module)) {
-            return;
-        }
-
-        $file = self::getModuleDir($module).'/lib/'.join('/', $path).'.php';
-        if (!file_exists($file)) {
-            return;
-        }
-        require_once($file);
-
-        if (!class_exists($className, false) && !interface_exists($className, false)) {
-            // the file exists, but the class is not defined. Is it using namespaces?
-            $nspath = join('\\', $path);
-            if (class_exists('SimpleSAML\Module\\'.$module.'\\'.$nspath) ||
-                interface_exists('SimpleSAML\Module\\'.$module.'\\'.$nspath)
-            ) {
-                // the class has been migrated, create an alias and warn about it
-                \SimpleSAML\Logger::warning(
-                    "The class or interface '$className' is now using namespaces, please use 'SimpleSAML\\Module\\".
-                    $module."\\".$nspath."' instead."
-                );
-                class_alias("SimpleSAML\\Module\\$module\\$nspath", $className);
-            }
-        }
-    }
-
-
-    /**
-     * Autoload function for SimpleSAMLphp modules following PSR-4.
-     *
-     * @param string $className Name of the class.
-     */
-    public static function autoloadPSR4($className)
-    {
-        $elements = explode('\\', $className);
-        if ($elements[0] === '') { // class name starting with /, ignore
-            array_shift($elements);
-        }
-        if (count($elements) < 4) {
-            return; // it can't be a module
-        }
-        if (array_shift($elements) !== 'SimpleSAML') {
-            return; // the first element is not "SimpleSAML"
-        }
-        if (array_shift($elements) !== 'Module') {
-            return; // the second element is not "module"
-        }
-
-        // this is a SimpleSAMLphp module following PSR-4
-        $module = array_shift($elements);
-        if (!self::isModuleEnabled($module)) {
-            return; // module not enabled, avoid giving out any information at all
-        }
-
-        $file = self::getModuleDir($module).'/lib/'.implode('/', $elements).'.php';
-
-        if (file_exists($file)) {
-            require_once($file);
-        }
-    }
-
-
     /**
      * Retrieve the base directory for a module.
      *
diff --git a/lib/SimpleSAML/Session.php b/lib/SimpleSAML/Session.php
index b9fa3f9d32e3b9ff5debd065bbbd8a825da84715..6eb306cf8f9d4571f3c702239b907dd3eda09415 100644
--- a/lib/SimpleSAML/Session.php
+++ b/lib/SimpleSAML/Session.php
@@ -482,11 +482,6 @@ class SimpleSAML_Session implements Serializable
 
         $this->dirty = true;
 
-        if (!function_exists('header_register_callback')) {
-            // PHP version < 5.4, can't register the callback
-            return;
-        }
-
         if ($this->callback_registered) {
             // we already have a shutdown callback registered for this object, no need to add another one
             return;
diff --git a/lib/SimpleSAML/SessionHandlerPHP.php b/lib/SimpleSAML/SessionHandlerPHP.php
index 206e3ce72af51f405ebebe58eecb6afe3c55c9e8..067c384d050d683bea83ace4144558eedd76f4ba 100644
--- a/lib/SimpleSAML/SessionHandlerPHP.php
+++ b/lib/SimpleSAML/SessionHandlerPHP.php
@@ -49,13 +49,7 @@ class SessionHandlerPHP extends SessionHandler
         $config = \SimpleSAML_Configuration::getInstance();
         $this->cookie_name = $config->getString('session.phpsession.cookiename', null);
 
-        if (function_exists('session_status') && defined('PHP_SESSION_ACTIVE')) { // PHP >= 5.4
-            $previous_session = session_status() === PHP_SESSION_ACTIVE;
-        } else {
-            $previous_session = (session_id() !== '') && (session_name() !== $this->cookie_name);
-        }
-
-        if ($previous_session) {
+        if (session_status() === PHP_SESSION_ACTIVE) {
             if (session_name() === $this->cookie_name || $this->cookie_name === null) {
                 Logger::warning(
                     'There is already a PHP session with the same name as SimpleSAMLphp\'s session, or the '.
diff --git a/lib/SimpleSAML/Utils/Config/Metadata.php b/lib/SimpleSAML/Utils/Config/Metadata.php
index caf7b4a8775b5e571b6f83453cebad952883aec5..632ec04cca8156bb31e1c7ee0ea452a48466d30e 100644
--- a/lib/SimpleSAML/Utils/Config/Metadata.php
+++ b/lib/SimpleSAML/Utils/Config/Metadata.php
@@ -230,7 +230,7 @@ class Metadata
                 continue;
             }
 
-            if (array_key_exists('isDefault', $ep)) {
+            if (isset($ep['isDefault'])) {
                 if ($ep['isDefault'] === true) {
                     // this is the first endpoint with isDefault set to true
                     return $ep;
diff --git a/lib/_autoload_modules.php b/lib/_autoload_modules.php
index 1cbe074594535c02be2ea85e7a9520a5ddb16446..9a2c753f76708e2d6bc037232996689fdff86acb 100644
--- a/lib/_autoload_modules.php
+++ b/lib/_autoload_modules.php
@@ -69,6 +69,89 @@ function temporaryLoader($class)
     }
 }
 
+
+/**
+ * Autoload function for SimpleSAMLphp modules following PSR-0.
+ *
+ * @param string $className Name of the class.
+ *
+ * @deprecated This method will be removed in SSP 2.0.
+ *
+ * TODO: this autoloader should be removed once everything has been migrated to namespaces.
+ */
+function sspmodAutoloadPSR0($className)
+{
+    $modulePrefixLength = strlen('sspmod_');
+    $classPrefix = substr($className, 0, $modulePrefixLength);
+    if ($classPrefix !== 'sspmod_') {
+        return;
+    }
+
+    $modNameEnd = strpos($className, '_', $modulePrefixLength);
+    $module = substr($className, $modulePrefixLength, $modNameEnd - $modulePrefixLength);
+    $path = explode('_', substr($className, $modNameEnd + 1));
+
+    if (!\SimpleSAML\Module::isModuleEnabled($module)) {
+        return;
+    }
+
+    $file = \SimpleSAML\Module::getModuleDir($module).'/lib/'.join('/', $path).'.php';
+    if (!file_exists($file)) {
+        return;
+    }
+    require_once($file);
+
+    if (!class_exists($className, false) && !interface_exists($className, false)) {
+        // the file exists, but the class is not defined. Is it using namespaces?
+        $nspath = join('\\', $path);
+        if (class_exists('SimpleSAML\Module\\'.$module.'\\'.$nspath) ||
+            interface_exists('SimpleSAML\Module\\'.$module.'\\'.$nspath)
+        ) {
+            // the class has been migrated, create an alias and warn about it
+            \SimpleSAML\Logger::warning(
+                "The class or interface '$className' is now using namespaces, please use 'SimpleSAML\\Module\\".
+                $module."\\".$nspath."' instead."
+            );
+            class_alias("SimpleSAML\\Module\\$module\\$nspath", $className);
+        }
+    }
+}
+
+
+/**
+ * Autoload function for SimpleSAMLphp modules following PSR-4.
+ *
+ * @param string $className Name of the class.
+ */
+function sspmodAutoloadPSR4($className)
+{
+    $elements = explode('\\', $className);
+    if ($elements[0] === '') { // class name starting with /, ignore
+        array_shift($elements);
+    }
+    if (count($elements) < 4) {
+        return; // it can't be a module
+    }
+    if (array_shift($elements) !== 'SimpleSAML') {
+        return; // the first element is not "SimpleSAML"
+    }
+    if (array_shift($elements) !== 'Module') {
+        return; // the second element is not "module"
+    }
+
+    // this is a SimpleSAMLphp module following PSR-4
+    $module = array_shift($elements);
+    if (!\SimpleSAML\Module::isModuleEnabled($module)) {
+        return; // module not enabled, avoid giving out any information at all
+    }
+
+    $file = \SimpleSAML\Module::getModuleDir($module).'/lib/'.implode('/', $elements).'.php';
+
+    if (file_exists($file)) {
+        require_once($file);
+    }
+}
+
 spl_autoload_register("temporaryLoader");
-spl_autoload_register(array('SimpleSAML\Module', 'autoloadPSR0'));
-spl_autoload_register(array('SimpleSAML\Module', 'autoloadPSR4'));
+spl_autoload_register('sspmodAutoloadPSR0');
+spl_autoload_register('sspmodAutoloadPSR4');
diff --git a/modules/authX509/lib/Auth/Source/X509userCert.php b/modules/authX509/lib/Auth/Source/X509userCert.php
index 6e00188c7049b9f395e924f3c273b1ece18ed6a2..0cf9083d051c31ab832e0a691f2d1dc5f174bd44 100644
--- a/modules/authX509/lib/Auth/Source/X509userCert.php
+++ b/modules/authX509/lib/Auth/Source/X509userCert.php
@@ -74,8 +74,7 @@ class sspmod_authX509_Auth_Source_X509userCert extends SimpleSAML_Auth_Source
         $t = new SimpleSAML_XHTML_Template($config, 'authX509:X509error.php');
         $t->data['loginurl'] = SimpleSAML\Utils\HTTP::getSelfURL();
         $t->data['errorcode'] = $state['authX509.error'];
-        $t->data['errortitle'] = $errorcodes['title'][$state['authX509.error']];
-        $t->data['errordescr'] = $errorcodes['descr'][$state['authX509.error']];
+        $t->data['errorcodes'] = SimpleSAML\Error\ErrorCodes::getAllErrorCodeMessages();
         $t->show();
         exit();
     }
diff --git a/modules/cron/templates/croninfo.twig b/modules/cron/templates/croninfo.twig
index b72688e147d8a9e3494d41d444882d19bf8e7ff6..62c76ebc6816f44f097e252b057431dd63c3afe3 100644
--- a/modules/cron/templates/croninfo.twig
+++ b/modules/cron/templates/croninfo.twig
@@ -4,7 +4,7 @@
 {% block content %}
     <h2>{{ 'Cron result page'|trans }}</h2>
     <p>{{ 'Cron is a way to run things regularly on unix systems.'|trans }}<br /><br /></p>
-    <p>{{ 'Here is a suggestion for a crontab file:'|trans }}<br /><br />
+    <p>{{ 'Here is a suggestion for a crontab file:'|trans }}<br /><br /></p>
 
     <div class="metadatabox metadata-code">
         <code>
@@ -13,7 +13,6 @@
                 {{ url.int }} curl --silent "{{ url.href }}" > /dev/null 2>&1<br />
         {% endfor %}
     </code></div><br />
-    </p>
 
     <p>{{ 'Click here to run the cron jobs:'|trans }}</p>
       <ul>
diff --git a/modules/ldap/docs/ldap.md b/modules/ldap/docs/ldap.md
index 098e4042f5cb925551d4edec4b58312de2459b92..fae1ca1c6ae30d51f946d48a3a8ba4088b38c510 100644
--- a/modules/ldap/docs/ldap.md
+++ b/modules/ldap/docs/ldap.md
@@ -562,13 +562,14 @@ required, see the config info above for details.
 	)
 
 Example for unsupported OpenLDAP usage. 
-Intention is to filter in 'ou=groups,dc=example,dc=com' for 
-'(memberUid = <UID>)' and take only the attributes 'cn' (=name of the group).
+Intention is to filter in `ou=groups,dc=example,dc=com` for
+`(memberUid = <UID>)` and take only the attribute `cn` (=name of the group).
 
     50 => array(
         'class' => 'ldap:AttributeAddUsersGroups',
         'ldap.product' => 'OpenLDAP',
         'ldap.basedn' => 'ou=groups,dc=example,dc=org',
+        'attribute.username' => 'uid',
         'attribute.member' => 'cn',
         'attribute.memberof' => 'memberUid',
     ),
diff --git a/modules/ldap/lib/Auth/Process/AttributeAddUsersGroups.php b/modules/ldap/lib/Auth/Process/AttributeAddUsersGroups.php
index 50d92381cc92644adac07991f300958b33d318b3..ab42a2ba360e56925cf519a9d63d3eb594599afc 100644
--- a/modules/ldap/lib/Auth/Process/AttributeAddUsersGroups.php
+++ b/modules/ldap/lib/Auth/Process/AttributeAddUsersGroups.php
@@ -122,12 +122,12 @@ class sspmod_ldap_Auth_Process_AttributeAddUsersGroups extends sspmod_ldap_Auth_
                 // Print group search string and search for all group names
                 $openldap_base = $this->config->getString('ldap.basedn','ou=groups,dc=example,dc=com');
                 SimpleSAML\Logger::debug(
-                    $this->title . "Searching for groups in ldap.basedn ".$openldap_base." with filter (".$map['memberof']."=".$attributes['uid'][0].") and attributes ".$map['member']
+                    $this->title . "Searching for groups in ldap.basedn ".$openldap_base." with filter (".$map['memberof']."=".$attributes[$map['username']][0].") and attributes ".$map['member']
                 );
                 $groups = array();
                 try {
-                    // Intention is to filter in 'ou=groups,dc=example,dc=com' for '(memberUid = <UID>)' and take only the attributes 'cn' (=name of the group)
-                    $all_groups = $this->getLdap()->searchformultiple( $openldap_base, array($map['memberof'] => $attributes['uid'][0]) , array($map['member']));
+                    // Intention is to filter in 'ou=groups,dc=example,dc=com' for '(memberUid = <value of attribute.username>)' and take only the attributes 'cn' (=name of the group)
+                    $all_groups = $this->getLdap()->searchformultiple( $openldap_base, array($map['memberof'] => $attributes[$map['username']][0]) , array($map['member']));
                 } catch (SimpleSAML_Error_UserNotFound $e) {
                     break; // if no groups found return with empty (still just initialized) groups array
                 }
diff --git a/modules/metarefresh/locales/en/LC_MESSAGES/metarefresh.po b/modules/metarefresh/locales/en/LC_MESSAGES/metarefresh.po
new file mode 100644
index 0000000000000000000000000000000000000000..483051bab4f733daa6bc6892c06e9cc43b5a73b4
--- /dev/null
+++ b/modules/metarefresh/locales/en/LC_MESSAGES/metarefresh.po
@@ -0,0 +1,25 @@
+
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: SimpleSAMLphp 1.15\n"
+"Report-Msgid-Bugs-To: simplesamlphp-translation@googlegroups.com\n"
+"POT-Creation-Date: 2017-12-06 09:23+0200\n"
+"PO-Revision-Date: 2017-12-06 12:14+0200\n"
+"Last-Translator: \n"
+"Language: en\n"
+"Language-Team: \n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.3.4\n"
+
+msgid "metarefresh_header"
+msgstr "Metarefresh"
+
+msgid "metarefresh_no_output"
+msgstr "No output from metarefresh."
+
+msgid "metarefresh_fetched"
+msgstr "Fetched metadata"
diff --git a/modules/metarefresh/templates/fetch.twig b/modules/metarefresh/templates/fetch.twig
new file mode 100644
index 0000000000000000000000000000000000000000..d8765ab3dd1e345a453196ac5d5350e870affb6c
--- /dev/null
+++ b/modules/metarefresh/templates/fetch.twig
@@ -0,0 +1,18 @@
+{% set pagetitle = "metarefresh_header" | trans %}
+
+{% extends "base.twig" %}
+
+{% block content %}
+
+{% if logentries %}
+        <h1>{{ "metarefresh_fetched" | trans }}</h1>
+        <ul>
+            {% for logentry in logentries %}
+            <li>{{ logentry }}</li>
+            {% endfor %}
+        </ul>
+{% else %}
+{{ "metarefresh_no_output" | trans }}
+{% endif %}
+
+{% endblock content %}
diff --git a/modules/sqlauth/default-enable b/modules/sqlauth/default-disable
similarity index 59%
rename from modules/sqlauth/default-enable
rename to modules/sqlauth/default-disable
index 25615cb47c350d23033eb9801627ed8330bcc3e9..fa0bd82e2df7bd79d57593d35bc53c1f9d3ef71f 100644
--- a/modules/sqlauth/default-enable
+++ b/modules/sqlauth/default-disable
@@ -1,3 +1,3 @@
 This file indicates that the default state of this module
-is enabled. To disable, create a file named disable in the
+is disabled. To enable, create a file named enable in the
 same directory as this file.
diff --git a/modules/statistics/lib/Graph/GoogleCharts.php b/modules/statistics/lib/Graph/GoogleCharts.php
index d86ccc788d4384875966cf590e737c1641a807aa..9a1126188387d359513bcef798b0011ebc5c8057 100644
--- a/modules/statistics/lib/Graph/GoogleCharts.php
+++ b/modules/statistics/lib/Graph/GoogleCharts.php
@@ -96,7 +96,7 @@ class sspmod_statistics_Graph_GoogleCharts
             $labeld = '&chxt=x,y,r' . '&chxr=0,0,1|1,0,' . $maxes[0] . '|2,0,' . $maxes[1];
         }
 
-        $url = 'http://chart.apis.google.com/chart?' .
+        $url = 'https://chart.apis.google.com/chart?' .
             // Dimension of graph. Default is 800x350
             'chs=' . $this->x . 'x' . $this->y . 
 
@@ -119,7 +119,7 @@ class sspmod_statistics_Graph_GoogleCharts
 
     public function showPie($axis, $datasets)
     {
-        $url = 'http://chart.apis.google.com/chart?' .
+        $url = 'https://chart.apis.google.com/chart?' .
 
         // Dimension of graph. Default is 800x350
         'chs=' . $this->x . 'x' . $this->y . 
diff --git a/modules/statistics/templates/statistics.twig b/modules/statistics/templates/statistics.twig
new file mode 100644
index 0000000000000000000000000000000000000000..7e5d1510fd19f8aa7bbe5279f282c6f77938600b
--- /dev/null
+++ b/modules/statistics/templates/statistics.twig
@@ -0,0 +1,212 @@
+{% set pagetitle = 'SimpleSAMLphp Statistics'|trans %}
+{% extends "base.twig" %}
+
+{% block preload %}
+    <link href="{{ baseurlpath }}style.css" rel="stylesheet" />
+{% endblock %}
+{% block postload %}
+    <script src="{{ baseurlpath }}javascript.js"></script>
+{% endblock %}
+
+{% block content %}
+    <h1>{{ current_rule.name }}</h1>
+    <p>{{ current_rule.descr }}</p>
+
+    <table class="selecttime">
+        <tr>
+            <td class="selecttime_icon">
+                <img src="/{{ baseurlpath }}resources/icons/crystal_project/kchart.32x32.png" alt="Report settings" />
+            </td>
+            <td>
+                <form action="#">
+                    {% for key, value in post_rule %}
+                    <input type="hidden" name="{{ key|escape('html') }}" value="{{ value|escape('html') }}">
+                    {% endfor %}
+                    <select onchange="submit();" name="rule">
+                    {% for key, rule in available_rules %}
+                        {% if key == selected_rule %}
+                        <option selected="selected" value="{{ key }}">{{ rule.name }}</option>
+                        {% else %}
+                        <option value="{{ key }}">{{ rule.name }}</option>
+                        {% endif %}
+                    {% endfor %}
+                    </select>
+                </form>
+            </td>
+            <td class="td_right">
+                <form action="#">
+                    {% for key, value in post_d %}
+                    <input type="hidden" name="{{ key|escape('html') }}" value="{{ value|escape('html') }}">
+                    {% endfor %}
+                    <select onchange="submit();" name="d">
+                    {% for key, delim in availdelimiters %}
+                        {% set delimName = delim %}
+
+                        {% if delimiterPresentation[delim] is defined %}
+                        {% set delimName = delimiterPresentation[delim] %}
+                        {% endif %}
+
+                        {% if key == "_" %}
+                        <option value="_">Total</option>
+                        {% elseif request_d is defined and delim == request_d %}
+                        <option selected="selected" value="{{ delim|escape('html') }}">{{ delimName|escape('html') }}</option>
+                        {% else %}
+                        <option value="{{ delim|escape('html') }}">{{ delimName|escape('html') }}</option>
+                        {% endif %}
+                    {% endfor %}
+                    </select>
+                </form>
+            </td>
+        </tr>
+    </table>
+
+    <table class="selecttime">
+        <tr>
+            <td class="selecttime_icon">
+                <img src="/{{ baseurlpath }}resources/icons/crystal_project/date.32x32.png" alt="Select date and time" />
+            </td>
+            {% if available_times_prev %}
+            <td><a href="{{ get_times_prev }}">&laquo; Previous</a></td>
+            {% else %}
+            <td class="selecttime_link_grey">&laquo; Previous</td>
+            {% endif %}
+            <td class="td_right">
+                <form action="#">
+                    {% for key, value in post_res %}
+                    <input type="hidden" name="{{ key|escape('html') }}" value="{{ value|escape('html') }}">
+                    {% endfor %}
+                    <select onchange="submit();" name="res">
+                    {% for key, timeresname in available_timeres %}
+                        {% if key == selected_timeres %}
+                        <option selected="selected" value="{{ key }}">{{ timeresname }}</option>
+                        {% else %}
+                        <option value="{{ key }}">{{ timeresname }}</option>
+                        {% endif %}
+                    {% endfor %}
+                    </select>
+                </form>
+            </td>
+            <td class="td_left">
+                <form action="#">
+                    {% for key, value in post_time %}
+                    <input type="hidden" name="{{ key|escape('html') }}" value="{{ value|escape('html') }}">
+                    {% endfor %}
+                    <select onchange="submit();" name=time>
+                    {% for key, timedescr in available_times %}
+                        {% if key == selected_time %}
+                        <option selected="selected" value="{{ key }}">{{ timedescr }}</option>
+                        {% else %}
+                        <option value="{{ key }}">{{ timedescr }}</option>
+                        {% endif%}
+                    {% endfor %}
+                    </select>
+                </form>
+            </td>
+            {% if available_times_next %}
+                <td class="td_right td_next_right"><a href="{{ get_times_next }}">Next &raquo;</a></td>
+            {% else %}
+                <td class="td_right selecttime_link_grey">Next &raquo;</td>
+            {% endif %}
+        </tr>
+    </table>
+
+    <div id="tabdiv">
+        <ul class="tabset_tabs">
+            <li class="tab-link current" data-tab="graph"><a href="#graph">Graph</a></li>
+            <li class="tab-link" data-tab="table"><a href="#table">Summary table</a></li>
+            <li class="tab-link" data-tab="debug"><a href="#debug">Time serie</a></li>
+        </ul>
+
+        <div id="graph" class="tabset_content current">
+            <img src="{{ imgurl }}" alt="Graph" />
+            <form action="#">
+                <p class="p_right">Compare with total from this dataset
+                <select onchange="submit();" name="rule2">
+                    <option value="_">None</option>
+                    {% for key, rule in available_rules %}
+                    {% if key == selected_rule2 %}
+                    <option selected="selected" value="{{ key }}">{{ rule.name }}</option>
+                    {% else %}
+                    <option value="{{ key }}">{{ rule.name }}</option>
+                    {% endif %}
+                    {% endfor %}
+                </select>
+                </p>
+            </form>
+        </div>
+
+        <div id="table" class="tabset_content">
+            {% if pieimgurl is defined %}
+            <img src="{{ pieimgurl }}" alt="Pie chart" />
+            {% endif %}
+
+            <table class="tableview">
+                <tr>
+                    <th class="value">Value</th>
+                    <th class="category">Data range</th>
+                </tr>
+                {% for key, value in summaryDataset %}
+                {% if loop.index0  % 2 == 0 %}
+                    {% set class = 'even' %}
+                {% else %}
+                    {% set class = 'odd' %}
+                {% endif %}
+
+                {% set keyName = key %}
+                {% if delimiterPresentation[key] is defined %}
+                {% set keyName = delimiterPresentation[key] %}
+                {% endif %}
+
+                {% if key == "_" %}
+                <tr class="total {{ class }}">
+                    <td class="value">{{ value }}</td>
+                    <td class="category">{{ keyName }}</td>
+                </tr>
+                {% else %}
+                <tr class="{{ class }}">
+                    <td class="value">{{ value }}</td>
+                    <td class="category">{{ keyName }}</td>
+                </tr>
+                {% endif %}
+                {% endfor %}
+            </table>
+        </div>
+
+        <div id="debug" class="tabset_content">
+            <table class="timeseries">
+                <tr>
+                    <th>Time</th>
+                    <th>Total</th>
+                    {% for key, value in topdelimiters %}
+                    {% set keyName = key %}
+                    {% if delimiterPresentation[key] is defined %}
+                        {% set keyName = delimiterPresentation[key] %}
+                    {% endif %}
+                    <th>{{ keyName }}</th>
+                    {% endfor %}
+                </tr>
+                {% set i = 0 %}
+                {% for slot, dd in debugdata %}
+
+                {% if i % 2 == 0 %}
+                    {% set class = 'even' %}
+                {% else %}
+                    {% set class = 'odd' %}
+                {% endif %}
+
+                <tr class="{{ class }}">
+                    <td>{{ dd[0] }}</td>
+                    <td class="datacontent">{{ dd[1] }}</td>
+                    {% for key, value in topdelimiters %}
+                    {% if results.slot is defined %}
+                    <td class="datacontent">{{ results.slot.key }}</td>
+                    {% else %}
+                    <td class="datacontent">&nbsp;</td>
+                    {% endif %}
+                    {% endfor %}
+                </tr>
+                {% endfor %}
+            </table>
+        </div>
+    </div>
+{% endblock %}
diff --git a/modules/statistics/templates/statmeta.twig b/modules/statistics/templates/statmeta.twig
new file mode 100644
index 0000000000000000000000000000000000000000..dd2c475c552376eb815c69729eef252f222ab9b4
--- /dev/null
+++ b/modules/statistics/templates/statmeta.twig
@@ -0,0 +1,39 @@
+{% set pagetitle = 'SimpleSAMLphp Statistics Metadata'|trans %}
+{% extends "base.twig" %}
+
+{% block preload %}
+    <link href="{{ baseurlpath }}style.css" rel="stylesheet" />
+{% endblock %}
+
+{% block content %}
+    <table id="statmeta">
+    {% if metadata is defined %}
+        {% if metadata.lastrun is defined %}
+            <tr><td>Aggregator last run at</td><td>{{ metadata.lastrun }}</td></tr>
+        {% endif %}
+
+        {% if metadata.notBefore is defined %}
+            <tr><td>Aggregated data until</td><td>{{ metadata.notBefore }}</td></tr>
+        {% endif %}
+
+        {% if metadata.memory is defined %}
+            <tr><td>Memory usage</td><td>{{ metadata.memory }} MB</td></tr>
+        {% endif %}
+
+        {% if metadata.memory is defined %}
+            <tr><td>Execution time</td><td>{{ metadata.time }} seconds</td></tr>
+        {% endif %}
+
+        {% if metadata.memory is defined %}
+            <tr><td>SHA1 of last processed logline</td><td>{{ metadata.lastlinehash }}</td></tr>
+        {% endif %}
+
+        {% if metadata.memory is defined %}
+            <tr><td>Last processed logline</td><td>{{ metadata.lastline }}</td></tr>
+        {% endif %}
+    {% else %}
+        <tr><td>No metadata found</td></tr>
+    {% endif %}
+    </table>
+    <p>[ <a href="{{ baseurlpath }}showstats.php">Show statistics</a> ]</p>
+{% endblock %}
diff --git a/modules/statistics/www/javascript.js b/modules/statistics/www/javascript.js
index a5aa421a67fffda6c3c2f4527c8227e58b1b9cde..77ef1b1a5e53c34054bdc7593183b7aa7e84f48c 100644
--- a/modules/statistics/www/javascript.js
+++ b/modules/statistics/www/javascript.js
@@ -1,4 +1,13 @@
 $(document).ready(function() {
-        $("#tabdiv").tabs();
-});
+    $('ul.tabset_tabs li').click(
+        function() {
+            var tab_id = $(this).attr('data-tab');
+            $('ul.tabset_tabs li').removeClass('current');
+            $('.tabset_content').removeClass('current');
 
+            $(this).addClass('current');
+            $("#"+tab_id).addClass('current');
+            $("html, body").animate({ scrollTop: 0 }, "slow");
+        }
+    )
+})
diff --git a/modules/statistics/www/showstats.php b/modules/statistics/www/showstats.php
index 051dc9aa55440fd3cd87059ce0a15fe6ab1dc302..1828a10726842cccc6a4cd34941abceb1bbf4073 100644
--- a/modules/statistics/www/showstats.php
+++ b/modules/statistics/www/showstats.php
@@ -29,6 +29,7 @@ if (array_key_exists('res', $_REQUEST)) {
 }
 if (array_key_exists('d', $_REQUEST)) {
     $delimiter = $_REQUEST['d'];
+    $t->data['request_d'] = $delimiter;
 }
 
 if ($preferRule2 === '_') {
@@ -100,17 +101,19 @@ $t->data['imgurl'] = $grapher->show($axis['axis'], $axis['axispos'], $datasets,
 if (isset($piedata)) {
     $t->data['pieimgurl'] = $grapher->showPie( $dataset->getDelimiterPresentationPie(), $piedata);
 }
-$t->data['available.rules'] = $ruleset->availableRulesNames();
-$t->data['available.times'] = $statrule->availableFileSlots($timeres);
-$t->data['available.timeres'] = $statrule->availableTimeRes();
-$t->data['available.times.prev'] = $timeNavigation['prev'];
-$t->data['available.times.next'] = $timeNavigation['next'];
-
-$t->data['selected.rule']= $rule;
-$t->data['selected.rule2']= $preferRule2;
-$t->data['selected.time'] = $fileslot;
-$t->data['selected.timeres'] = $timeres;
-$t->data['selected.delimiter'] = $delimiter;
+$t->data['available_rules'] = $ruleset->availableRulesNames();
+$t->data['available_times'] = $statrule->availableFileSlots($timeres);
+$t->data['available_timeres'] = $statrule->availableTimeRes();
+$t->data['available_times_prev'] = $timeNavigation['prev'];
+$t->data['available_times_next'] = $timeNavigation['next'];
+
+$t->data['current_rule'] = $t->data['available_rules'][$rule];
+
+$t->data['selected_rule'] = $rule;
+$t->data['selected_rule2'] = $preferRule2;
+$t->data['selected_time'] = $fileslot;
+$t->data['selected_timeres'] = $timeres;
+$t->data['selected_delimiter'] = $delimiter;
 
 $t->data['debugdata'] = $dataset->getDebugData();
 $t->data['results'] = $dataset->getResults();
@@ -118,30 +121,31 @@ $t->data['summaryDataset'] = $dataset->getSummary();
 $t->data['topdelimiters'] = $dataset->getTopDelimiters();
 $t->data['availdelimiters'] = $dataset->availDelimiters();
 
-$t->data['delimiterPresentation'] =  $dataset->getDelimiterPresentation();
-
+$t->data['delimiterPresentation'] = $dataset->getDelimiterPresentation();
 $t->data['post_rule'] = getBaseURL($t, 'post', 'rule');
 $t->data['post_rule2'] = getBaseURL($t, 'post', 'rule2');
 $t->data['post_d'] = getBaseURL($t, 'post', 'd');
 $t->data['post_res'] = getBaseURL($t, 'post', 'res');
 $t->data['post_time'] = getBaseURL($t, 'post', 'time');
-$t->data['get_times_prev'] = getBaseURL($t, 'get', 'time', $t->data['available.times.prev']);
-$t->data['get_times_next'] = getBaseURL($t, 'get', 'time', $t->data['available.times.next']);
+$t->data['get_times_prev'] = getBaseURL($t, 'get', 'time', $t->data['available_times_prev']);
+$t->data['get_times_next'] = getBaseURL($t, 'get', 'time', $t->data['available_times_next']);
+
+//$t->data['jquery'] = array('ui' => true, 'core' => true);
 
 $t->show();
 
 function getBaseURL($t, $type = 'get', $key = null, $value = null)
 {
     $vars = array(
-        'rule' => $t->data['selected.rule'],
-        'time' => $t->data['selected.time'],
-        'res' => $t->data['selected.timeres'],
+        'rule' => $t->data['selected_rule'],
+        'time' => $t->data['selected_time'],
+        'res' => $t->data['selected_timeres'],
     );
-    if (isset($t->data['selected.delimiter'])) {
-        $vars['d'] = $t->data['selected.delimiter'];
+    if (isset($t->data['selected_delimiter'])) {
+        $vars['d'] = $t->data['selected_delimiter'];
     }
-    if (!empty($t->data['selected.rule2']) && $t->data['selected.rule2'] !== '_') {
-        $vars['rule2'] = $t->data['selected.rule2'];
+    if (!empty($t->data['selected_rule2']) && $t->data['selected_rule2'] !== '_') {
+        $vars['rule2'] = $t->data['selected_rule2'];
     }
 
     if (isset($key)) {
@@ -154,12 +158,7 @@ function getBaseURL($t, $type = 'get', $key = null, $value = null)
     }
 
     if ($type === 'get') {
-        return SimpleSAML\Module::getModuleURL("statistics/showstats.php") . '?' . http_build_query($vars, '', '&amp;');
-    } else {
-        $text = '';
-        foreach($vars as $k => $v) {
-            $text .= '<input type="hidden" name="' . $k . '" value="'. htmlspecialchars($v) . '" />' . "\n";
-        }
-        return $text;
+        return SimpleSAML\Module::getModuleURL("statistics/showstats.php") . '?' . http_build_query($vars, '', '&');
     }
+    return $vars;
 }
diff --git a/modules/statistics/www/style.css b/modules/statistics/www/style.css
index 1485d07910f3e7bb23af42fc43546184c050338b..4d0d163d600681ffe26fa34cee7cd7cbd9505bc4 100644
--- a/modules/statistics/www/style.css
+++ b/modules/statistics/www/style.css
@@ -1,6 +1,4 @@
 @media all {
-    .ui-tabs-panel { padding: .5em }
-
     div#content {
         margin: .4em ! important;
     }
@@ -35,10 +33,6 @@
         text-align: right;
     }
 
-    div.corner_t {
-        max-width: none ! important;
-    }
-
     table.timeseries tr.odd td {
         background-color: #f4f4f4;
     }
@@ -92,4 +86,42 @@
     table#statmeta {
         width: 100%;
     }
+
+    ul.tabset_tabs {
+        margin: 0px;
+        padding: 0px;
+        list-style: none;
+    }
+
+    ul.tabset_tabs li {
+        background: none;
+        color: #222;
+        display: inline-block;
+        padding: 10px 15px;
+        cursor: pointer;
+    }
+
+    ul.tabset_tabs li.current {
+        background: #ededed;
+        color: #222;
+    }
+
+    .tabset_content {
+        display: none;
+        background: #ededed;
+        padding: 15px;
+    }
+
+    .tabset_content.current {
+        display: inherit;
+    }
+
+    #graph img {
+        max-width: 77%;
+        height: auto;
+    }
+    #table img {
+        max-width: 77%;
+        height: auto;
+    }
 }
diff --git a/templates/error.twig b/templates/error.twig
index 7e226502a9a2034eef1375840513501dfef589f1..a885475584a08f669754a5ef0038f4fe12c8ba4a 100644
--- a/templates/error.twig
+++ b/templates/error.twig
@@ -1,5 +1,5 @@
 
-{% set pagetitle = 'Logged out'|trans %}
+{% set pagetitle = dictTitle | trans %}
 {% extends "base.twig" %}
 
 {% block postload %}
@@ -13,7 +13,7 @@
     {{ dictDescr | trans(parameters) }}
 
     {# include optional information for error
-       Some excepltions set 'incluseTemplate' to the name of a template to include.
+       Some exceptions set 'includeTemplate' to the name of a template to include.
        e.g. "core:no_state.tpl.php". The format is "<module>:<template name>"
     #}
     {% if includeTemplate %}
@@ -26,7 +26,7 @@
         {# TODO: Update class #}
         <div class="">
             <pre id="trackid">{{ error.trackId }}</pre>
-            <button data-clipboard-target="#trackid" id="btntrackid" class="clipboard-bnt pure-button left clipboard-btn">
+            <button data-clipboard-target="#trackid" id="btntrackid" class="pure-button left clipboard-btn">
                 <i class="fa fa-copy"></i>
             </button>
         </div>
diff --git a/templates/selectidp-dropdown.php b/templates/selectidp-dropdown.php
index ebe7646b481781968c42b7953c1aa0e25819d195..a3c4d2bf1d014c5720cdd389d689e5eee267c3e5 100644
--- a/templates/selectidp-dropdown.php
+++ b/templates/selectidp-dropdown.php
@@ -33,21 +33,12 @@ foreach ($this->data['idplist'] as $idpentry) {
                value="<?php echo htmlspecialchars($this->data['returnIDParam']); ?>"/>
         <select id="dropdownlist" name="idpentityid">
             <?php
-            /*
-             * TODO: change this to use $this instead when PHP 5.4 is the minimum requirement.
-             *
-             * This is a dirty hack because PHP 5.3 does not allow the use of $this inside closures. Therefore, the
-             * translation function must be passed somehow inside the closure. PHP 5.4 allows using $this, so we can
-             * then go back to the previous behaviour.
-             */
-            $GLOBALS['__t'] = $this;
             usort($this->data['idplist'], function ($idpentry1, $idpentry2) {
                 return strcmp(
-                    $GLOBALS['__t']->t('idpname_'.$idpentry1['entityid']),
-                    $GLOBALS['__t']->t('idpname_'.$idpentry2['entityid'])
+                    $this->t('idpname_'.$idpentry1['entityid']),
+                    $this->t('idpname_'.$idpentry2['entityid'])
                 );
             });
-            unset($GLOBALS['__t']);
 
             foreach ($this->data['idplist'] as $idpentry) {
                 echo '<option value="'.htmlspecialchars($idpentry['entityid']).'"';
diff --git a/tests/Utils/ClearStateTestCase.php b/tests/Utils/ClearStateTestCase.php
index 701dba29f95beb7b5e2d92063c8f071980ed313d..2cecc16b4c5d46af5774f50ee9332737f34b2389 100644
--- a/tests/Utils/ClearStateTestCase.php
+++ b/tests/Utils/ClearStateTestCase.php
@@ -4,10 +4,12 @@ namespace SimpleSAML\Test\Utils;
 
 include(dirname(__FILE__) . '/StateClearer.php');
 
+use PHPUnit\Framework\TestCase;
+
 /**
  * A base SSP test case that takes care of removing global state prior to test runs
  */
-class ClearStateTestCase extends \PHPUnit_Framework_TestCase
+class ClearStateTestCase extends TestCase
 {
     /**
      * Used for managing and clearing state
diff --git a/tests/lib/SimpleSAML/Auth/StateTest.php b/tests/lib/SimpleSAML/Auth/StateTest.php
index 5bbb9c5d88b28c8b25e03ea00efb66af5b6c0d14..0edb0b20ec8b2a94549f2e4a09aba1776b0d79fd 100644
--- a/tests/lib/SimpleSAML/Auth/StateTest.php
+++ b/tests/lib/SimpleSAML/Auth/StateTest.php
@@ -1,10 +1,11 @@
 <?php
 
+use PHPUnit\Framework\TestCase;
 
 /**
  * Tests for SimpleSAML_Auth_State
  */
-class Auth_StateTest extends PHPUnit_Framework_TestCase
+class Auth_StateTest extends TestCase
 {
 
 
diff --git a/tests/lib/SimpleSAML/DatabaseTest.php b/tests/lib/SimpleSAML/DatabaseTest.php
index 833d2e0ae618fe631f51febd4b9456d5cd0ecd0f..5f04dd87b9bf3e0322ccc3da6c367f57212e6bf3 100644
--- a/tests/lib/SimpleSAML/DatabaseTest.php
+++ b/tests/lib/SimpleSAML/DatabaseTest.php
@@ -1,5 +1,6 @@
 <?php
 
+use PHPUnit\Framework\TestCase;
 
 /**
  * This test ensures that the SimpleSAML_Database class can properly
@@ -12,7 +13,7 @@
  * @author Tyler Antonio, University of Alberta. <tantonio@ualberta.ca>
  * @package SimpleSAMLphp
  */
-class SimpleSAML_DatabaseTest extends PHPUnit_Framework_TestCase
+class SimpleSAML_DatabaseTest extends TestCase
 {
 
     /**
@@ -30,8 +31,6 @@ class SimpleSAML_DatabaseTest extends PHPUnit_Framework_TestCase
      * Make protected functions available for testing
      *
      * @param string $getMethod The method to get.
-     * @requires PHP 5.3.2
-     *
      * @return mixed The method itself.
      */
     protected static function getMethod($getMethod)
diff --git a/tests/lib/SimpleSAML/Locale/LanguageTest.php b/tests/lib/SimpleSAML/Locale/LanguageTest.php
index 7072a4930ccdf1504b0eeb2ae6c1317a71af1cf0..4b76c09d6187570990a2e4e0e45419a0f05e179c 100644
--- a/tests/lib/SimpleSAML/Locale/LanguageTest.php
+++ b/tests/lib/SimpleSAML/Locale/LanguageTest.php
@@ -2,9 +2,10 @@
 
 namespace SimpleSAML\Test\Locale;
 
+use PHPUnit\Framework\TestCase;
 use SimpleSAML\Locale\Language;
 
-class LanguageTest extends \PHPUnit_Framework_TestCase
+class LanguageTest extends TestCase
 {
 
 
diff --git a/tests/lib/SimpleSAML/Locale/LocalizationTest.php b/tests/lib/SimpleSAML/Locale/LocalizationTest.php
index a8ea072affe88884e1db299fa12b9a88c92890d9..d38af4add61227531ff0710238450c7c8a1e7c56 100644
--- a/tests/lib/SimpleSAML/Locale/LocalizationTest.php
+++ b/tests/lib/SimpleSAML/Locale/LocalizationTest.php
@@ -2,11 +2,12 @@
 
 namespace SimpleSAML\Test\Locale;
 
+use PHPUnit\Framework\TestCase;
 use SimpleSAML\Locale\Localization;
 use \SimpleSAML_Configuration as Configuration;
 
 
-class LocalizationTest extends \PHPUnit_Framework_TestCase
+class LocalizationTest extends TestCase
 {
     protected function setUp()
     {
diff --git a/tests/lib/SimpleSAML/Locale/TranslateTest.php b/tests/lib/SimpleSAML/Locale/TranslateTest.php
index ee431d6005e4c2f50cb0240a5a452616d3b4a3a7..93d6c441ba830b160c79f06925426331ccb082ff 100644
--- a/tests/lib/SimpleSAML/Locale/TranslateTest.php
+++ b/tests/lib/SimpleSAML/Locale/TranslateTest.php
@@ -2,9 +2,10 @@
 
 namespace SimpleSAML\Test\Locale;
 
+use PHPUnit\Framework\TestCase;
 use SimpleSAML\Locale\Translate;
 
-class TranslateTest extends \PHPUnit_Framework_TestCase
+class TranslateTest extends TestCase
 {
 
     /**
diff --git a/tests/lib/SimpleSAML/Metadata/SAMLBuilderTest.php b/tests/lib/SimpleSAML/Metadata/SAMLBuilderTest.php
index e958667c76cf478a6b18bab3aa529768813e8b9e..d9aa96a5a60c8d9778655eb78f0769c300b96aff 100644
--- a/tests/lib/SimpleSAML/Metadata/SAMLBuilderTest.php
+++ b/tests/lib/SimpleSAML/Metadata/SAMLBuilderTest.php
@@ -1,10 +1,11 @@
 <?php
 
+use PHPUnit\Framework\TestCase;
 
 /**
  * Class SimpleSAML_Metadata_SAMLBuilderTest
  */
-class SimpleSAML_Metadata_SAMLBuilderTest extends PHPUnit_Framework_TestCase
+class SimpleSAML_Metadata_SAMLBuilderTest extends TestCase
 {
 
     /**
diff --git a/tests/lib/SimpleSAML/Metadata/SAMLParserTest.php b/tests/lib/SimpleSAML/Metadata/SAMLParserTest.php
index 85cb0858b24bc5905cdbb8853f39c981915fef57..0e34ad063e006f336fe978f7178a51a516eea086 100644
--- a/tests/lib/SimpleSAML/Metadata/SAMLParserTest.php
+++ b/tests/lib/SimpleSAML/Metadata/SAMLParserTest.php
@@ -1,10 +1,12 @@
 <?php
 namespace SimpleSAML\Metadata;
 
+use PHPUnit\Framework\TestCase;
+
 /**
  * Test SAML parsing
  */
-class SAMLParserTest extends \PHPUnit_Framework_TestCase
+class SAMLParserTest extends TestCase
 {
 
     /**
diff --git a/tests/lib/SimpleSAML/ModuleTest.php b/tests/lib/SimpleSAML/ModuleTest.php
index a3abf0e11460e1de5e5692f6fde13cf1fb0e2cd4..00cff258cabf8d7246cbcebedca1ebd80cc27968 100644
--- a/tests/lib/SimpleSAML/ModuleTest.php
+++ b/tests/lib/SimpleSAML/ModuleTest.php
@@ -1,9 +1,10 @@
 <?php
 namespace SimpleSAML\Test;
 
+use PHPUnit\Framework\TestCase;
 use SimpleSAML\Module;
 
-class ModuleTest extends \PHPUnit_Framework_TestCase
+class ModuleTest extends TestCase
 {
 
 
diff --git a/tests/lib/SimpleSAML/Store/RedisTest.php b/tests/lib/SimpleSAML/Store/RedisTest.php
index 329f57113374e7e7d5f1ca6cc455077520f2b318..d17f4473a3f99b48db61f1e2b52f76aebbc23f0b 100644
--- a/tests/lib/SimpleSAML/Store/RedisTest.php
+++ b/tests/lib/SimpleSAML/Store/RedisTest.php
@@ -2,6 +2,7 @@
 
 namespace SimpleSAML\Test\Store;
 
+use PHPUnit\Framework\TestCase;
 use \SimpleSAML_Configuration as Configuration;
 use \SimpleSAML\Store;
 
@@ -13,7 +14,7 @@ use \SimpleSAML\Store;
  *
  * @package simplesamlphp/simplesamlphp
  */
-class RedisTest extends \PHPUnit_Framework_TestCase
+class RedisTest extends TestCase
 {
     protected function setUp()
     {
diff --git a/tests/lib/SimpleSAML/Store/SQLTest.php b/tests/lib/SimpleSAML/Store/SQLTest.php
index 09b4ddbf5a9964662e5f7536be8c2adec472d392..143632f9060f5ccfa4d992883ff68191bc7d4b34 100644
--- a/tests/lib/SimpleSAML/Store/SQLTest.php
+++ b/tests/lib/SimpleSAML/Store/SQLTest.php
@@ -2,6 +2,7 @@
 
 namespace SimpleSAML\Test\Store;
 
+use PHPUnit\Framework\TestCase;
 use \SimpleSAML_Configuration as Configuration;
 use \SimpleSAML\Store;
 
@@ -14,7 +15,7 @@ use \SimpleSAML\Store;
  * @author Sergio Gómez <sergio@uco.es>
  * @package simplesamlphp/simplesamlphp
  */
-class SQLTest extends \PHPUnit_Framework_TestCase
+class SQLTest extends TestCase
 {
     protected function setUp()
     {
@@ -93,7 +94,7 @@ class SQLTest extends \PHPUnit_Framework_TestCase
 
         $value = $store->get('test', 'foo');
 
-        $this->assertEquals(null, $value);
+        $this->assertNull($value);
     }
 
     /**
@@ -106,10 +107,10 @@ class SQLTest extends \PHPUnit_Framework_TestCase
     {
         /** @var \SimpleSAML\Store\SQL $store */
         $store = Store::getInstance();
-        
+
         $store->set('test', 'foo', 'bar');
         $value = $store->get('test', 'foo');
-        
+
         $this->assertEquals('bar', $value);
     }
 
@@ -147,7 +148,7 @@ class SQLTest extends \PHPUnit_Framework_TestCase
         $store->delete('test', 'foo');
         $value = $store->get('test', 'foo');
 
-        $this->assertEquals(null, $value);
+        $this->assertNull($value);
     }
 
     /**
@@ -167,7 +168,7 @@ class SQLTest extends \PHPUnit_Framework_TestCase
         $store->delete('test', $key);
         $value = $store->get('test', $key);
 
-        $this->assertEquals(null, $value);
+        $this->assertNull($value);
     }
 
     protected function tearDown()
diff --git a/tests/lib/SimpleSAML/StoreTest.php b/tests/lib/SimpleSAML/StoreTest.php
index 6fd9650fc9120938c36112cefe75ab6984351b1d..22641681dcd9143f351226d9d55797854ed811f9 100644
--- a/tests/lib/SimpleSAML/StoreTest.php
+++ b/tests/lib/SimpleSAML/StoreTest.php
@@ -2,6 +2,7 @@
 
 namespace SimpleSAML\Test;
 
+use PHPUnit\Framework\TestCase;
 use \SimpleSAML_Configuration as Configuration;
 use \SimpleSAML\Store;
 
@@ -14,7 +15,7 @@ use \SimpleSAML\Store;
  * @author Sergio Gómez <sergio@uco.es>
  * @package simplesamlphp/simplesamlphp
  */
-class StoreTest extends \PHPUnit_Framework_TestCase
+class StoreTest extends TestCase
 {
     /**
      * @covers \SimpleSAML\Store::getInstance
@@ -27,7 +28,7 @@ class StoreTest extends \PHPUnit_Framework_TestCase
 
         $store = Store::getInstance();
 
-        $this->assertEquals(false, $store);
+        $this->assertFalse($store);
     }
 
 
@@ -42,7 +43,7 @@ class StoreTest extends \PHPUnit_Framework_TestCase
 
         $store = Store::getInstance();
 
-        $this->assertEquals(false, $store);
+        $this->assertFalse($store);
     }
 
 
diff --git a/tests/lib/SimpleSAML/Utils/ArraysTest.php b/tests/lib/SimpleSAML/Utils/ArraysTest.php
index c4a38726aef8839a2e1458d99773e2b088bddb0d..0ec3e29bdf74250eb7a098fa230647db0e54f96f 100644
--- a/tests/lib/SimpleSAML/Utils/ArraysTest.php
+++ b/tests/lib/SimpleSAML/Utils/ArraysTest.php
@@ -2,12 +2,13 @@
 
 namespace SimpleSAML\Test\Utils;
 
+use PHPUnit\Framework\TestCase;
 use SimpleSAML\Utils\Arrays;
 
 /**
  * Tests for SimpleSAML\Utils\Arrays.
  */
-class ArraysTest extends \PHPUnit_Framework_TestCase
+class ArraysTest extends TestCase
 {
 
     /**
diff --git a/tests/lib/SimpleSAML/Utils/AttributesTest.php b/tests/lib/SimpleSAML/Utils/AttributesTest.php
index 0dc083314e29e18c55ff6d49ef90a60869c93016..271b506848b0b76a1bcf4d6a94bac2d0b994fc09 100644
--- a/tests/lib/SimpleSAML/Utils/AttributesTest.php
+++ b/tests/lib/SimpleSAML/Utils/AttributesTest.php
@@ -2,6 +2,7 @@
 
 namespace SimpleSAML\Test\Utils;
 
+use PHPUnit\Framework\TestCase;
 use SimpleSAML\Utils\Attributes;
 
 /**
@@ -9,7 +10,7 @@ use SimpleSAML\Utils\Attributes;
  *
  * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no>
  */
-class AttributesTest extends \PHPUnit_Framework_TestCase
+class AttributesTest extends TestCase
 {
 
     /**
diff --git a/tests/lib/SimpleSAML/Utils/Config/MetadataTest.php b/tests/lib/SimpleSAML/Utils/Config/MetadataTest.php
index 95f0aa547c5a0c33ff3f135684ee42ea674e9502..98ffb7f0b3350e28b71cc50211e4a65994352323 100644
--- a/tests/lib/SimpleSAML/Utils/Config/MetadataTest.php
+++ b/tests/lib/SimpleSAML/Utils/Config/MetadataTest.php
@@ -2,12 +2,13 @@
 
 namespace SimpleSAML\Test\Utils\Config;
 
+use PHPUnit\Framework\TestCase;
 use SimpleSAML\Utils\Config\Metadata;
 
 /**
  * Tests related to SAML metadata.
  */
-class MetadataTest extends \PHPUnit_Framework_TestCase
+class MetadataTest extends TestCase
 {
 
     /**
diff --git a/tests/lib/SimpleSAML/Utils/ConfigTest.php b/tests/lib/SimpleSAML/Utils/ConfigTest.php
index 5dd0fa33b1ad2dca80bd5d43a530af385a7ca532..262fbfd801837ef611fb738c69424fa5caaa41ea 100644
--- a/tests/lib/SimpleSAML/Utils/ConfigTest.php
+++ b/tests/lib/SimpleSAML/Utils/ConfigTest.php
@@ -2,12 +2,13 @@
 
 namespace SimpleSAML\Test\Utils;
 
+use PHPUnit\Framework\TestCase;
 use SimpleSAML\Utils\Config;
 
 /**
  * Tests for SimpleSAML\Utils\Config
  */
-class ConfigTest extends \PHPUnit_Framework_TestCase
+class ConfigTest extends TestCase
 {
 
     /**
diff --git a/tests/lib/SimpleSAML/Utils/CryptoTest.php b/tests/lib/SimpleSAML/Utils/CryptoTest.php
index cb31cdbae0b8afa9c18f0b74c9ca4921c6ba7d5e..a47d0d432c938fe3001a6a0c2a4fb059937d77a0 100644
--- a/tests/lib/SimpleSAML/Utils/CryptoTest.php
+++ b/tests/lib/SimpleSAML/Utils/CryptoTest.php
@@ -2,6 +2,7 @@
 
 namespace SimpleSAML\Test\Utils;
 
+use PHPUnit\Framework\TestCase;
 use SimpleSAML\Utils\Crypto;
 use \SimpleSAML_Configuration as Configuration;
 
@@ -10,7 +11,7 @@ use \org\bovigo\vfs\vfsStream;
 /**
  * Tests for SimpleSAML\Utils\Crypto.
  */
-class CryptoTest extends \PHPUnit_Framework_TestCase
+class CryptoTest extends TestCase
 {
     const ROOTDIRNAME = 'testdir';
     const DEFAULTCERTDIR = 'certdir';
diff --git a/tests/lib/SimpleSAML/Utils/HTTPTest.php b/tests/lib/SimpleSAML/Utils/HTTPTest.php
index 4fd540cfb241d38575cf0638b9fa10902f54fb2d..073073c9378b12d1ca2b306fdf5392304adf6ca3 100644
--- a/tests/lib/SimpleSAML/Utils/HTTPTest.php
+++ b/tests/lib/SimpleSAML/Utils/HTTPTest.php
@@ -1,9 +1,10 @@
 <?php
 namespace SimpleSAML\Test\Utils;
 
+use PHPUnit\Framework\TestCase;
 use SimpleSAML\Utils\HTTP;
 
-class HTTPTest extends \PHPUnit_Framework_TestCase
+class HTTPTest extends TestCase
 {
 
 
diff --git a/tests/lib/SimpleSAML/Utils/NetTest.php b/tests/lib/SimpleSAML/Utils/NetTest.php
index c6f4d4f9e2c4b34b43bf39df59bae7f10f2b9201..8e7e29e5453794f34bce483ee921279dd37b845e 100644
--- a/tests/lib/SimpleSAML/Utils/NetTest.php
+++ b/tests/lib/SimpleSAML/Utils/NetTest.php
@@ -2,12 +2,13 @@
 
 namespace SimpleSAML\Test\Utils;
 
+use PHPUnit\Framework\TestCase;
 use SimpleSAML\Utils\Net;
 
 /**
  * Tests for SimpleSAML\Utils\Test.
  */
-class NetTest extends \PHPUnit_Framework_TestCase
+class NetTest extends TestCase
 {
 
 
diff --git a/tests/lib/SimpleSAML/Utils/RandomTest.php b/tests/lib/SimpleSAML/Utils/RandomTest.php
index 9f2f6381be6a1a5852810140073d1202e361423b..ff05ed0243f2ceb09104ee59b2b3deb6f529e401 100644
--- a/tests/lib/SimpleSAML/Utils/RandomTest.php
+++ b/tests/lib/SimpleSAML/Utils/RandomTest.php
@@ -2,12 +2,13 @@
 
 namespace SimpleSAML\Test\Utils;
 
+use PHPUnit\Framework\TestCase;
 use SimpleSAML\Utils\Random;
 
 /**
  * Tests for SimpleSAML\Utils\Random.
  */
-class RandomTest extends \PHPUnit_Framework_TestCase
+class RandomTest extends TestCase
 {
 
     /**
diff --git a/tests/lib/SimpleSAML/Utils/SystemTest.php b/tests/lib/SimpleSAML/Utils/SystemTest.php
index c749b798757df5ce3baad64aed59135a5fbb1b38..897f8a3d9d68b6c7f011c3f0fddb1f06464e4572 100644
--- a/tests/lib/SimpleSAML/Utils/SystemTest.php
+++ b/tests/lib/SimpleSAML/Utils/SystemTest.php
@@ -2,6 +2,7 @@
 
 namespace SimpleSAML\Test\Utils;
 
+use PHPUnit\Framework\TestCase;
 use \SimpleSAML_Configuration as Configuration;
 use \SimpleSAML\Utils\System;
 
@@ -10,7 +11,7 @@ use \org\bovigo\vfs\vfsStream;
 /**
  * Tests for SimpleSAML\Utils\System.
  */
-class SystemTest extends \PHPUnit_Framework_TestCase
+class SystemTest extends TestCase
 {
     const ROOTDIRNAME = 'testdir';
     const DEFAULTTEMPDIR = 'tempdir';
@@ -109,7 +110,6 @@ class SystemTest extends \PHPUnit_Framework_TestCase
     }
 
     /**
-     * @requires PHP 5.4.0
      * @covers \SimpleSAML\Utils\System::writeFile
      * @test
      */
@@ -128,7 +128,6 @@ class SystemTest extends \PHPUnit_Framework_TestCase
     }
 
     /**
-     * @requires PHP 5.4.0
      * @covers \SimpleSAML\Utils\System::writeFile
      * @test
      */
@@ -151,7 +150,6 @@ class SystemTest extends \PHPUnit_Framework_TestCase
     }
 
     /**
-     * @requires PHP 5.4.0
      * @covers \SimpleSAML\Utils\System::writeFile
      * @test
      */
@@ -210,7 +208,6 @@ class SystemTest extends \PHPUnit_Framework_TestCase
     }
 
     /**
-     * @requires PHP 5.4.0
      * @requires OS Linux
      * @covers \SimpleSAML\Utils\System::getTempDir
      * @test
diff --git a/tests/lib/SimpleSAML/Utils/TimeTest.php b/tests/lib/SimpleSAML/Utils/TimeTest.php
index 953bd13311b08bf04600faa3c3dc5a2dc1f78dde..043aefbe52d4bec5519bda67216e5086fb4e79ff 100644
--- a/tests/lib/SimpleSAML/Utils/TimeTest.php
+++ b/tests/lib/SimpleSAML/Utils/TimeTest.php
@@ -2,9 +2,10 @@
 
 namespace SimpleSAML\Test\Utils;
 
+use PHPUnit\Framework\TestCase;
 use SimpleSAML\Utils\Time;
 
-class TimeTest extends \PHPUnit_Framework_TestCase
+class TimeTest extends TestCase
 {
 
     /**
diff --git a/tests/lib/SimpleSAML/Utils/XMLTest.php b/tests/lib/SimpleSAML/Utils/XMLTest.php
index a812b964f0d8a8a7a83c0835dd95a88b37edca0b..7660487956ba79e9449342b26380896d7641041f 100644
--- a/tests/lib/SimpleSAML/Utils/XMLTest.php
+++ b/tests/lib/SimpleSAML/Utils/XMLTest.php
@@ -2,13 +2,14 @@
 
 namespace SimpleSAML\Test\Utils;
 
+use PHPUnit\Framework\TestCase;
 use \SimpleSAML_Configuration as Configuration;
 use \SimpleSAML\Utils\XML;
 
 /**
  * Tests for SimpleSAML\Utils\XML.
  */
-class XMLTest extends \PHPUnit_Framework_TestCase
+class XMLTest extends TestCase
 {
     /**
      * @covers \SimpleSAML\Utils\XML::isDOMNodeOfType
diff --git a/tests/lib/SimpleSAML/XML/ErrorsTest.php b/tests/lib/SimpleSAML/XML/ErrorsTest.php
index c73580a500bd2f007fa558ea22af35867a915a16..a6a46f19da67564f78e765bb4865bb1821a6068d 100644
--- a/tests/lib/SimpleSAML/XML/ErrorsTest.php
+++ b/tests/lib/SimpleSAML/XML/ErrorsTest.php
@@ -12,9 +12,10 @@
 
 namespace SimpleSAML\Test\XML;
 
+use PHPUnit\Framework\TestCase;
 use SimpleSAML\XML\Errors;
 
-class ErrorsTest extends \PHPUnit_Framework_TestCase
+class ErrorsTest extends TestCase
 {
     /**
      * @covers \SimpleSAML\XML\Errors::begin
diff --git a/tests/lib/SimpleSAML/XML/ParserTest.php b/tests/lib/SimpleSAML/XML/ParserTest.php
index 7860d9523285b4a6a2597107b60bb972ab04079f..336ae86f12357fff99e3870873d19bc171807c90 100644
--- a/tests/lib/SimpleSAML/XML/ParserTest.php
+++ b/tests/lib/SimpleSAML/XML/ParserTest.php
@@ -11,9 +11,10 @@
 
 namespace SimpleSAML\Test\XML;
 
+use PHPUnit\Framework\TestCase;
 use SimpleSAML\XML\Parser;
 
-class ParserTest extends \PHPUnit_Framework_TestCase
+class ParserTest extends TestCase
 {
     const XMLDOC = <<< XML
 <?xml version="1.0" encoding="UTF-8"?>
diff --git a/tests/lib/SimpleSAML/XML/Shib13/AuthnResponseTest.php b/tests/lib/SimpleSAML/XML/Shib13/AuthnResponseTest.php
index f10423c0aaa6712a134ec86507deb102c2da5f94..b492337f6780387f9125ddac3cea9a9885997fa7 100644
--- a/tests/lib/SimpleSAML/XML/Shib13/AuthnResponseTest.php
+++ b/tests/lib/SimpleSAML/XML/Shib13/AuthnResponseTest.php
@@ -11,12 +11,13 @@
 
 namespace SimpleSAML\Test\XML\Shib13;
 
+use PHPUnit\Framework\TestCase;
 use SimpleSAML\XML\Shib13\AuthnResponse;
 
-class AuthnResponseTest extends \PHPUnit_Framework_TestCase
+class AuthnResponseTest extends TestCase
 {
     const XMLDOC = <<< XML
-<Response xmlns="urn:oasis:names:tc:SAML:1.0:protocol" 
+<Response xmlns="urn:oasis:names:tc:SAML:1.0:protocol"
     MajorVersion="1" MinorVersion="1"
     ResponseID="" IssueInstant="">
     <Assertion xmlns="urn:oasis:names:tc:SAML:1.0:assertion"
@@ -34,7 +35,7 @@ class AuthnResponseTest extends \PHPUnit_Framework_TestCase
 XML;
 
     const BADXMLDOC = <<< XML
-<Response xmlns="urn:oasis:names:tc:SAML:1.0:protocol" 
+<Response xmlns="urn:oasis:names:tc:SAML:1.0:protocol"
     MajorVersion="1" MinorVersion="1"
     ResponseID="" IssueInstant="">
     <Assertion xmlns="urn:oasis:names:tc:SAML:1.0:assertion"
diff --git a/tests/lib/SimpleSAML/XML/SignerTest.php b/tests/lib/SimpleSAML/XML/SignerTest.php
index 359836b8b8d6b6a2dfec24751a011621f011c68e..77edff09d1d1291fef61af531b5988d5b41ac04a 100644
--- a/tests/lib/SimpleSAML/XML/SignerTest.php
+++ b/tests/lib/SimpleSAML/XML/SignerTest.php
@@ -2,6 +2,7 @@
 
 namespace SimpleSAML\Test\Utils;
 
+use PHPUnit\Framework\TestCase;
 use \SimpleSAML_Configuration as Configuration;
 use \SimpleSAML\XML\Signer;
 
@@ -10,7 +11,7 @@ use \org\bovigo\vfs\vfsStream;
 /**
  * Tests for SimpleSAML\XML\Signer.
  */
-class SignerTest extends \PHPUnit_Framework_TestCase
+class SignerTest extends TestCase
 {
     // openssl genrsa -out private.pem 2048
     private $private_key = <<<'NOWDOC'
diff --git a/tests/lib/SimpleSAML/XML/ValidatorTest.php b/tests/lib/SimpleSAML/XML/ValidatorTest.php
index 2b178022e80b994d2b9dac0ca124ef7ca190b51d..8dc2179f45986bfc4c287dc1a54c7c31f252e721 100644
--- a/tests/lib/SimpleSAML/XML/ValidatorTest.php
+++ b/tests/lib/SimpleSAML/XML/ValidatorTest.php
@@ -2,6 +2,7 @@
 
 namespace SimpleSAML\Test\XML;
 
+use PHPUnit\Framework\TestCase;
 use \SimpleSAML_Configuration as Configuration;
 use \SimpleSAML\XML\Signer;
 use \SimpleSAML\XML\Validator;
@@ -11,7 +12,7 @@ use \org\bovigo\vfs\vfsStream;
 /**
  * Tests for SimpleSAML\XML\Validator.
  */
-class ValidatorTest extends \PHPUnit_Framework_TestCase
+class ValidatorTest extends TestCase
 {
     // openssl genrsa -out ca.key.pem 2048
     private $ca_private_key = <<<'NOWDOC'
diff --git a/tests/modules/consent/lib/Auth/Process/ConsentTest.php b/tests/modules/consent/lib/Auth/Process/ConsentTest.php
index e83659d22e054db22ba453e60a5372ac2af2f56b..887e11b433e8daffa88fdef506339124d9622685 100644
--- a/tests/modules/consent/lib/Auth/Process/ConsentTest.php
+++ b/tests/modules/consent/lib/Auth/Process/ConsentTest.php
@@ -8,9 +8,10 @@
 
 namespace SimpleSAML\Test\Module\consent\Auth\Process;
 
+use PHPUnit\Framework\TestCase;
 use \SimpleSAML_Configuration as Configuration;
 
-class ConsentTest extends \PHPUnit_Framework_TestCase
+class ConsentTest extends TestCase
 {
     public function setUp()
     {
diff --git a/tests/modules/core/lib/Auth/Process/AttributeAddTest.php b/tests/modules/core/lib/Auth/Process/AttributeAddTest.php
index 29e900db754cbf1303a081fefc33ae89677eabff..1fd9fc72bc67ccfdefcb3ee23822be1afddf8333 100644
--- a/tests/modules/core/lib/Auth/Process/AttributeAddTest.php
+++ b/tests/modules/core/lib/Auth/Process/AttributeAddTest.php
@@ -1,9 +1,11 @@
 <?php
 
+use PHPUnit\Framework\TestCase;
+
 /**
  * Test for the core:AttributeAdd filter.
  */
-class Test_Core_Auth_Process_AttributeAdd extends PHPUnit_Framework_TestCase
+class Test_Core_Auth_Process_AttributeAdd extends TestCase
 {
 
     /**
diff --git a/tests/modules/core/lib/Auth/Process/AttributeAlterTest.php b/tests/modules/core/lib/Auth/Process/AttributeAlterTest.php
index 47a16e5a43d309d3f5ef4869b2588990edcfa0d0..8260c04e487e3c9d345ac9391604589d194bd941 100644
--- a/tests/modules/core/lib/Auth/Process/AttributeAlterTest.php
+++ b/tests/modules/core/lib/Auth/Process/AttributeAlterTest.php
@@ -1,9 +1,11 @@
 <?php
 
+use PHPUnit\Framework\TestCase;
+
 /**
  * Test for the core:AttributeAlter filter.
  */
-class Test_Core_Auth_Process_AttributeAlter extends PHPUnit_Framework_TestCase
+class Test_Core_Auth_Process_AttributeAlter extends TestCase
 {
 
     /**
@@ -354,4 +356,3 @@ class Test_Core_Auth_Process_AttributeAlter extends PHPUnit_Framework_TestCase
         $result = self::processFilter($config, $request);
     }
 }
-
diff --git a/tests/modules/core/lib/Auth/Process/AttributeCopyTest.php b/tests/modules/core/lib/Auth/Process/AttributeCopyTest.php
index 599df57bb3fa8f16110ea5065b65224268268df2..d960c379f764e9b0db635a8f8aa44253daaa52c5 100644
--- a/tests/modules/core/lib/Auth/Process/AttributeCopyTest.php
+++ b/tests/modules/core/lib/Auth/Process/AttributeCopyTest.php
@@ -1,9 +1,11 @@
 <?php
 
+use PHPUnit\Framework\TestCase;
+
 /**
  * Test for the core:AttributeCopy filter.
  */
-class Test_Core_Auth_Process_AttributeCopy extends PHPUnit_Framework_TestCase
+class Test_Core_Auth_Process_AttributeCopy extends TestCase
 {
 
     /**
diff --git a/tests/modules/core/lib/Auth/Process/AttributeLimitTest.php b/tests/modules/core/lib/Auth/Process/AttributeLimitTest.php
index 7c2e6ac7a172b6ffb7a3a4c3bed4d92df980483b..57e912e88cdf993f143e9240e79acd455b8738a3 100644
--- a/tests/modules/core/lib/Auth/Process/AttributeLimitTest.php
+++ b/tests/modules/core/lib/Auth/Process/AttributeLimitTest.php
@@ -1,8 +1,11 @@
 <?php
+
+use PHPUnit\Framework\TestCase;
+
 /**
  * Test for the core:AttributeLimit filter.
  */
-class Test_Core_Auth_Process_AttributeLimitTest extends PHPUnit_Framework_TestCase
+class Test_Core_Auth_Process_AttributeLimitTest extends TestCase
 {
 
     /**
@@ -197,8 +200,8 @@ class Test_Core_Auth_Process_AttributeLimitTest extends PHPUnit_Framework_TestCa
 
     /**
      * Test for exception with illegal config.
-     * 
-     * @expectedException Exception 
+     *
+     * @expectedException Exception
      */
     public function testInvalidConfig()
     {
@@ -211,8 +214,8 @@ class Test_Core_Auth_Process_AttributeLimitTest extends PHPUnit_Framework_TestCa
 
     /**
      * Test for invalid attribute name
-     * 
-     * @expectedException Exception 
+     *
+     * @expectedException Exception
      */
     public function testInvalidAttributeName()
     {
@@ -265,12 +268,12 @@ class Test_Core_Auth_Process_AttributeLimitTest extends PHPUnit_Framework_TestCa
     }
 
     /**
-     * Test for allowed attributes not an array. 
+     * Test for allowed attributes not an array.
      *
-     * This test is very unlikely and would require malformed metadata processing. 
+     * This test is very unlikely and would require malformed metadata processing.
      * Cannot be generated via config options.
      *
-     * @expectedException Exception 
+     * @expectedException Exception
      */
     public function testMatchAttributeValuesNotArray()
     {
diff --git a/tests/modules/core/lib/Auth/Process/AttributeMapTest.php b/tests/modules/core/lib/Auth/Process/AttributeMapTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..719ad54cf222f10215dad485d6fb5c2b010ed5ce
--- /dev/null
+++ b/tests/modules/core/lib/Auth/Process/AttributeMapTest.php
@@ -0,0 +1,155 @@
+<?php
+
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Test for the core:AttributeMap filter.
+ */
+class Test_Core_Auth_Process_AttributeMap extends TestCase
+{
+    /**
+     * Helper function to run the filter with a given configuration.
+     *
+     * @param array $config  The filter configuration.
+     * @param array $request  The request state.
+     * @return array  The state array after processing.
+     */
+    private static function processFilter(array $config, array $request)
+    {
+        $filter = new sspmod_core_Auth_Process_AttributeMap($config, null);
+        $filter->process($request);
+        return $request;
+    }
+
+
+    public function testBasic()
+    {
+        $config = [
+            'attribute1' => 'attribute2',
+        ];
+        $request = [
+            'Attributes' => [
+                'attribute1' => ['value'],
+            ],
+        ];
+
+        $processed = self::processFilter($config, $request);
+        $result = $processed['Attributes'];
+        $expected = [
+            'attribute2' => ['value'],
+        ];
+
+        $this->assertEquals($expected, $result);
+    }
+
+    public function testDuplicate()
+    {
+        $config = [
+            'attribute1' => 'attribute2',
+            '%duplicate',
+        ];
+        $request = [
+            'Attributes' => [
+                'attribute1' => ['value'],
+            ],
+        ];
+
+        $processed = self::processFilter($config, $request);
+        $result = $processed['Attributes'];
+        $expected = [
+            'attribute1' => ['value'],
+            'attribute2' => ['value'],
+        ];
+
+        $this->assertEquals($expected, $result);
+    }
+
+    public function testMultiple()
+    {
+        $config = [
+            'attribute1' => ['attribute2', 'attribute3'],
+        ];
+        $request = [
+            'Attributes' => [
+                'attribute1' => ['value'],
+            ],
+        ];
+
+        $processed = self::processFilter($config, $request);
+        $result = $processed['Attributes'];
+        $expected = [
+            'attribute2' => ['value'],
+            'attribute3' => ['value'],
+        ];
+
+        $this->assertEquals($expected, $result);
+    }
+
+    public function testMultipleDuplicate()
+    {
+        $config = [
+            'attribute1' => ['attribute2', 'attribute3'],
+            '%duplicate',
+        ];
+        $request = [
+            'Attributes' => [
+                'attribute1' => ['value'],
+            ],
+        ];
+
+        $processed = self::processFilter($config, $request);
+        $result = $processed['Attributes'];
+        $expected = [
+            'attribute1' => ['value'],
+            'attribute2' => ['value'],
+            'attribute3' => ['value'],
+        ];
+
+        $this->assertEquals($expected, $result);
+    }
+
+    public function testInvalidOriginalAttributeType()
+    {
+        $config = [
+            10 => 'attribute2',
+        ];
+        $request = [
+            'Attributes' => [
+                'attribute1' => ['value'],
+            ],
+        ];
+
+        $this->setExpectedException('\Exception');
+        self::processFilter($config, $request);
+    }
+
+    public function testInvalidMappedAttributeType()
+    {
+        $config = [
+            'attribute1' => 10,
+        ];
+        $request = [
+            'Attributes' => [
+                'attribute1' => ['value'],
+            ],
+        ];
+
+        $this->setExpectedException('\Exception');
+        self::processFilter($config, $request);
+    }
+
+    public function testMissingMapFile()
+    {
+        $config = [
+            'non_existant_mapfile',
+        ];
+        $request = [
+            'Attributes' => [
+                'attribute1' => ['value'],
+            ],
+        ];
+
+        $this->setExpectedException('\Exception');
+        self::processFilter($config, $request);
+    }
+}
diff --git a/tests/modules/core/lib/Auth/Process/AttributeRealmTest.php b/tests/modules/core/lib/Auth/Process/AttributeRealmTest.php
index af3cd3e38a87bca9e2dec09ea685c81cab2d9084..915475d17d559b7eb163eff0dd6242e38f034978 100644
--- a/tests/modules/core/lib/Auth/Process/AttributeRealmTest.php
+++ b/tests/modules/core/lib/Auth/Process/AttributeRealmTest.php
@@ -1,9 +1,11 @@
 <?php
 
+use PHPUnit\Framework\TestCase;
+
 /**
  * Test for the core:AttributeRealm filter.
  */
-class Test_Core_Auth_Process_AttributeRealm extends PHPUnit_Framework_TestCase
+class Test_Core_Auth_Process_AttributeRealm extends TestCase
 {
 
     /**
diff --git a/tests/modules/core/lib/Auth/Process/AttributeValueMapTest.php b/tests/modules/core/lib/Auth/Process/AttributeValueMapTest.php
index c0deaf8aa62885c505111cbfaaf65c84bc686963..a6d601f06bfa821ab68f514fd471d1f37a8a4a47 100644
--- a/tests/modules/core/lib/Auth/Process/AttributeValueMapTest.php
+++ b/tests/modules/core/lib/Auth/Process/AttributeValueMapTest.php
@@ -2,12 +2,13 @@
 
 namespace SimpleSAML\Test\Module\core\Auth\Process;
 
+use PHPUnit\Framework\TestCase;
 use SimpleSAML\Module\core\Auth\Process\AttributeValueMap;
 
 /**
  * Test for the core:AttributeValueMap filter.
  */
-class AttributeValueMapTest extends \PHPUnit_Framework_TestCase
+class AttributeValueMapTest extends TestCase
 {
 
     /**
diff --git a/tests/modules/core/lib/Auth/Process/PHPTest.php b/tests/modules/core/lib/Auth/Process/PHPTest.php
index 46c7d37d8418294012ffaf2bc30275a4d667918e..0e18e9bcd9f10b052fa934a950369b222c9035e4 100644
--- a/tests/modules/core/lib/Auth/Process/PHPTest.php
+++ b/tests/modules/core/lib/Auth/Process/PHPTest.php
@@ -1,10 +1,11 @@
 <?php
 
+use PHPUnit\Framework\TestCase;
 
 /**
  * Test for the core:PHP filter.
  */
-class Test_Core_Auth_Process_PHP extends PHPUnit_Framework_TestCase
+class Test_Core_Auth_Process_PHP extends TestCase
 {
 
     /**
diff --git a/tests/modules/core/lib/Auth/Process/ScopeAttributeTest.php b/tests/modules/core/lib/Auth/Process/ScopeAttributeTest.php
index 8b86314cf2ba9909e4168da047c3d40e90153f6c..866fc1a641a60db42eaffa27365e8664957b6197 100644
--- a/tests/modules/core/lib/Auth/Process/ScopeAttributeTest.php
+++ b/tests/modules/core/lib/Auth/Process/ScopeAttributeTest.php
@@ -1,9 +1,11 @@
 <?php
 
+use PHPUnit\Framework\TestCase;
+
 /**
  * Test for the core:ScopeAttribute filter.
  */
-class Test_Core_Auth_Process_ScopeAttribute extends PHPUnit_Framework_TestCase
+class Test_Core_Auth_Process_ScopeAttribute extends TestCase
 {
 
     /*
diff --git a/tests/modules/core/lib/Auth/Process/ScopeFromAttributeTest.php b/tests/modules/core/lib/Auth/Process/ScopeFromAttributeTest.php
index f6ef1bf90414abb094f6e92db22c2d75fda23fd1..545093124dbe74709ed2b8fec9fd98eb90d212e1 100644
--- a/tests/modules/core/lib/Auth/Process/ScopeFromAttributeTest.php
+++ b/tests/modules/core/lib/Auth/Process/ScopeFromAttributeTest.php
@@ -1,9 +1,11 @@
 <?php
 
+use PHPUnit\Framework\TestCase;
+
 /**
  * Test for the core:ScopeFromAttribute filter.
  */
-class Test_Core_Auth_Process_ScopeFromAttribute extends PHPUnit_Framework_TestCase
+class Test_Core_Auth_Process_ScopeFromAttribute extends TestCase
 {
 
     /*
diff --git a/tests/modules/core/lib/Auth/Process/TargetedIDTest.php b/tests/modules/core/lib/Auth/Process/TargetedIDTest.php
index 1e5de903351a8aa2276f0cb2f170d4d62e538beb..7cfc1abe3e222bf193a8514e1a10f3179914db66 100644
--- a/tests/modules/core/lib/Auth/Process/TargetedIDTest.php
+++ b/tests/modules/core/lib/Auth/Process/TargetedIDTest.php
@@ -1,9 +1,11 @@
 <?php
 
+use PHPUnit\Framework\TestCase;
+
 /**
  * Test for the core:TargetedID filter.
  */
-class Test_Core_Auth_Process_TargetedID extends PHPUnit_Framework_TestCase
+class Test_Core_Auth_Process_TargetedID extends TestCase
 {
     /**
      * Helper function to run the filter with a given configuration.
@@ -149,13 +151,13 @@ class Test_Core_Auth_Process_TargetedID extends PHPUnit_Framework_TestCase
 //        $request['UserID'] = 'user3@example.org';
 //	$result = self::processFilter($config, $request);
 //	$tid2 = $result['Attributes']['eduPersonTargetedID'][0];
-//        
+//
 //        $this->assertNotEquals($tid1, $tid2);
 //
 //        $request['Destination']['entityid'] = 'urn:example.org:another-sp';
 //	$result = self::processFilter($config, $request);
 //	$tid3 = $result['Attributes']['eduPersonTargetedID'][0];
-//        
+//
 //        $this->assertNotEquals($tid2, $tid3);
 //    }
 
diff --git a/tests/modules/ldap/lib/Auth/Process/BaseFilterTest.php b/tests/modules/ldap/lib/Auth/Process/BaseFilterTest.php
index b21b426ab88d22fd9b82fdfc519d6edbe5626251..08dd8ea52a8b59bd177f9a8997e03fe2f7fff563 100644
--- a/tests/modules/ldap/lib/Auth/Process/BaseFilterTest.php
+++ b/tests/modules/ldap/lib/Auth/Process/BaseFilterTest.php
@@ -1,6 +1,8 @@
 <?php
 
-class sspmod_ldap_Auth_Process_BaseFilter_Test extends PHPUnit_Framework_TestCase
+use PHPUnit\Framework\TestCase;
+
+class sspmod_ldap_Auth_Process_BaseFilter_Test extends TestCase
 {
     public function testVarExportHidesLdapPassword()
     {
diff --git a/tests/modules/saml/lib/Auth/Process/FilterScopesTest.php b/tests/modules/saml/lib/Auth/Process/FilterScopesTest.php
index 4f595c609d4183c4eebad8479a6b4e469554829c..1915c9c0e77316a4a7236e309ce6777ccb891cc9 100644
--- a/tests/modules/saml/lib/Auth/Process/FilterScopesTest.php
+++ b/tests/modules/saml/lib/Auth/Process/FilterScopesTest.php
@@ -8,8 +8,9 @@
 
 namespace SimpleSAML\Test\Module\saml\Auth\Process;
 
+use PHPUnit\Framework\TestCase;
 
-class FilterScopesTest extends \PHPUnit_Framework_TestCase
+class FilterScopesTest extends TestCase
 {
 
     /*
diff --git a/tests/modules/saml/lib/Auth/Source/Auth_Source_SP_Test.php b/tests/modules/saml/lib/Auth/Source/Auth_Source_SP_Test.php
index bb3d065db979d757aa9a474c55706ade050578c3..57baff5ff06b30480f5742dc97dde3f657e614f5 100644
--- a/tests/modules/saml/lib/Auth/Source/Auth_Source_SP_Test.php
+++ b/tests/modules/saml/lib/Auth/Source/Auth_Source_SP_Test.php
@@ -2,6 +2,7 @@
 
 namespace SimpleSAML\Test\Module\saml\Auth\Source;
 
+use PHPUnit\Framework\TestCase;
 use \SimpleSAML_Configuration as Configuration;
 
 /**
@@ -67,7 +68,7 @@ class SP_Tester extends \sspmod_saml_Auth_Source_SP
 /**
  * Set of test cases for sspmod_saml_Auth_Source_SP.
  */
-class SP_Test extends \PHPUnit_Framework_TestCase
+class SP_Test extends TestCase
 {
 
     private $idpMetadata = null;
diff --git a/tests/www/IndexTest.php b/tests/www/IndexTest.php
index aa7df8bb5d68a1b36e6acdf0a0eb05382c6ed4cb..be7d060121a32b0d275f17f41a811a857e9cd9e1 100644
--- a/tests/www/IndexTest.php
+++ b/tests/www/IndexTest.php
@@ -12,9 +12,10 @@ namespace SimpleSAML\Test\Web;
 
 include(dirname(__FILE__).'/../BuiltInServer.php');
 
+use PHPUnit\Framework\TestCase;
 use \SimpleSAML\Test\BuiltInServer;
 
-class IndexTest extends \PHPUnit_Framework_TestCase
+class IndexTest extends TestCase
 {
 
     /**
@@ -70,11 +71,6 @@ class IndexTest extends \PHPUnit_Framework_TestCase
             $this->markTestSkipped('The web-based tests cannot be run in HHVM for the moment.');
         }
 
-        if (version_compare(phpversion(), '5.4') === -1) {
-            // no built-in server prior to 5.4
-            $this->markTestSkipped('The web-based tests cannot be run in PHP versions older than 5.4.');
-        }
-
         // test most basic redirection
         $this->updateConfig(array(
                 'baseurlpath' => 'http://example.org/simplesaml/'
diff --git a/www/_include.php b/www/_include.php
index d8117ee18a458e55e492d9506e8b881a393dc0f0..7c18a2525447ec5ea88eecab9b879fc121af5822 100644
--- a/www/_include.php
+++ b/www/_include.php
@@ -1,33 +1,5 @@
 <?php
 
-/**
- * Disable magic quotes if they are enabled.
- */
-function removeMagicQuotes()
-{
-    if (get_magic_quotes_gpc()) {
-        foreach (array('_GET', '_POST', '_COOKIE', '_REQUEST') as $a) {
-            if (isset($$a) && is_array($$a)) {
-                foreach ($$a as &$v) {
-                    // we don't use array-parameters anywhere. Ignore any that may appear
-                    if (is_array($v)) {
-                        continue;
-                    }
-                    // unescape the string
-                    $v = stripslashes($v);
-                }
-            }
-        }
-    }
-    if (get_magic_quotes_runtime()) {
-        set_magic_quotes_runtime(false);
-    }
-}
-
-if (version_compare(PHP_VERSION, '5.4.0', '<')) {
-    removeMagicQuotes();
-}
-
 // initialize the autoloader
 require_once(dirname(dirname(__FILE__)).'/lib/_autoload.php');