diff --git a/.travis.yml b/.travis.yml
index 030f8371742c7369c4d076cde1bd58dfe9be329d..b15a8136ef2275146a4a7baac9e2aeb7d071caa0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,6 +5,7 @@ php:
 - 5.5
 - 5.6
 - 7.0
+- 7.1
 - hhvm
 matrix:
   allow_failures:
diff --git a/README.md b/README.md
index a5d3a4a9b952106de9cea1c808a5039e24ec6726..6b167c8c8088e0db0188df9b9427c1cc9efe6fe1 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,7 @@
 SimpleSAMLphp
 =============
-[![Build Status](https://travis-ci.org/simplesamlphp/simplesamlphp.svg?branch=master)]
-(https://travis-ci.org/simplesamlphp/simplesamlphp) [![Coverage Status](https://img.shields.io/coveralls/simplesamlphp/simplesamlphp.svg)] 
-(https://coveralls.io/r/simplesamlphp/simplesamlphp)
+[![Build Status](https://travis-ci.org/simplesamlphp/simplesamlphp.svg?branch=master)](https://travis-ci.org/simplesamlphp/simplesamlphp)
+[![Coverage Status](https://img.shields.io/coveralls/simplesamlphp/simplesamlphp.svg)](https://coveralls.io/r/simplesamlphp/simplesamlphp)
 
 This is the official repository of the SimpleSAMLphp software.
 
diff --git a/composer.json b/composer.json
index 0801632ab68e49005dfccc7bbff7d23a7a23d6e9..7dc0538f6078e1504d5a1d66c6590f47e8ee0d1c 100644
--- a/composer.json
+++ b/composer.json
@@ -27,14 +27,15 @@
     },
     "require": {
         "php": ">=5.3",
-		"ext-SPL": "*",
-		"ext-zlib": "*",
-		"ext-pcre": "*",
-		"ext-openssl": "*",
-		"ext-dom": "*",
-		"ext-date": "*",
-		"ext-hash": "*",
-		"ext-json": "*",
+        "ext-SPL": "*",
+        "ext-zlib": "*",
+        "ext-pcre": "*",
+        "ext-openssl": "*",
+        "ext-dom": "*",
+        "ext-date": "*",
+        "ext-hash": "*",
+        "ext-json": "*",
+        "ext-mbstring": "*",
         "simplesamlphp/saml2": "dev-master#f079abe36ab4101dfda654a087f8003a9673b952 as 2.4",
         "robrichards/xmlseclibs": "~2.0",
         "whitehat101/apr1-md5": "~1.0",
@@ -45,7 +46,11 @@
     "require-dev": {
         "ext-pdo_sqlite": "*",
         "phpunit/phpunit": "~4.8",
-        "satooshi/php-coveralls": "^1.0"
+        "satooshi/php-coveralls": "^1.0",
+        "mikey179/vfsStream": "~1.6"
+    },
+    "suggests": {
+        "predis/predis": "1.1.1"
     },
     "support": {
         "issues": "https://github.com/simplesamlphp/simplesamlphp/issues",
diff --git a/composer.lock b/composer.lock
index 39eccf6b723ac11028edad578773f64fa5af4dda..b4af03638c07c58effb366ad1de62f89e3b85f8a 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,8 @@
         "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": "d457492f7cd0fcade4c93ef810d2794c",
+    "hash": "a1ec673cce9acc0b51f539956596145e",
+    "content-hash": "ea73e02f0531f0013dd750a39647e36b",
     "packages": [
         {
             "name": "gettext/gettext",
@@ -64,25 +65,32 @@
                 "po",
                 "translation"
             ],
-            "time": "2016-08-01T18:09:57+00:00"
+            "time": "2016-08-01 18:09:57"
         },
         {
             "name": "gettext/languages",
-            "version": "2.1.2",
+            "version": "2.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/mlocati/cldr-to-gettext-plural-rules.git",
-                "reference": "c43ade7e3fb68bcf2379036513dce8d20553d9c8"
+                "reference": "49c39e51569963cc917a924b489e7025bfb9d8c7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/mlocati/cldr-to-gettext-plural-rules/zipball/c43ade7e3fb68bcf2379036513dce8d20553d9c8",
-                "reference": "c43ade7e3fb68bcf2379036513dce8d20553d9c8",
+                "url": "https://api.github.com/repos/mlocati/cldr-to-gettext-plural-rules/zipball/49c39e51569963cc917a924b489e7025bfb9d8c7",
+                "reference": "49c39e51569963cc917a924b489e7025bfb9d8c7",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3"
             },
+            "require-dev": {
+                "phpunit/phpunit": "^4"
+            },
+            "bin": [
+                "bin/export-plural-rules",
+                "bin/export-plural-rules.php"
+            ],
             "type": "library",
             "autoload": {
                 "psr-4": {
@@ -118,7 +126,7 @@
                 "translations",
                 "unicode"
             ],
-            "time": "2015-03-27T11:32:41+00:00"
+            "time": "2017-03-23 17:02:28"
         },
         {
             "name": "jaimeperez/twig-configurable-i18n",
@@ -162,20 +170,20 @@
                 "translation",
                 "twig"
             ],
-            "time": "2016-10-03T12:34:15+00:00"
+            "time": "2016-10-03 12:34:15"
         },
         {
             "name": "psr/log",
-            "version": "1.0.1",
+            "version": "1.0.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/php-fig/log.git",
-                "reference": "5277094ed527a1c4477177d102fe4c53551953e0"
+                "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/log/zipball/5277094ed527a1c4477177d102fe4c53551953e0",
-                "reference": "5277094ed527a1c4477177d102fe4c53551953e0",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
+                "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
                 "shasum": ""
             },
             "require": {
@@ -209,7 +217,7 @@
                 "psr",
                 "psr-3"
             ],
-            "time": "2016-09-19T16:02:08+00:00"
+            "time": "2016-10-10 12:19:37"
         },
         {
             "name": "robrichards/xmlseclibs",
@@ -250,7 +258,7 @@
                 "xml",
                 "xmldsig"
             ],
-            "time": "2016-09-08T13:15:00+00:00"
+            "time": "2016-09-08 13:15:00"
         },
         {
             "name": "simplesamlphp/saml2",
@@ -269,6 +277,7 @@
             "require": {
                 "ext-dom": "*",
                 "ext-openssl": "*",
+                "ext-zlib": "*",
                 "php": ">=5.3.3",
                 "psr/log": "~1.0",
                 "robrichards/xmlseclibs": "^2.0"
@@ -306,16 +315,16 @@
         },
         {
             "name": "twig/extensions",
-            "version": "v1.4.0",
+            "version": "v1.4.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/twigphp/Twig-extensions.git",
-                "reference": "531eaf4b9ab778b1d7cdd10d40fc6aa74729dfee"
+                "reference": "f0bb8431c8691f5a39f1017d9a5967a082bf01ff"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/twigphp/Twig-extensions/zipball/531eaf4b9ab778b1d7cdd10d40fc6aa74729dfee",
-                "reference": "531eaf4b9ab778b1d7cdd10d40fc6aa74729dfee",
+                "url": "https://api.github.com/repos/twigphp/Twig-extensions/zipball/f0bb8431c8691f5a39f1017d9a5967a082bf01ff",
+                "reference": "f0bb8431c8691f5a39f1017d9a5967a082bf01ff",
                 "shasum": ""
             },
             "require": {
@@ -354,33 +363,34 @@
                 "i18n",
                 "text"
             ],
-            "time": "2016-09-22T16:50:57+00:00"
+            "time": "2016-10-25 17:34:14"
         },
         {
             "name": "twig/twig",
-            "version": "v1.25.0",
+            "version": "v1.33.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/twigphp/Twig.git",
-                "reference": "f16a634ab08d87e520da5671ec52153d627f10f6"
+                "reference": "dd6ca96227917e1e85b41c7c3cc6507b411e0927"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/twigphp/Twig/zipball/f16a634ab08d87e520da5671ec52153d627f10f6",
-                "reference": "f16a634ab08d87e520da5671ec52153d627f10f6",
+                "url": "https://api.github.com/repos/twigphp/Twig/zipball/dd6ca96227917e1e85b41c7c3cc6507b411e0927",
+                "reference": "dd6ca96227917e1e85b41c7c3cc6507b411e0927",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.2.7"
             },
             "require-dev": {
+                "psr/container": "^1.0",
                 "symfony/debug": "~2.7",
-                "symfony/phpunit-bridge": "~2.7"
+                "symfony/phpunit-bridge": "~3.3@dev"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.25-dev"
+                    "dev-master": "1.33-dev"
                 }
             },
             "autoload": {
@@ -415,7 +425,7 @@
             "keywords": [
                 "templating"
             ],
-            "time": "2016-09-21T23:05:12+00:00"
+            "time": "2017-04-20 17:39:48"
         },
         {
             "name": "whitehat101/apr1-md5",
@@ -459,7 +469,7 @@
                 "MD5",
                 "apr1"
             ],
-            "time": "2015-02-11T11:06:42+00:00"
+            "time": "2015-02-11 11:06:42"
         }
     ],
     "packages-dev": [
@@ -515,7 +525,7 @@
                 "constructor",
                 "instantiate"
             ],
-            "time": "2015-06-14T21:17:01+00:00"
+            "time": "2015-06-14 21:17:01"
         },
         {
             "name": "guzzle/guzzle",
@@ -611,7 +621,53 @@
                 "web service"
             ],
             "abandoned": "guzzlehttp/guzzle",
-            "time": "2015-03-18T18:23:50+00:00"
+            "time": "2015-03-18 18:23:50"
+        },
+        {
+            "name": "mikey179/vfsStream",
+            "version": "v1.6.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/mikey179/vfsStream.git",
+                "reference": "0247f57b2245e8ad2e689d7cee754b45fbabd592"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/mikey179/vfsStream/zipball/0247f57b2245e8ad2e689d7cee754b45fbabd592",
+                "reference": "0247f57b2245e8ad2e689d7cee754b45fbabd592",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.6.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "org\\bovigo\\vfs\\": "src/main/php"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Frank Kleine",
+                    "homepage": "http://frankkleine.de/",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Virtual file system to mock the real file system in unit tests.",
+            "homepage": "http://vfs.bovigo.org/",
+            "time": "2016-07-18 14:02:57"
         },
         {
             "name": "phpdocumentor/reflection-common",
@@ -665,7 +721,7 @@
                 "reflection",
                 "static analysis"
             ],
-            "time": "2015-12-27T11:43:31+00:00"
+            "time": "2015-12-27 11:43:31"
         },
         {
             "name": "phpdocumentor/reflection-docblock",
@@ -710,20 +766,20 @@
                 }
             ],
             "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
-            "time": "2016-09-30T07:12:33+00:00"
+            "time": "2016-09-30 07:12:33"
         },
         {
             "name": "phpdocumentor/type-resolver",
-            "version": "0.2",
+            "version": "0.2.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpDocumentor/TypeResolver.git",
-                "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443"
+                "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443",
-                "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443",
+                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb",
+                "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb",
                 "shasum": ""
             },
             "require": {
@@ -757,31 +813,32 @@
                     "email": "me@mikevanriel.com"
                 }
             ],
-            "time": "2016-06-10T07:14:17+00:00"
+            "time": "2016-11-25 06:54:22"
         },
         {
             "name": "phpspec/prophecy",
-            "version": "v1.6.1",
+            "version": "v1.7.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpspec/prophecy.git",
-                "reference": "58a8137754bc24b25740d4281399a4a3596058e0"
+                "reference": "93d39f1f7f9326d746203c7c056f300f7f126073"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0",
-                "reference": "58a8137754bc24b25740d4281399a4a3596058e0",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073",
+                "reference": "93d39f1f7f9326d746203c7c056f300f7f126073",
                 "shasum": ""
             },
             "require": {
                 "doctrine/instantiator": "^1.0.2",
                 "php": "^5.3|^7.0",
                 "phpdocumentor/reflection-docblock": "^2.0|^3.0.2",
-                "sebastian/comparator": "^1.1",
-                "sebastian/recursion-context": "^1.0"
+                "sebastian/comparator": "^1.1|^2.0",
+                "sebastian/recursion-context": "^1.0|^2.0|^3.0"
             },
             "require-dev": {
-                "phpspec/phpspec": "^2.0"
+                "phpspec/phpspec": "^2.5|^3.2",
+                "phpunit/phpunit": "^4.8 || ^5.6.5"
             },
             "type": "library",
             "extra": {
@@ -819,7 +876,7 @@
                 "spy",
                 "stub"
             ],
-            "time": "2016-06-07T08:13:47+00:00"
+            "time": "2017-03-02 20:05:34"
         },
         {
             "name": "phpunit/php-code-coverage",
@@ -881,20 +938,20 @@
                 "testing",
                 "xunit"
             ],
-            "time": "2015-10-06T15:47:00+00:00"
+            "time": "2015-10-06 15:47:00"
         },
         {
             "name": "phpunit/php-file-iterator",
-            "version": "1.4.1",
+            "version": "1.4.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
-                "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0"
+                "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
-                "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
+                "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
                 "shasum": ""
             },
             "require": {
@@ -928,7 +985,7 @@
                 "filesystem",
                 "iterator"
             ],
-            "time": "2015-06-21T13:08:43+00:00"
+            "time": "2016-10-03 07:40:28"
         },
         {
             "name": "phpunit/php-text-template",
@@ -969,29 +1026,34 @@
             "keywords": [
                 "template"
             ],
-            "time": "2015-06-21T13:50:34+00:00"
+            "time": "2015-06-21 13:50:34"
         },
         {
             "name": "phpunit/php-timer",
-            "version": "1.0.8",
+            "version": "1.0.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-timer.git",
-                "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260"
+                "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260",
-                "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
+                "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.3"
+                "php": "^5.3.3 || ^7.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "~4|~5"
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
             "autoload": {
                 "classmap": [
                     "src/"
@@ -1013,20 +1075,20 @@
             "keywords": [
                 "timer"
             ],
-            "time": "2016-05-12T18:03:57+00:00"
+            "time": "2017-02-26 11:10:40"
         },
         {
             "name": "phpunit/php-token-stream",
-            "version": "1.4.8",
+            "version": "1.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-token-stream.git",
-                "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da"
+                "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
-                "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7",
+                "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7",
                 "shasum": ""
             },
             "require": {
@@ -1062,20 +1124,20 @@
             "keywords": [
                 "tokenizer"
             ],
-            "time": "2015-09-15T10:49:45+00:00"
+            "time": "2017-02-27 10:12:30"
         },
         {
             "name": "phpunit/phpunit",
-            "version": "4.8.27",
+            "version": "4.8.35",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "c062dddcb68e44b563f66ee319ddae2b5a322a90"
+                "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c062dddcb68e44b563f66ee319ddae2b5a322a90",
-                "reference": "c062dddcb68e44b563f66ee319ddae2b5a322a90",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/791b1a67c25af50e230f841ee7a9c6eba507dc87",
+                "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87",
                 "shasum": ""
             },
             "require": {
@@ -1091,7 +1153,7 @@
                 "phpunit/php-text-template": "~1.2",
                 "phpunit/php-timer": "^1.0.6",
                 "phpunit/phpunit-mock-objects": "~2.3",
-                "sebastian/comparator": "~1.1",
+                "sebastian/comparator": "~1.2.2",
                 "sebastian/diff": "~1.2",
                 "sebastian/environment": "~1.3",
                 "sebastian/exporter": "~1.2",
@@ -1134,7 +1196,7 @@
                 "testing",
                 "xunit"
             ],
-            "time": "2016-07-21T06:48:14+00:00"
+            "time": "2017-02-06 05:18:07"
         },
         {
             "name": "phpunit/phpunit-mock-objects",
@@ -1190,7 +1252,7 @@
                 "mock",
                 "xunit"
             ],
-            "time": "2015-10-02T06:51:40+00:00"
+            "time": "2015-10-02 06:51:40"
         },
         {
             "name": "satooshi/php-coveralls",
@@ -1248,26 +1310,26 @@
                 "github",
                 "test"
             ],
-            "time": "2016-01-20T17:35:46+00:00"
+            "time": "2016-01-20 17:35:46"
         },
         {
             "name": "sebastian/comparator",
-            "version": "1.2.0",
+            "version": "1.2.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/comparator.git",
-                "reference": "937efb279bd37a375bcadf584dec0726f84dbf22"
+                "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22",
-                "reference": "937efb279bd37a375bcadf584dec0726f84dbf22",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
+                "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.3",
                 "sebastian/diff": "~1.2",
-                "sebastian/exporter": "~1.2"
+                "sebastian/exporter": "~1.2 || ~2.0"
             },
             "require-dev": {
                 "phpunit/phpunit": "~4.4"
@@ -1312,7 +1374,7 @@
                 "compare",
                 "equality"
             ],
-            "time": "2015-07-26T15:48:44+00:00"
+            "time": "2017-01-29 09:50:25"
         },
         {
             "name": "sebastian/diff",
@@ -1364,7 +1426,7 @@
             "keywords": [
                 "diff"
             ],
-            "time": "2015-12-08T07:14:41+00:00"
+            "time": "2015-12-08 07:14:41"
         },
         {
             "name": "sebastian/environment",
@@ -1414,7 +1476,7 @@
                 "environment",
                 "hhvm"
             ],
-            "time": "2016-08-18T05:49:44+00:00"
+            "time": "2016-08-18 05:49:44"
         },
         {
             "name": "sebastian/exporter",
@@ -1481,7 +1543,7 @@
                 "export",
                 "exporter"
             ],
-            "time": "2016-06-17T09:04:28+00:00"
+            "time": "2016-06-17 09:04:28"
         },
         {
             "name": "sebastian/global-state",
@@ -1532,20 +1594,20 @@
             "keywords": [
                 "global state"
             ],
-            "time": "2015-10-12T03:26:01+00:00"
+            "time": "2015-10-12 03:26:01"
         },
         {
             "name": "sebastian/recursion-context",
-            "version": "1.0.2",
+            "version": "1.0.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/recursion-context.git",
-                "reference": "913401df809e99e4f47b27cdd781f4a258d58791"
+                "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791",
-                "reference": "913401df809e99e4f47b27cdd781f4a258d58791",
+                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
+                "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
                 "shasum": ""
             },
             "require": {
@@ -1585,7 +1647,7 @@
             ],
             "description": "Provides functionality to recursively process PHP variables",
             "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
-            "time": "2015-11-11T19:50:13+00:00"
+            "time": "2016-10-03 07:41:43"
         },
         {
             "name": "sebastian/version",
@@ -1620,33 +1682,36 @@
             ],
             "description": "Library that helps with managing the version number of Git-hosted PHP projects",
             "homepage": "https://github.com/sebastianbergmann/version",
-            "time": "2015-06-21T13:59:46+00:00"
+            "time": "2015-06-21 13:59:46"
         },
         {
             "name": "symfony/config",
-            "version": "v3.1.4",
+            "version": "v3.2.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/config.git",
-                "reference": "431d28df9c7bb6e77f8f6289d8670b044fabb9e8"
+                "reference": "8444bde28e3c2a33e571e6f180c2d78bfdc4480d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/config/zipball/431d28df9c7bb6e77f8f6289d8670b044fabb9e8",
-                "reference": "431d28df9c7bb6e77f8f6289d8670b044fabb9e8",
+                "url": "https://api.github.com/repos/symfony/config/zipball/8444bde28e3c2a33e571e6f180c2d78bfdc4480d",
+                "reference": "8444bde28e3c2a33e571e6f180c2d78bfdc4480d",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.5.9",
                 "symfony/filesystem": "~2.8|~3.0"
             },
+            "require-dev": {
+                "symfony/yaml": "~3.0"
+            },
             "suggest": {
                 "symfony/yaml": "To use the yaml reference dumper"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.1-dev"
+                    "dev-master": "3.2-dev"
                 }
             },
             "autoload": {
@@ -1673,40 +1738,43 @@
             ],
             "description": "Symfony Config Component",
             "homepage": "https://symfony.com",
-            "time": "2016-08-27T18:50:07+00:00"
+            "time": "2017-04-04 15:30:56"
         },
         {
             "name": "symfony/console",
-            "version": "v3.1.4",
+            "version": "v3.2.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "8ea494c34f0f772c3954b5fbe00bffc5a435e563"
+                "reference": "c30243cc51f726812be3551316b109a2f5deaf8d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/8ea494c34f0f772c3954b5fbe00bffc5a435e563",
-                "reference": "8ea494c34f0f772c3954b5fbe00bffc5a435e563",
+                "url": "https://api.github.com/repos/symfony/console/zipball/c30243cc51f726812be3551316b109a2f5deaf8d",
+                "reference": "c30243cc51f726812be3551316b109a2f5deaf8d",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.5.9",
+                "symfony/debug": "~2.8|~3.0",
                 "symfony/polyfill-mbstring": "~1.0"
             },
             "require-dev": {
                 "psr/log": "~1.0",
                 "symfony/event-dispatcher": "~2.8|~3.0",
+                "symfony/filesystem": "~2.8|~3.0",
                 "symfony/process": "~2.8|~3.0"
             },
             "suggest": {
                 "psr/log": "For using the console logger",
                 "symfony/event-dispatcher": "",
+                "symfony/filesystem": "",
                 "symfony/process": ""
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.1-dev"
+                    "dev-master": "3.2-dev"
                 }
             },
             "autoload": {
@@ -1733,20 +1801,77 @@
             ],
             "description": "Symfony Console Component",
             "homepage": "https://symfony.com",
-            "time": "2016-08-19T06:48:39+00:00"
+            "time": "2017-04-04 14:33:42"
+        },
+        {
+            "name": "symfony/debug",
+            "version": "v3.2.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/debug.git",
+                "reference": "56f613406446a4a0a031475cfd0a01751de22659"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/debug/zipball/56f613406446a4a0a031475cfd0a01751de22659",
+                "reference": "56f613406446a4a0a031475cfd0a01751de22659",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9",
+                "psr/log": "~1.0"
+            },
+            "conflict": {
+                "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
+            },
+            "require-dev": {
+                "symfony/class-loader": "~2.8|~3.0",
+                "symfony/http-kernel": "~2.8|~3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.2-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Debug\\": ""
+                },
+                "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 Debug Component",
+            "homepage": "https://symfony.com",
+            "time": "2017-03-28 21:38:24"
         },
         {
             "name": "symfony/event-dispatcher",
-            "version": "v2.8.11",
+            "version": "v2.8.19",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/event-dispatcher.git",
-                "reference": "889983a79a043dfda68f38c38b6dba092dd49cd8"
+                "reference": "88b65f0ac25355090e524aba4ceb066025df8bd2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/889983a79a043dfda68f38c38b6dba092dd49cd8",
-                "reference": "889983a79a043dfda68f38c38b6dba092dd49cd8",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/88b65f0ac25355090e524aba4ceb066025df8bd2",
+                "reference": "88b65f0ac25355090e524aba4ceb066025df8bd2",
                 "shasum": ""
             },
             "require": {
@@ -1754,7 +1879,7 @@
             },
             "require-dev": {
                 "psr/log": "~1.0",
-                "symfony/config": "~2.0,>=2.0.5|~3.0.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"
@@ -1793,20 +1918,20 @@
             ],
             "description": "Symfony EventDispatcher Component",
             "homepage": "https://symfony.com",
-            "time": "2016-07-28T16:56:28+00:00"
+            "time": "2017-04-03 20:37:06"
         },
         {
             "name": "symfony/filesystem",
-            "version": "v3.1.4",
+            "version": "v3.2.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "bb29adceb552d202b6416ede373529338136e84f"
+                "reference": "64421e6479c4a8e60d790fb666bd520992861b66"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/bb29adceb552d202b6416ede373529338136e84f",
-                "reference": "bb29adceb552d202b6416ede373529338136e84f",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/64421e6479c4a8e60d790fb666bd520992861b66",
+                "reference": "64421e6479c4a8e60d790fb666bd520992861b66",
                 "shasum": ""
             },
             "require": {
@@ -1815,7 +1940,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.1-dev"
+                    "dev-master": "3.2-dev"
                 }
             },
             "autoload": {
@@ -1842,20 +1967,20 @@
             ],
             "description": "Symfony Filesystem Component",
             "homepage": "https://symfony.com",
-            "time": "2016-07-20T05:44:26+00:00"
+            "time": "2017-03-26 15:47:15"
         },
         {
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.2.0",
+            "version": "v1.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "dff51f72b0706335131b00a7f49606168c582594"
+                "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/dff51f72b0706335131b00a7f49606168c582594",
-                "reference": "dff51f72b0706335131b00a7f49606168c582594",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4",
+                "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4",
                 "shasum": ""
             },
             "require": {
@@ -1867,7 +1992,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.2-dev"
+                    "dev-master": "1.3-dev"
                 }
             },
             "autoload": {
@@ -1901,20 +2026,20 @@
                 "portable",
                 "shim"
             ],
-            "time": "2016-05-18T14:26:46+00:00"
+            "time": "2016-11-14 01:06:16"
         },
         {
             "name": "symfony/stopwatch",
-            "version": "v3.1.4",
+            "version": "v3.2.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/stopwatch.git",
-                "reference": "bb42806b12c5f89db4ebf64af6741afe6d8457e1"
+                "reference": "c5ee0f8650c84b4d36a5f76b3b504233feaabf75"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/stopwatch/zipball/bb42806b12c5f89db4ebf64af6741afe6d8457e1",
-                "reference": "bb42806b12c5f89db4ebf64af6741afe6d8457e1",
+                "url": "https://api.github.com/repos/symfony/stopwatch/zipball/c5ee0f8650c84b4d36a5f76b3b504233feaabf75",
+                "reference": "c5ee0f8650c84b4d36a5f76b3b504233feaabf75",
                 "shasum": ""
             },
             "require": {
@@ -1923,7 +2048,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.1-dev"
+                    "dev-master": "3.2-dev"
                 }
             },
             "autoload": {
@@ -1950,29 +2075,35 @@
             ],
             "description": "Symfony Stopwatch Component",
             "homepage": "https://symfony.com",
-            "time": "2016-06-29T05:41:56+00:00"
+            "time": "2017-02-18 17:28:00"
         },
         {
             "name": "symfony/yaml",
-            "version": "v3.1.4",
+            "version": "v3.2.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/yaml.git",
-                "reference": "f291ed25eb1435bddbe8a96caaef16469c2a092d"
+                "reference": "62b4cdb99d52cb1ff253c465eb1532a80cebb621"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/yaml/zipball/f291ed25eb1435bddbe8a96caaef16469c2a092d",
-                "reference": "f291ed25eb1435bddbe8a96caaef16469c2a092d",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/62b4cdb99d52cb1ff253c465eb1532a80cebb621",
+                "reference": "62b4cdb99d52cb1ff253c465eb1532a80cebb621",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.5.9"
             },
+            "require-dev": {
+                "symfony/console": "~2.8|~3.0"
+            },
+            "suggest": {
+                "symfony/console": "For validating YAML files using the lint command"
+            },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.1-dev"
+                    "dev-master": "3.2-dev"
                 }
             },
             "autoload": {
@@ -1999,24 +2130,24 @@
             ],
             "description": "Symfony Yaml Component",
             "homepage": "https://symfony.com",
-            "time": "2016-09-02T02:12:52+00:00"
+            "time": "2017-03-20 09:45:15"
         },
         {
             "name": "webmozart/assert",
-            "version": "1.1.0",
+            "version": "1.2.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/webmozart/assert.git",
-                "reference": "bb2d123231c095735130cc8f6d31385a44c7b308"
+                "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/webmozart/assert/zipball/bb2d123231c095735130cc8f6d31385a44c7b308",
-                "reference": "bb2d123231c095735130cc8f6d31385a44c7b308",
+                "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f",
+                "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.3.3|^7.0"
+                "php": "^5.3.3 || ^7.0"
             },
             "require-dev": {
                 "phpunit/phpunit": "^4.6",
@@ -2025,7 +2156,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.2-dev"
+                    "dev-master": "1.3-dev"
                 }
             },
             "autoload": {
@@ -2049,7 +2180,7 @@
                 "check",
                 "validate"
             ],
-            "time": "2016-08-09T15:02:57+00:00"
+            "time": "2016-11-23 20:04:58"
         }
     ],
     "aliases": [
@@ -2075,7 +2206,8 @@
         "ext-dom": "*",
         "ext-date": "*",
         "ext-hash": "*",
-        "ext-json": "*"
+        "ext-json": "*",
+        "ext-mbstring": "*"
     },
     "platform-dev": {
         "ext-pdo_sqlite": "*"
diff --git a/config-templates/authsources.php b/config-templates/authsources.php
index 049cad5541a2c469617ed2f0566c78161e0ea48f..6afe417811d36c5248af6fa115dc3001d2bf1456 100644
--- a/config-templates/authsources.php
+++ b/config-templates/authsources.php
@@ -51,11 +51,16 @@ $config = array(
         /*
          * The attributes parameter must contain an array of desired attributes by the SP.
          * The attributes can be expressed as an array of names or as an associative array
-         * in the form of 'friendlyName' => 'name'.
+         * in the form of 'friendlyName' => 'name'. This feature requires 'name' to be set.
          * The metadata will then be created as follows:
          * <md:RequestedAttribute FriendlyName="friendlyName" Name="name" />
          */
-        /*'attributes' => array(
+        /*'name' => array(
+             'en' => 'A service',
+             'no' => 'En tjeneste',
+          ),
+
+          'attributes' => array(
             'attrname' => 'urn:oid:x.x.x.x',
         ),*/
         /*'attributes.required' => array (
diff --git a/config-templates/config.php b/config-templates/config.php
index 65f9c1fe15f8b9a2f315ac3495c1d7c23bcd8577..f25f30b1e955ba0c01dd60ddf21254cb4f444aa0 100644
--- a/config-templates/config.php
+++ b/config-templates/config.php
@@ -503,7 +503,7 @@ $config = array(
     /*
      * Options to override the default settings for php sessions.
      */
-    'session.phpsession.cookiename' => null,
+    'session.phpsession.cookiename' => 'SimpleSAML',
     'session.phpsession.savepath' => null,
     'session.phpsession.httponly' => true,
 
@@ -725,14 +725,19 @@ $config = array(
      *
      * By default, twig templates are not cached. To turn on template caching:
      * Set 'template.cache' to an absolute path pointing to a directory that
-     * SimpleSAMLphp has read and write permissions to. Then, set
-     * 'template.auto_reload' to false.
-     *
-     * When upgrading or changing themes, delete the contents of the cache.
+     * SimpleSAMLphp has read and write permissions to.
      */
-    'template.auto_reload' => true,
     //'template.cache' => '',
 
+    /*
+     * Set the 'template.auto_reload' to true if you would like SimpleSAMLphp to
+     * recompile the templates (when using the template cache) if the templates
+     * change. If you don't want to check the source templates for every request,
+     * set it to false.
+     */
+    'template.auto_reload' => false,
+
+
 
     /*********************
      | DISCOVERY SERVICE |
@@ -975,6 +980,7 @@ $config = array(
      * - 'phpsession': Limited datastore, which uses the PHP session.
      * - 'memcache': Key-value datastore, based on memcache.
      * - 'sql': SQL datastore, using PDO.
+     * - 'redis': Key-value datastore, based on redis.
      *
      * The default datastore is 'phpsession'.
      *
@@ -1000,4 +1006,15 @@ $config = array(
      * The prefix we should use on our tables.
      */
     'store.sql.prefix' => 'SimpleSAMLphp',
+
+    /*
+     * The hostname and port of the Redis datastore instance.
+     */
+    'store.redis.host' => 'localhost',
+    'store.redis.port' => 6379,
+
+    /*
+     * The prefix we should use on our Redis datastore.
+     */
+    'store.redis.prefix' => 'SimpleSAMLphp',
 );
diff --git a/dictionaries/admin.translation.json b/dictionaries/admin.translation.json
index a32cad344ddca03d1c096c992391f132c6c7e13e..28742f46bead11bf762aba2dcce6829192db3cbb 100644
--- a/dictionaries/admin.translation.json
+++ b/dictionaries/admin.translation.json
@@ -152,7 +152,7 @@
 		"it": "Nessun errore trovato.",
 		"ja": "\u30a8\u30e9\u30fc\u306f\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002",
 		"lt": "Klaid\u0173 nerasta.",
-		"zh-tw": "\u6c92\u6709\u932f\u8aa4\u3002",
+		"zh-tw": "\u6c92\u6709\u767c\u73fe\u932f\u8aa4\u3002",
 		"et": "T\u00f5rkeid ei leitud",
 		"he": "\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05e9\u05d2\u05d9\u05d0\u05d5\u05ea.",
 		"zh": "\u6ca1\u6709\u53d1\u73b0\u9519\u8bef",
@@ -185,7 +185,7 @@
 		"it": "Tornare indietro alla lista dei file",
 		"ja": "\u30d5\u30a1\u30a4\u30eb\u30ea\u30b9\u30c8\u306b\u623b\u308b",
 		"lt": "Gr\u012f\u017eti \u012f fail\u0173 s\u0105ra\u0161\u0105",
-		"zh-tw": "\u56de\u5230\u6a94\u6848\u6e05\u55ae",
+		"zh-tw": "\u56de\u5230\u6a94\u6848\u5217\u8868",
 		"et": "Mine tagasi failide nimekirja",
 		"he": "\u05d7\u05d6\u05d5\u05e8 \u05d0\u05dc \u05e8\u05e9\u05d9\u05de\u05ea \u05d4\u05e7\u05d1\u05e6\u05d9\u05dd",
 		"zh": "\u8fd4\u56de\u81f3\u6587\u4ef6\u5217\u8868",
@@ -448,7 +448,7 @@
 		"it": "I seguenti campi non sono stati riconosciuti",
 		"ja": "\u4ee5\u4e0b\u306e\u9805\u76ee\u306f\u8a8d\u8b58\u3055\u308c\u307e\u305b\u3093\u3067\u3057\u305f",
 		"lt": "\u0160ie laukai neatpa\u017einti",
-		"zh-tw": "\u4e0b\u5217\u8cc7\u6599\u672a\u7d93\u78ba\u8a8d",
+		"zh-tw": "\u4e0b\u5217\u6b04\u4f4d\u7121\u6cd5\u8b58\u5225",
 		"et": "J\u00e4rgmistest v\u00e4ljadest ei saadud aru",
 		"he": "\u05d4\u05e9\u05d3\u05d5\u05ea \u05d4\u05d1\u05d0\u05d9\u05dd \u05dc\u05d0 \u05d6\u05d5\u05d4\u05d5",
 		"zh": "\u4e0b\u5217\u533a\u57df\u65e0\u6cd5\u8bc6\u522b",
@@ -547,7 +547,7 @@
 		"it": "Si sta per inviare un messaggio. Premere il pulsante di invio per continuare.",
 		"ja": "\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u9001\u4fe1\u3057\u307e\u3059\u3002\u7d9a\u3051\u308b\u306b\u306f\u30e1\u30c3\u30bb\u30fc\u30b8\u9001\u4fe1\u30ea\u30f3\u30af\u3092\u62bc\u3057\u3066\u304f\u3060\u3055\u3044\u3002",
 		"lt": "J\u016bs\u0173 prane\u0161imas siun\u010diamas. Nor\u0117dami t\u0119sti, paspauskite prane\u0161imo patvirtinimo nuorod\u0105.",
-		"zh-tw": "\u60a8\u6b63\u5728\u50b3\u9001\u4e00\u5247\u8a0a\u606f\uff0c\u8acb\u9ede\u9078\u63d0\u4ea4\u8a0a\u606f\u9023\u7d50\u4f86\u7e7c\u7e8c\u3002",
+		"zh-tw": "\u60a8\u6b63\u5728\u50b3\u9001\u4e00\u5247\u8a0a\u606f\uff0c\u8acb\u9ede\u9078\u50b3\u9001\u8a0a\u606f\u9023\u7d50\u4f86\u7e7c\u7e8c\u3002",
 		"et": "Oled teadet saatmas. J\u00e4tkamiseks vajuta teateviidet.",
 		"he": "\u05d0\u05ea\u05d4 \u05e2\u05d5\u05de\u05d3 \u05dc\u05e9\u05dc\u05d5\u05d7 \u05d4\u05d5\u05d3\u05e2\u05d4. \u05dc\u05d7\u05e5 \u05e2\u05dc \u05db\u05e4\u05ea\u05d5\u05e8 \u05d4\u05e9\u05dc\u05d9\u05d7\u05d4 \u05db\u05d3\u05d9 \u05dc\u05d4\u05de\u05e9\u05d9\u05da.",
 		"zh": "\u4f60\u51c6\u5907\u53d1\u9001\u4e00\u4e2a\u6d88\u606f\uff0c\u8bf7\u70b9\u51fb\u63d0\u4ea4\u94fe\u63a5\u4ee5\u7ee7\u7eed",
@@ -580,7 +580,7 @@
 		"it": "Invio messaggio",
 		"ja": "\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u9001\u4fe1",
 		"lt": "Patvirtinti prane\u0161im\u0105",
-		"zh-tw": "\u63d0\u4ea4\u8a0a\u606f",
+		"zh-tw": "\u50b3\u9001\u8a0a\u606f",
 		"et": "Saada teade",
 		"he": "\u05e9\u05dc\u05d7 \u05d4\u05d5\u05d3\u05e2\u05d4",
 		"zh": "\u63d0\u4ea4\u4fe1\u606f",
@@ -644,7 +644,7 @@
 		"it": "Poich\u00e8 ci si trova in modalit\u00e0 di debug, si pu\u00f2 vedere il contenuto del messaggio che si sta per inviare:",
 		"ja": "\u304a\u6c17\u3065\u304d\u306e\u69d8\u306b\u3042\u306a\u305f\u306f\u30c7\u30d0\u30c3\u30b0\u30e2\u30fc\u30c9\u306b\u3044\u307e\u3059\u3002\u3042\u306a\u305f\u306f\u9001\u4fe1\u3059\u308b\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u5185\u5bb9\u3092\u898b\u308b\u3053\u3068\u304c\u51fa\u6765\u307e\u3059\u3002",
 		"lt": "\u012ejungtas detalus nar\u0161ymas, tod\u0117l matote siun\u010diamos \u017einut\u0117s turin\u012f:",
-		"zh-tw": "\u7576\u60a8\u5728\u9664\u932f\u6a21\u5f0f\uff0c\u60a8\u53ef\u4ee5\u770b\u5230\u60a8\u6240\u50b3\u905e\u7684\u8a0a\u606f\u5167\u5bb9\uff1a",
+		"zh-tw": "\u7576\u60a8\u5728\u9664\u932f\u6a21\u5f0f\u6642\uff0c\u60a8\u5c07\u53ef\u4ee5\u770b\u5230\u60a8\u6240\u50b3\u9001\u7684\u8a0a\u606f\u5167\u5bb9\uff1a",
 		"et": "Kuna oled silumisre\u017eiimis, siis on sul v\u00f5imalik n\u00e4ha saadetava teate sisu:",
 		"he": "\u05db\u05d9\u05d5\u05d5\u05df \u05e9\u05d0\u05ea\u05d4 \u05d1\u05de\u05e6\u05d1 \u05de\u05d1\u05d3\u05d9\u05e7\u05ea \u05d1\u05d0\u05d2\u05d9\u05dd, \u05d0\u05ea\u05d4 \u05e8\u05d5\u05d0\u05d4 \u05d0\u05ea \u05ea\u05d5\u05db\u05df \u05d4\u05d4\u05d5\u05d3\u05e2\u05d4 \u05e9\u05d0\u05ea\u05d4 \u05e9\u05d5\u05dc\u05d7:",
 		"zh": "\u5f53\u4f60\u5904\u5728\u8c03\u8bd5\u6a21\u5f0f\u4e2d\u65f6\uff0c\u4f60\u5c06\u770b\u5230\u4f60\u6b63\u5728\u53d1\u9001\u7684\u6d88\u606f\u7684\u5185\u5bb9",
@@ -677,7 +677,7 @@
 		"it": "SAML 2.0 Service Provider (Remoto)",
 		"ja": "SAML 2.0\u30b5\u30fc\u30d3\u30b9\u30d7\u30ed\u30d0\u30a4\u30c0(\u30ea\u30e2\u30fc\u30c8)",
 		"lt": "SAML 2.0 Paslaugos teik\u0117jas (nutol\u0119s)",
-		"zh-tw": "SAML 2.0 \u670d\u52d9\u63d0\u4f9b\u8005(\u9060\u7aef)",
+		"zh-tw": "SAML 2.0 \u670d\u52d9\u63d0\u4f9b\u8005 (\u9060\u7aef)",
 		"et": "SAML 2.0 teenusepakkuja (kaug)",
 		"he": "\u05e1\u05e4\u05e7 \u05e9\u05d9\u05e8\u05d5\u05ea \u05de\u05e8\u05d5\u05d7\u05e7 \u05de\u05e1\u05d5\u05d2 SAML 2.0",
 		"zh": "SAML 2.0 \u670d\u52a1\u63d0\u4f9b\u8005 (\u8fdc\u7a0b)",
@@ -710,7 +710,7 @@
 		"it": "SAML 2.o Identity Provider (Hosted)",
 		"ja": "SAML 2.0\u30a2\u30a4\u30c7\u30f3\u30c6\u30a3\u30c6\u30a3\u30d7\u30ed\u30d0\u30a4\u30c0(\u30db\u30b9\u30c8)",
 		"lt": "SAML 2.0 Tapatybi\u0173 teik\u0117jas (vietinis)",
-		"zh-tw": "SAML 2.0 \u9a57\u8b49\u63d0\u4f9b\u8005(\u4e3b\u6a5f)",
+		"zh-tw": "SAML 2.0 \u9a57\u8b49\u63d0\u4f9b\u8005 (\u672c\u5730)",
 		"et": "SAML 2.0 identiteedipakkuja (hostitud)",
 		"he": "\u05e1\u05e4\u05e7 \u05d6\u05d4\u05d5\u05ea \u05de\u05e7\u05d5\u05de\u05d9 \u05de\u05e1\u05d5\u05d2 SAML 2.0",
 		"zh": "SAML 2.0 \u8eab\u4efd\u63d0\u4f9b\u8005\uff08\u672c\u5730\uff09",
@@ -743,7 +743,7 @@
 		"it": "SAML 2.0 Identity Provider (Remoto)",
 		"ja": "SAML 2.0\u30a2\u30a4\u30c7\u30f3\u30c6\u30a3\u30c6\u30a3\u30d7\u30ed\u30d0\u30a4\u30c0(\u30ea\u30e2\u30fc\u30c8)",
 		"lt": "SAML 2.0 Tapatybi\u0173 teik\u0117jas (nutol\u0119s)",
-		"zh-tw": "SAML 2.0 \u9a57\u8b49\u63d0\u4f9b\u8005(\u9060\u7aef)",
+		"zh-tw": "SAML 2.0 \u9a57\u8b49\u63d0\u4f9b\u8005 (\u9060\u7aef)",
 		"et": "SAML 2.0 identiteedipakkuja (hostitud)",
 		"he": "\u05e1\u05e4\u05e7 \u05d6\u05d4\u05d5\u05ea \u05de\u05e8\u05d5\u05d7\u05e7 \u05de\u05e1\u05d5\u05d2 SAML 2.0",
 		"zh": "SAML 2.0 \u8eab\u4efd\u63d0\u4f9b\u8005\uff08\u8fdc\u7a0b\uff09",
@@ -776,7 +776,7 @@
 		"it": "Shib 1.3 Service Provider (Hosted)",
 		"ja": "Shib 1.3\u30b5\u30fc\u30d3\u30b9\u30d7\u30ed\u30d0\u30a4\u30c0(\u30db\u30b9\u30c8)",
 		"lt": "Shib 1.3 Paslaugos teik\u0117jas (vietinis)",
-		"zh-tw": "Shib 1.3 \u670d\u52d9\u63d0\u4f9b\u8005(\u4e3b\u6a5f)",
+		"zh-tw": "Shib 1.3 \u670d\u52d9\u63d0\u4f9b\u8005 (\u672c\u5730)",
 		"et": "Shib 1.3 teenusepakkuja (hostitud)",
 		"he": "\u05e1\u05e4\u05e7 \u05e9\u05d9\u05e8\u05d5\u05ea \u05de\u05e7\u05d5\u05de\u05d9 \u05de\u05e1\u05d5\u05d2 Shib 1.3",
 		"zh": "Shib 1.3 \u670d\u52a1\u63d0\u4f9b\u8005\uff08\u672c\u5730\uff09",
@@ -809,7 +809,7 @@
 		"it": "Shib 1.3 Service Provider (Remoto)",
 		"ja": "Shib 1.3\u30b5\u30fc\u30d3\u30b9\u30d7\u30ed\u30d0\u30a4\u30c0(\u30ea\u30e2\u30fc\u30c8)",
 		"lt": "Shib 1.3 Paslaugos teik\u0117jas (nutol\u0119s)",
-		"zh-tw": "Shib 1.3 \u670d\u52d9\u63d0\u4f9b\u8005(\u9060\u7aef)",
+		"zh-tw": "Shib 1.3 \u670d\u52d9\u63d0\u4f9b\u8005 (\u9060\u7aef)",
 		"et": "Shib 1.3 teenusepakkuja (kaug)",
 		"he": "\u05e1\u05e4\u05e7 \u05e9\u05d9\u05e8\u05d5\u05ea \u05de\u05e8\u05d5\u05d7\u05e7 \u05de\u05e1\u05d5\u05d2 Shib 1.3",
 		"zh": "Shib 1.3 \u670d\u52a1\u63d0\u4f9b\u8005\uff08\u8fdc\u7a0b\uff09",
@@ -842,7 +842,7 @@
 		"it": "Shib 1.3 Identity Provider (Hosted)",
 		"ja": "Shib 1.3\u30a2\u30a4\u30c7\u30f3\u30c6\u30a3\u30c6\u30a3\u30d7\u30ed\u30d0\u30a4\u30c0(\u30db\u30b9\u30c8)",
 		"lt": "Shib 1.3 Tapatybi\u0173 teik\u0117jas (vietinis)",
-		"zh-tw": "Shib 1.3 \u9a57\u8b49\u63d0\u4f9b\u8005(\u4e3b\u6a5f)",
+		"zh-tw": "Shib 1.3 \u9a57\u8b49\u63d0\u4f9b\u8005 (\u672c\u5730)",
 		"et": "Shib 1.3 identiteedipakkuja (hostitud)",
 		"he": "\u05e1\u05e4\u05e7 \u05d6\u05d4\u05d5\u05ea \u05de\u05e7\u05d5\u05de\u05d9 \u05de\u05e1\u05d5\u05d2 Shib 1.3",
 		"zh": "Shib 1.3 \u8ba4\u8bc1\u63d0\u4f9b\u8005\uff08\u672c\u5730\uff09",
@@ -875,7 +875,7 @@
 		"it": "Shib 1.3 Identity Provider (Remoto)",
 		"ja": "Shib 1.3\u30a2\u30a4\u30c7\u30f3\u30c6\u30a3\u30c6\u30a3\u30d7\u30ed\u30d0\u30a4\u30c0(\u30ea\u30e2\u30fc\u30c8)",
 		"lt": "Shib 1.3 Tapatybi\u0173 teik\u0117jas (nutol\u0119s)",
-		"zh-tw": "Shib 1.3 \u9a57\u8b49\u63d0\u4f9b\u8005(\u9060\u7aef)",
+		"zh-tw": "Shib 1.3 \u9a57\u8b49\u63d0\u4f9b\u8005 (\u9060\u7aef)",
 		"et": "Shib 1.3 identiteedipakkuja (kaug)",
 		"he": "\u05e1\u05e4\u05e7 \u05d6\u05d4\u05d5\u05ea \u05de\u05e8\u05d5\u05d7\u05e7 \u05de\u05e1\u05d5\u05d2 Shib 1.3",
 		"zh": "Shib 1.3 \u8ba4\u8bc1\u63d0\u4f9b\u8005\uff08\u8fdc\u7a0b\uff09",
@@ -908,7 +908,7 @@
 		"it": "WS-Federation Service Provider (Hosted)",
 		"ja": "WS-Federation\u30b5\u30fc\u30d3\u30b9\u30d7\u30ed\u30d0\u30a4\u30c0(\u30db\u30b9\u30c8)",
 		"lt": "WS-Federacijos Paslaugos teik\u0117jas (vietinis)",
-		"zh-tw": "WS-Federation \u670d\u52d9\u63d0\u4f9b\u8005(\u4e3b\u6a5f)",
+		"zh-tw": "WS-Federation \u670d\u52d9\u63d0\u4f9b\u8005 (\u672c\u5730)",
 		"et": "WS-Federation teenusepakkuja (hostitud)",
 		"he": "\u05e1\u05e4\u05e7 \u05e9\u05d9\u05e8\u05d5\u05ea \u05de\u05e7\u05d5\u05de\u05d9 \u05de\u05e1\u05d5\u05d2 \u05d0\u05d9\u05d7\u05d5\u05d3-WS",
 		"zh": "WS-Federation \u670d\u52a1\u63d0\u4f9b\u8005\uff08\u672c\u5730\uff09",
@@ -941,7 +941,7 @@
 		"it": "WS-Federation Identity Provider (Remoto)",
 		"ja": "WS-Federation\u30a2\u30a4\u30c7\u30f3\u30c6\u30a3\u30c6\u30a3\u30d7\u30ed\u30d0\u30a4\u30c0(\u30ea\u30e2\u30fc\u30c8)",
 		"lt": "WS-Federacijos Paslaugos teik\u0117jas (nutol\u0119s)",
-		"zh-tw": "WS-Federation \u9a57\u8b49\u63d0\u4f9b\u8005(\u9060\u7aef)",
+		"zh-tw": "WS-Federation \u9a57\u8b49\u63d0\u4f9b\u8005 (\u9060\u7aef)",
 		"et": "WS-Federation identiteedipakkuja (kaug)",
 		"he": "\u05e1\u05e4\u05e7 \u05d6\u05d4\u05d5\u05ea \u05de\u05e8\u05d5\u05d7\u05e7 \u05de\u05e1\u05d5\u05d2 \u05d0\u05d9\u05d7\u05d5\u05d3-WS",
 		"zh": "WS-Federation \u8eab\u4efd\u63d0\u4f9b\u8005\uff08\u8fdc\u7a0b\uff09",
@@ -988,6 +988,7 @@
 		"el": "\u0391\u03bd\u03b1\u03bb\u03c5\u03c4\u03ae\u03c2 (parser) \u03bc\u03b5\u03c4\u03b1\u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd"
 	},
 	"metaconv_selectfile": {
+		"zh-tw": "\u6216\u9078\u64c7\u4e00\u500b\u6a94\u6848\uff1a",
 		"el": "\u03ae \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf"
 	},
 	"metaconv_xmlmetadata": {
@@ -1240,7 +1241,7 @@
 		"it": "Questi sono i metadati che SimpleSAMLphp ha generato e che possono essere inviati ai partner fidati per creare una federazione tra siti.",
 		"ja": "\u3053\u3053\u306f SimpleSAMLphp \u304c\u751f\u6210\u3057\u305f\u30e1\u30bf\u30c7\u30fc\u30bf\u304c\u3042\u308a\u307e\u3059\u3002\u3042\u306a\u305f\u306f\u4fe1\u983c\u3059\u308b\u30d1\u30fc\u30c8\u30ca\u30fc\u306b\u3053\u306e\u30e1\u30bf\u30c7\u30fc\u30bf\u3092\u9001\u4fe1\u3057\u4fe1\u983c\u3055\u308c\u305f\u9023\u643a\u3092\u69cb\u7bc9\u51fa\u6765\u307e\u3059\u3002",
 		"lt": "Metaduomenys, kuriuos Jums sugeneravo SimpleSAMLphp. Norint \u012fsteigti patikim\u0105 federacij\u0105, galite patikimiems partneriams i\u0161si\u0173sti \u0161iuos metaduomenis.",
-		"zh-tw": "\u9019\u662f SimpleSAMLphp \u7522\u751f\u7d66\u60a8\u7684 Metadata\uff0c\u60a8\u53ef\u4ee5\u50b3\u9001\u6b64 Metadata \u6587\u4ef6\u7d66\u60a8\u4fe1\u4efb\u7684\u5408\u4f5c\u5925\u4f34\u4f86\u5efa\u7acb\u53ef\u4fe1\u4efb\u7684\u806f\u76df\u3002",
+		"zh-tw": "\u9019\u662f SimpleSAMLphp \u7522\u751f\u7d66\u60a8\u7684 Metadata\uff0c\u60a8\u53ef\u4ee5\u50b3\u9001\u6b64 Metadata \u6587\u4ef6\u7d66\u60a8\u4fe1\u4efb\u7684\u5408\u4f5c\u5925\u4f34\u4f86\u5efa\u7acb\u4fe1\u4efb\u806f\u76df\u3002",
 		"et": "Need on SimpleSAMLphp poolt sulle genereeritud metaandmed. V\u00f5id saata need metaandmed usaldatavatele partneritele usaldatava f\u00f6deratsiooni loomiseks.",
 		"he": "\u05d4\u05e0\u05d4 \u05d4\u05de\u05d8\u05d0-\u05de\u05d9\u05d3\u05e2 \u05e9 SimpleSAMLphp \u05d9\u05d9\u05e6\u05e8 \u05e2\u05d1\u05d5\u05e8\u05da. \u05d0\u05ea\u05d4 \u05d9\u05db\u05d5\u05dc \u05dc\u05e9\u05dc\u05d5\u05d7 \u05d0\u05ea \u05de\u05e1\u05de\u05da \u05d4\u05de\u05d8\u05d0-\u05de\u05d9\u05d3\u05e2 \u05dc\u05e9\u05d5\u05ea\u05e4\u05d9\u05dd \u05de\u05d4\u05d9\u05de\u05e0\u05d9\u05dd \u05db\u05d3\u05d9 \u05dc\u05d9\u05e6\u05d5\u05e8 \u05d0\u05d9\u05d7\u05d5\u05d3 \u05de\u05d0\u05d5\u05d1\u05d8\u05d7. ",
 		"zh": "\u8fd9\u91cc\u662fSimpleSAMLphp\u4e3a\u4f60\u751f\u6210\u7684\u5143\u4fe1\u606f\uff0c\u4f60\u5e94\u8be5\u53d1\u9001\u8fd9\u4e2a\u5143\u4fe1\u606f\u6587\u6863\u7ed9\u4f60\u7684\u4fe1\u4efb\u7684\u5408\u4f5c\u4f19\u4f34\u4ee5\u5efa\u7acb\u4fe1\u4efb\u7684\u8054\u76df",
@@ -1273,7 +1274,7 @@
 		"it": "Si possono <a href=\"%METAURL%\">ottenere i metadati in XML dall'URL dedicata<\/a>:",
 		"ja": "<a href=\"%METAURL%\">\u3053\u306eURL\u3067\u30e1\u30bf\u30c7\u30fc\u30bf\u306eXML\u3092\u53d6\u5f97\u3067\u304d\u307e\u3059<\/a>:",
 		"lt": "J\u016bs galite <a href=\"%METAURL%\">gauti metaduomenis XML formatu<\/a>:",
-		"zh-tw": "<a href=\"%METAURL%\"> \u76f4\u63a5\u53d6\u5f97 Metadata XML \u683c\u5f0f\u6a94 <\/a>",
+		"zh-tw": "\u60a8\u53ef\u4ee5\u5728 <a href=\"%METAURL%\"> \u76f4\u63a5\u53d6\u5f97 Metadata XML \u683c\u5f0f\u6a94<\/a>",
 		"et": "<a href=\"%METAURL%\">Metaandmete XML-i on v\u00f5imalik saada spetsiaalselt aadressilt<\/a>:",
 		"he": "\u05d0\u05ea\u05d4 \u05d9\u05db\u05d5\u05dc <a href=\"%METAURL%\">\u05dc\u05e7\u05d1\u05dc \u05d0\u05ea \u05d4\u05de\u05d8\u05d0 \u05de\u05d9\u05d3\u05e2 \u05d1\u05db\u05ea\u05d5\u05d1\u05ea \u05e0\u05e4\u05e8\u05d3\u05ea<\/a>:",
 		"zh": "\u4f60\u53ef\u4ee5\u5728 <a href=\"%METAURL%\">\u83b7\u53d6\u5143\u4fe1\u606fXML<\/a>",
@@ -1305,7 +1306,7 @@
 		"it": "In formato flat per SimpleSAMLphp - da utilizzare se dall'altra parte c'\u00e8 un'entit\u00e0 che utilizza SimpleSAMLphp",
 		"ja": "SimpleSAMLphp \u306e\u30d5\u30a1\u30a4\u30eb\u30d5\u30a9\u30fc\u30de\u30c3\u30c8 - \u7247\u5074\u3067\u3082 SimpleSAMLphp\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u3092\u4f7f\u7528\u3059\u308b\u5834\u5408\u306b\u3053\u308c\u3092\u4f7f\u7528\u3057\u307e\u3059:",
 		"lt": "SimpleSAMLphp paprasto failo formatas - naudokite j\u012f, jei naudojate SimpleSAMLphp kitoje esyb\u0117je:",
-		"zh-tw": "\u5982\u679c\u60a8\u9700\u8981\u65bc\u5176\u4ed6\u5730\u65b9\u4f7f\u7528 SimpleSAMLphp \u5be6\u9ad4 - \u8acb\u53c3\u95b1 SimpleSAMLphp \u5e73\u9762\u6587\u4ef6\u683c\u5f0f\uff1a",
+		"zh-tw": "\u5982\u679c\u60a8\u9700\u8981\u65bc\u5176\u4ed6\u7ad9\u53f0\u4f7f\u7528 SimpleSAMLphp - \u8acb\u53c3\u95b1 SimpleSAMLphp \u5e73\u9762\u6587\u4ef6\u683c\u5f0f\uff1a",
 		"et": "SimpleSAMLphp formaadis: kasuta seda siis, kui ka teine pool kasutab SimpleSAMLphp-d:",
 		"he": "\u05d1\u05ea\u05d1\u05e0\u05d9\u05ea \u05e7\u05d5\u05d1\u05e5 SimpleSAMLphp \u05e9\u05d8\u05d5\u05d7 - \u05dc\u05de\u05e7\u05e8\u05d9\u05dd \u05d1\u05d4\u05dd \u05d0\u05ea\u05d4 \u05de\u05e9\u05ea\u05de\u05e9 \u05d1\u05d9\u05e9\u05d5\u05ea SimpleSAMLphp \u05d1\u05e6\u05d3 \u05d4\u05e9\u05e0\u05d9: ",
 		"zh": "\u5982\u679c\u4f60\u60f3\u5728\u5176\u4ed6\u7f51\u7ad9\u4f7f\u7528\u7684SimpleSAMLphp\uff0c\u90a3\u4e48\u4f60\u5e94\u8be5\u4f7f\u7528SimpleSAMLphp\u6241\u5e73\u7684\u6587\u4ef6\u683c\u5f0f",
@@ -1339,7 +1340,7 @@
 		"it": "Invio del messaggio",
 		"ja": "\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u9001\u4fe1\u4e2d",
 		"lt": "Siun\u010diamas prane\u0161imas",
-		"zh-tw": "\u50b3\u9001\u8a0a\u606f",
+		"zh-tw": "\u8a0a\u606f\u50b3\u9001\u4e2d",
 		"et": "Teate saatmine",
 		"he": "\u05e9\u05d5\u05dc\u05d7 \u05d4\u05d5\u05d3\u05e2\u05d4",
 		"zh": "\u6b63\u5728\u53d1\u9001\u6d88\u606f",
@@ -1371,7 +1372,7 @@
 		"it": "Si sta per inviare un messaggio. Premere il pulsante di invio per continuare.",
 		"ja": "\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u9001\u4fe1\u3057\u307e\u3059\u3002\u7d9a\u3051\u308b\u306b\u306f\u30e1\u30c3\u30bb\u30fc\u30b8\u9001\u4fe1\u30dc\u30bf\u30f3\u3092\u62bc\u3057\u3066\u304f\u3060\u3055\u3044\u3002",
 		"lt": "J\u016bs\u0173 prane\u0161imas siun\u010diamas. Nor\u0117dami t\u0119sti, paspauskite prane\u0161imo patvirtinimo mygtuk\u0105.",
-		"zh-tw": "\u60a8\u6b63\u5728\u50b3\u9001\u4e00\u5247\u8a0a\u606f\uff0c\u8acb\u9ede\u9078\u63d0\u4ea4\u8a0a\u606f\u6309\u9215\u4f86\u7e7c\u7e8c\u3002",
+		"zh-tw": "\u60a8\u6b63\u5728\u50b3\u9001\u4e00\u5247\u8a0a\u606f\uff0c\u8acb\u9ede\u9078\u50b3\u9001\u8a0a\u606f\u6309\u9215\u4f86\u7e7c\u7e8c\u3002",
 		"et": "Oled teadet saatmas. J\u00e4tkamiseks vajuta teatesaatmisnuppu.",
 		"he": "\u05d0\u05ea\u05d4 \u05e2\u05d5\u05de\u05d3 \u05dc\u05e9\u05dc\u05d5\u05d7 \u05d4\u05d5\u05d3\u05e2\u05d4. \u05dc\u05d7\u05e5 \u05e2\u05dc \u05db\u05e4\u05ea\u05d5\u05e8 \u05d4\u05e9\u05dc\u05d9\u05d7\u05d4 \u05db\u05d3\u05d9 \u05dc\u05d4\u05de\u05e9\u05d9\u05da.",
 		"zh": "\u4f60\u51c6\u5907\u53d1\u9001\u4e00\u4e2a\u6d88\u606f\uff0c\u8bf7\u70b9\u51fb\u63d0\u4ea4\u6309\u94ae\u4ee5\u7ee7\u7eed",
@@ -1436,7 +1437,7 @@
 		"it": "SAML 2.0 Service Provider (Hosted)",
 		"ja": "SAML 2.0\u30b5\u30fc\u30d3\u30b9\u30d7\u30ed\u30d0\u30a4\u30c0(\u30db\u30b9\u30c8)",
 		"lt": "SAML 2.0 Paslaugos teik\u0117jas (vietinis)",
-		"zh-tw": "SAML 2.0 \u670d\u52d9\u63d0\u4f9b\u8005(\u4e3b\u6a5f)",
+		"zh-tw": "SAML 2.0 \u670d\u52d9\u63d0\u4f9b\u8005 (\u672c\u5730)",
 		"et": "SAML 2.0 teenusepakkuja (hostitud)",
 		"he": "\u05e1\u05e4\u05e7 \u05e9\u05d9\u05e8\u05d5\u05ea \u05de\u05e7\u05d5\u05de\u05d9 \u05de\u05e1\u05d5\u05d2 SAML 2.0",
 		"zh": "SAML 2.0 \u670d\u52a1\u63d0\u4f9b\u8005\uff08\u672c\u5730\uff09",
@@ -1541,7 +1542,7 @@
 	"metaover_group_metadata.adfs-sp-remote": {
 		"es": "Proveedor de Servicio ADFS (remoto)",
 		"ru": "\u0421\u0435\u0440\u0432\u0438\u0441 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 ADFS (\u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0435 \u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u0435)",
-		"zh-tw": "ADFS \u670d\u52d9\u63d0\u4f9b\u8005(\u9060\u7aef)",
+		"zh-tw": "ADFS \u670d\u52d9\u63d0\u4f9b\u8005 (\u9060\u7aef)",
 		"nl": "ADFS Service Provider (Remote)",
 		"da": "ADFS tjenesteudbyder (remote)",
 		"el": "\u03a0\u03ac\u03c1\u03bf\u03c7\u03bf\u03c2 \u03a5\u03c0\u03b7\u03c1\u03b5\u03c3\u03b9\u03ce\u03bd ADFS (\u0391\u03c0\u03bf\u03bc\u03b1\u03ba\u03c1\u03c5\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2)"
@@ -1549,7 +1550,7 @@
 	"metaover_group_metadata.adfs-idp-hosted": {
 		"es": "Proveedor de Identidad ADFS (local)",
 		"ru": "\u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 ADFS (\u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u0435)",
-		"zh-tw": "ADFS \u9a57\u8b49\u63d0\u4f9b\u8005(\u4e3b\u6a5f)",
+		"zh-tw": "ADFS \u9a57\u8b49\u63d0\u4f9b\u8005 (\u672c\u5730)",
 		"nl": "ADFS Identity Provider (Hosted)",
 		"da": "ADFS identitetsudbyder (hosted)",
 		"el": "\u03a0\u03ac\u03c1\u03bf\u03c7\u03bf\u03c2 \u03a4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 ADFS (\u03a6\u03b9\u03bb\u03bf\u03be\u03b5\u03bd\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf\u03c2)"
diff --git a/dictionaries/attributes.translation.json b/dictionaries/attributes.translation.json
index 25c2e7fe5865731b566b41cb2c310a68ee6d37d4..3e6b7cbec5c9bb26c9a407ea3e6bb604b783b6e5 100644
--- a/dictionaries/attributes.translation.json
+++ b/dictionaries/attributes.translation.json
@@ -250,7 +250,7 @@
 		"it": "Mail",
 		"lt": "El.pa\u0161tas",
 		"ja": "\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9",
-		"zh-tw": "\u90f5\u4ef6",
+		"zh-tw": "Email",
 		"et": "E-post",
 		"he": "\u05d3\u05d5\u05d0\u05e8",
 		"ru": "\u041f\u043e\u0447\u0442\u0430",
@@ -400,7 +400,7 @@
 		"it": "Nome del dominio della propria organizzazione",
 		"lt": "Organizacijos domenas",
 		"ja": "\u7d44\u7e54\u5185\u30c9\u30e1\u30a4\u30f3",
-		"zh-tw": "\u9810\u8a2d\u7d44\u7e54 domain name",
+		"zh-tw": "\u6240\u5c6c\u7684\u7d44\u7e54\u7db2\u57df\u540d\u7a31",
 		"et": "Koduorganisatsiooni domeen",
 		"he": "\u05e9\u05dd \u05d4\u05de\u05ea\u05d7\u05dd \u05e9\u05dc \u05d0\u05d9\u05e8\u05d2\u05d5\u05df \u05d4\u05d1\u05d9\u05ea",
 		"ru": "\u0414\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u0433\u043b\u0430\u0432\u043d\u043e\u0439 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438",
@@ -514,7 +514,7 @@
 		"it": "Affiliazione nella propria organizzazione",
 		"lt": "S\u0105saja su organizacija",
 		"ja": "\u7d44\u7e54\u5185\u8077\u7a2e",
-		"zh-tw": "\u5bb6\u5ead\u9023\u7d61\u5730\u5740",
+		"zh-tw": "\u6240\u5c6c\u55ae\u4f4d\u7d44\u7e54\u4e4b\u806f\u7d61\u65b9\u5f0f",
 		"he": "\u05e9\u05d9\u05d9\u05db\u05d5\u05ea \u05d1\u05d0\u05d9\u05e8\u05d2\u05d5\u05df \u05d4\u05d1\u05d9\u05ea",
 		"ru": "\u0427\u043b\u0435\u043d\u0441\u0442\u0432\u043e \u0432 \u0433\u043b\u0430\u0432\u043d\u043e\u0439 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438",
 		"et": "Rollid koduorganisatsioonis",
@@ -550,7 +550,7 @@
 		"it": "Pseudonimo identificativo persistente",
 		"lt": "Nuolatinio pseudonimo ID",
 		"ja": "\u6c38\u7d9a\u7684\u533f\u540dID",
-		"zh-tw": "\u6301\u7e8c\u7684\u533f\u540d ID",
+		"zh-tw": "\u6c38\u4e45\u6027\u533f\u540d ID",
 		"he": "\u05de\u05d6\u05d4\u05d4 \u05de\u05e9\u05ea\u05de\u05e9 \u05d2\u05dc\u05d5\u05d1\u05dc\u05d9",
 		"ru": "ID \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e\u0433\u043e \u043f\u0441\u0435\u0432\u0434\u043e\u043d\u0438\u043c\u0430",
 		"et": "P\u00fcsiv pseudon\u00fc\u00fcmne ID",
@@ -588,7 +588,7 @@
 		"it": "Nome identificativo (principal name) nella propria organizzazione",
 		"lt": "Asmens pagrindinis vardas organizacijoje",
 		"ja": "\u6c38\u7d9a\u7684\u5229\u7528\u8005\u540d",
-		"zh-tw": "\u5bb6\u5ead\u7684\u500b\u4eba\u4e3b\u8981\u540d\u5b57",
+		"zh-tw": "\u500b\u4eba\u65bc\u6240\u5c6c\u7d44\u7e54\u7684\u6c38\u4e45\u540d\u5b57",
 		"he": "\u05d4\u05e9\u05dd \u05d4\u05e2\u05d9\u05e7\u05e8\u05d9 \u05d1\u05d0\u05d9\u05e8\u05d2\u05d5\u05df \u05d4\u05d1\u05d9\u05ea",
 		"ru": "\u0418\u043c\u044f \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044f \u0432 \u0433\u043b\u0430\u0432\u043d\u043e\u0439 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438",
 		"et": "Isiku p\u00f5hinimi koduasutuses",
@@ -605,9 +605,11 @@
 		"el": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c3\u03c4\u03bf\u03bd \u03bf\u03b9\u03ba\u03b5\u03af\u03bf \u03bf\u03c1\u03b3\u03b1\u03bd\u03b9\u03c3\u03bc\u03cc"
 	},
 	"attribute_edupersonuniqueid": {
+		"zh-tw": "\u500b\u4eba\u7121\u6cd5\u91cd\u65b0\u8a2d\u7f6e\uff0c\u65bc\u6240\u5c6c\u7d44\u7e54\u7684\u6c38\u4e45\u533f\u540d ID",
 		"el": "\u039c\u03cc\u03bd\u03b9\u03bc\u03bf, \u03b1\u03b4\u03b9\u03b1\u03c6\u03b1\u03bd\u03ad\u03c2 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c3\u03c4\u03bf\u03bd \u03bf\u03b9\u03ba\u03b5\u03af\u03bf \u03bf\u03c1\u03b3\u03b1\u03bd\u03b9\u03c3\u03bc\u03cc"
 	},
 	"attribute_edupersonorcid": {
+		"zh-tw": "ORCID \u7814\u7a76\u8005\u8b58\u5225\u78bc",
 		"el": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03ac \u03b5\u03c1\u03b5\u03c5\u03bd\u03b7\u03c4\u03ae ORCID"
 	},
 	"attribute_o": {
@@ -669,7 +671,7 @@
 		"it": "Componente di dominio (DC)",
 		"lt": "Domeno komponentas",
 		"ja": "\u30c9\u30e1\u30a4\u30f3\u540d",
-		"zh-tw": "Domain component (DC)",
+		"zh-tw": "\u7db2\u57df Domain component (DC)",
 		"et": "Domeeni komponent (DC)",
 		"he": "\u05de\u05e8\u05db\u05d9\u05d1 \u05de\u05ea\u05d7\u05dd (DC)",
 		"ru": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043e\u043c\u0435\u043d\u0430 (DC)",
@@ -744,7 +746,7 @@
 		"it": "Numero di fax",
 		"lt": "Fakso numeris",
 		"ja": "Fax\u756a\u53f7",
-		"zh-tw": "\u50b3\u771f",
+		"zh-tw": "\u50b3\u771f\u865f\u78bc",
 		"et": "Faksinumber",
 		"he": "\u05de\u05e1' \u05e4\u05e7\u05e1",
 		"ru": "\u041d\u043e\u043c\u0435\u0440 \u0444\u0430\u043a\u0441\u0430",
@@ -891,7 +893,7 @@
 		"it": "Localit\u00e0",
 		"lt": "Vietov\u0117",
 		"ja": "\u5730\u57df",
-		"zh-tw": "\u4f4d\u7f6e",
+		"zh-tw": "\u5340\u57df",
 		"he": "\u05d0\u05d9\u05d6\u05d5\u05e8",
 		"ru": "\u0420\u0430\u0439\u043e\u043d",
 		"et": "Asukoht",
@@ -1224,7 +1226,7 @@
 		"it": "Nome legale della propria organizzazione",
 		"lt": "Organizacijos juridinis pavadinimas",
 		"ja": "\u7d44\u7e54\u306e\u6b63\u5f0f\u540d\u79f0",
-		"zh-tw": "\u7d44\u7e54\u6b63\u5f0f\u540d\u7a31",
+		"zh-tw": "\u7d44\u7e54\u5168\u929c",
 		"et": "Organisatsiooni ametlik nimetus",
 		"he": "\u05d4\u05e9\u05dd \u05d4\u05e8\u05e9\u05de\u05d9 \u05e9\u05dc \u05d4\u05d0\u05d9\u05e8\u05d2\u05d5\u05df",
 		"ru": "\u042e\u0440\u0438\u0434\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438",
@@ -1298,7 +1300,7 @@
 		"it": "Distinguished name (DN) dell'organizzazione ",
 		"lt": "Asmens organizacijos atpa\u017einimo vardas",
 		"ja": "\u7d44\u7e54\u8b58\u5225\u540d",
-		"zh-tw": "Distinguished name (DN) \u500b\u4eba\u9810\u8a2d\u7d44\u7e54",
+		"zh-tw": "\u500b\u4eba\u6240\u5c6c\u7d44\u7e54 Distinguished name (DN)",
 		"he": "\u05e9\u05dd \u05de\u05d6\u05d4\u05d4 (DN) \u05e9\u05dc \u05d0\u05d9\u05e8\u05d2\u05d5\u05df \u05d4\u05d1\u05d9\u05ea",
 		"ru": "\u041e\u0442\u043b\u0438\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0438\u043c\u044f (DN) \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u0430 \u0434\u043e\u043c\u0430\u0448\u043d\u0435\u0439 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438",
 		"zh": "\u4eba\u7684\u5bb6\u5ead\u7ec4\u7ec7\u7684\u5206\u8fa8\u540d\u79f0\uff08DN\uff09",
@@ -1335,7 +1337,7 @@
 		"it": "Distinguished name (DN) dell'unit\u00e0 organizzativa della persona",
 		"lt": "Asmens organizacijos skyriaus atpa\u017einimo vardas",
 		"ja": "\u7d44\u7e54\u5358\u4f4d\u8b58\u5225\u540d",
-		"zh-tw": "Distinguished name (DN) \u500b\u4eba\u9810\u8a2d\u7d44\u7e54\u55ae\u4f4d",
+		"zh-tw": "\u500b\u4eba\u6240\u5c6c\u55ae\u4f4d Distinguished name (DN)",
 		"he": "\u05e9\u05dd \u05de\u05d6\u05d4\u05d4 (DN) \u05e9\u05dc \u05d4\u05d9\u05d7\u05d9\u05d3\u05d4 \u05d1\u05d0\u05d9\u05e8\u05d2\u05d5\u05df \u05d4\u05d1\u05d9\u05ea",
 		"ru": "\u041e\u0442\u043b\u0438\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0438\u043c\u044f (DN) \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u0430 \u043f\u043e\u0434\u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0434\u043e\u043c\u0430\u0448\u043d\u0435\u0439 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438",
 		"zh": "\u4eba\u7684\u5bb6\u7ec4\u7ec7\u5355\u4f4d\u7684\u8fa8\u522b\u540d\u79f0\uff08DN\uff09",
@@ -1483,7 +1485,7 @@
 		"it": "Numero identificativo locale",
 		"lt": "Vietinis tapatyb\u0117s numeris",
 		"ja": "\u652f\u90e8ID",
-		"zh-tw": "\u672c\u5730\u9a57\u8b49\u78bc",
+		"zh-tw": "\u672c\u5730\u8eab\u5206\u8b49\u5b57\u865f",
 		"he": "\u05de\u05e1\u05e4\u05e8 \u05d6\u05d4\u05d5\u05ea \u05de\u05e7\u05d5\u05de\u05d9",
 		"ru": "\u041c\u0435\u0441\u0442\u043d\u044b\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u043d\u043e\u043c\u0435\u0440",
 		"et": "Kohalik isikukood",
@@ -1555,7 +1557,7 @@
 		"it": "Hash della password utente",
 		"lt": "Naudotojo slapta\u017eod\u017eio mai\u0161a",
 		"ja": "\u30d1\u30b9\u30ef\u30fc\u30c9\u30cf\u30c3\u30b7\u30e5",
-		"zh-tw": "\u4f7f\u7528\u8005\u5bc6\u78bc\u7de8\u78bc",
+		"zh-tw": "\u4f7f\u7528\u8005\u5bc6\u78bc  (hash)",
 		"et": "Kasutaja paroolir\u00e4si",
 		"he": "\u05d4\u05d2\u05d9\u05d1\u05d5\u05d1 \u05e9\u05dc \u05e1\u05d9\u05e1\u05de\u05ea \u05d4\u05de\u05e9\u05ea\u05de\u05e9",
 		"ru": "\u0425\u044d\u0448 \u043f\u0430\u0440\u043e\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f",
@@ -1589,7 +1591,7 @@
 		"it": "Distinguished name (DN) dell'unit\u00e0 organizzativa della persona",
 		"lt": "Asmens pirminio organizacijos skyriaus atpa\u017einimo vardas",
 		"ja": "\u4e3b\u8981\u7d44\u7e54\u5358\u4f4d\u8b58\u5225\u540d",
-		"zh-tw": "Distinguished name (DN) of \u500b\u4eba\u4e3b\u8981\u7d44\u7e54\u55ae\u4f4d",
+		"zh-tw": "\u500b\u4eba\u4e3b\u8981\u7d44\u7e54\u55ae\u4f4d Distinguished name (DN)",
 		"he": "\u05e9\u05dd \u05de\u05d6\u05d4\u05d4 (DN) \u05e9\u05dc \u05d4\u05d9\u05d7\u05d9\u05d3\u05d4 \u05d4\u05e2\u05d9\u05e7\u05e8\u05d9\u05ea \u05d1\u05d0\u05d9\u05e8\u05d2\u05d5\u05df \u05d4\u05d1\u05d9\u05ea",
 		"ru": "\u041e\u0442\u043b\u0438\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0438\u043c\u044f (DN) \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u0430 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u043f\u043e\u0434\u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438",
 		"zh": "\u4eba\u7684\u4e3b\u8981\u7ec4\u7ec7\u5355\u4f4d\u7684\u8fa8\u522b\u540d\u79f0\uff08DN\uff09",
@@ -1687,7 +1689,7 @@
 		"nl": "Identiteitsverzekeringsprofiel",
 		"ja": "\u8b58\u5225\u5b50\u4fdd\u8a3c\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb",
 		"de": "Identity Assurance Profil",
-		"zh-tw": "\u53ef\u9760\u9a57\u8b49\u8a2d\u5b9a\u6a94",
+		"zh-tw": "\u8eab\u5206\u9a57\u8b49\u6587\u4ef6",
 		"he": "\u05e4\u05e8\u05d5\u05e4\u05d9\u05dc \u05d4\u05d1\u05d8\u05d7\u05ea \u05d6\u05d4\u05d5\u05ea",
 		"ru": "\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0444\u0430\u0439\u043b\u0430",
 		"et": "Identiteedi tagamise profiil",
diff --git a/dictionaries/disco.translation.json b/dictionaries/disco.translation.json
index 8520ec66c23c126d73f06c74c21324f05e8961a0..8fea4c86f66085692a8be988c259f610109c63a4 100644
--- a/dictionaries/disco.translation.json
+++ b/dictionaries/disco.translation.json
@@ -21,7 +21,7 @@
 		"lt": "Pasirinkite savo tapatybi\u0173 tiek\u0117j\u0105",
 		"it": "Selezionare il proprio identity provider",
 		"ja": "\u30a2\u30a4\u30c7\u30f3\u30c6\u30a3\u30c6\u30a3\u30d7\u30ed\u30d0\u30a4\u30c0\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044",
-		"zh-tw": "\u9078\u64c7\u4f60\u7684\u8b58\u5225\u63d0\u4f9b\u8005(idp)",
+		"zh-tw": "\u9078\u64c7\u4f60\u7684\u8b58\u5225\u63d0\u4f9b\u8005 (IdP)",
 		"ru": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0432\u0430\u0448\u0435\u0433\u043e identity provider",
 		"et": "Vali oma identiteedipakkuja",
 		"he": "\u05d1\u05d7\u05e8 \u05d0\u05ea \u05e1\u05e4\u05e7 \u05d4\u05d6\u05d4\u05d5\u05ea \u05e9\u05dc\u05da",
@@ -158,7 +158,7 @@
 		"lt": "[Rekomenduojame]",
 		"it": "[Scelta preferita]",
 		"ja": "[\u63a8\u5968\u3059\u308b\u9078\u629e]",
-		"zh-tw": "\u559c\u597d\u9078\u64c7",
+		"zh-tw": "[\u559c\u597d\u9078\u9805]",
 		"ru": "[\u041f\u0440\u0435\u0434\u043f\u043e\u0447\u0442\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0432\u044b\u0431\u043e\u0440]",
 		"et": "[Eelistatud valik]",
 		"he": "[\u05d1\u05d7\u05d9\u05e8\u05d4 \u05de\u05e2\u05d5\u05d3\u05e4\u05ea]",
diff --git a/dictionaries/errors.translation.json b/dictionaries/errors.translation.json
index 5519d42d83e9306f6b470925fe8147a44d164709..a206080f5aa8fe444f35c308e9e98733cddcc85a 100644
--- a/dictionaries/errors.translation.json
+++ b/dictionaries/errors.translation.json
@@ -22,7 +22,7 @@
 		"it": "Errore di SimpleSAMLphp",
 		"lt": "SimpleSAMLphp klaida",
 		"ja": "SimpleSAMLphp\u30a8\u30e9\u30fc",
-		"zh-tw": "SimpleSAMLphp \u7570\u5e38",
+		"zh-tw": "SimpleSAMLphp \u932f\u8aa4",
 		"et": "SimpleSAMLphp t\u00f5rge",
 		"he": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1 SimpleSAMLphp",
 		"zh": "SimpleSAMLphp\u9519\u8bef",
@@ -57,7 +57,7 @@
 		"it": "Se inoltri questo errore, per favore riporta anche questo tracking ID, esso render&agrave; possibile all'amministratore del sistema il tracciamento della tua sessione nei log:",
 		"lt": "Jei prane\u0161ate apie \u0161i\u0105 klaid\u0105, neu\u017emir\u0161kite pateikti \u0161ios klaidos ID, kurio d\u0117ka sistemos administratorius gal\u0117s surasti J\u016bs\u0173 sesijos metu atliktus veiksmus atlikt\u0173 veiksm\u0173 istorijoje:",
 		"ja": "\u3053\u306e\u30a8\u30e9\u30fc\u3092\u5831\u544a\u3059\u308b\u5834\u5408\u3001\u30b7\u30b9\u30c6\u30e0\u7ba1\u7406\u8005\u304c\u30ed\u30b0\u304b\u3089\u3042\u306a\u305f\u306e\u30bb\u30c3\u30b7\u30e7\u30f3\u3092\u7279\u5b9a\u3059\u308b\u70ba\u306b\u3001\u30c8\u30e9\u30c3\u30ad\u30f3\u30b0\u756a\u53f7\u3092\u5831\u544a\u3057\u3066\u304f\u3060\u3055\u3044\u3002",
-		"zh-tw": "\u5982\u679c\u60a8\u56de\u5831\u9019\u500b\u932f\u8aa4\uff0c\u8acb\u540c\u6642\u56de\u5831\u9019\u500b\u8ffd\u8e64\u6578\u5b57\uff0c\u8b93\u7cfb\u7d71\u7ba1\u7406\u54e1\u53ef\u4ee5\u85c9\u7531\u5b83\u5728\u8a18\u9304\u88e1\u627e\u5230\u60a8\u7684\u9023\u7dda\uff1a",
+		"zh-tw": "\u5982\u679c\u60a8\u56de\u5831\u9019\u500b\u932f\u8aa4\uff0c\u8acb\u540c\u6642\u56de\u5831\u9019\u500b\u8ffd\u8e64\u865f\u78bc\uff0c\u8b93\u7cfb\u7d71\u7ba1\u7406\u54e1\u53ef\u4ee5\u85c9\u7531\u5b83\u627e\u5230\u60a8\u7684\u9023\u7dda\u8a18\u9304\uff1a",
 		"et": "Kui rapoteerid sellest t\u00f5rkest, siis teata kindlasti ka j\u00e4lgimisnumber, mis v\u00f5imaldab s\u00fcsteemiadministraatoril logifailidest sinu sessiooniga seotud infot leida:",
 		"he": "\u05d0\u05dd \u05d0\u05ea\u05d4 \u05de\u05d3\u05d5\u05d5\u05d7 \u05e2\u05dc \u05d4\u05ea\u05e7\u05dc\u05d4, \u05d0\u05e0\u05d0 \u05d3\u05d5\u05d5\u05d7 \u05d2\u05dd \u05d0\u05ea \u05de\u05e1\u05e4\u05e8 \u05d4\u05de\u05e2\u05e7\u05d1 \u05d4\u05de\u05d0\u05e4\u05e9\u05e8 \u05dc\u05d0\u05ea\u05e8 \u05d0\u05ea \u05d4\u05e9\u05d9\u05d7\u05d4 \u05e9\u05dc\u05da \u05d1\u05d9\u05d5\u05de\u05e0\u05d9\u05dd \u05d4\u05e2\u05d5\u05de\u05d3\u05d9\u05dd \u05dc\u05e8\u05e9\u05d5\u05ea \u05de\u05e0\u05d4\u05dc \u05d4\u05de\u05e2\u05e8\u05db\u05ea: ",
 		"zh": "\u5982\u679c\u4f60\u62a5\u544a\u4e86\u8fd9\u4e2a\u9519\u8bef\uff0c\u90a3\u4e48\u8bf7\u4f60\u4e5f\u62a5\u544a\u8fd9\u4e2a\u8ffd\u8e2a\u53f7\u7801\uff0c\u7cfb\u7edf\u7ba1\u7406\u5458\u6709\u53ef\u80fd\u6839\u636e\u8fd9\u4e2a\u53f7\u7801\u5728\u65e5\u5fd7\u4e2d\u5b9a\u4f4d\u4f60\u7684SESSION",
@@ -130,7 +130,7 @@
 		"it": "Le seguenti informazioni di debug possono interessare l'amministratore di sistema o il supporto utenti:",
 		"lt": "\u0160i detali informacija gali b\u016bti \u012fdomi administratoriui:",
 		"ja": "\u30b7\u30b9\u30c6\u30e0\u7ba1\u7406\u8005\u3084\u30d8\u30eb\u30d7\u30c7\u30b9\u30af\u306f\u4ee5\u4e0b\u306e\u30c7\u30d0\u30c3\u30b0\u60c5\u5831\u306b\u8208\u5473\u3092\u6301\u3064\u304b\u3082\u3057\u308c\u307e\u305b\u3093:",
-		"zh-tw": "\u7ba1\u7406\u54e1\/\u670d\u52d9\u53f0\u53ef\u80fd\u5c0d\u4e0b\u5217\u9664\u932f\u8cc7\u8a0a\u6709\u8208\u8da3\uff1a",
+		"zh-tw": "\u7ba1\u7406\u54e1\u6216\u670d\u52d9\u53f0\u53ef\u80fd\u5c0d\u4e0b\u5217\u9664\u932f\u8cc7\u8a0a\u6709\u8208\u8da3\uff1a",
 		"et": "Allpool olev silumisinfo v\u00f5ib olla administraatorile v\u00f5i kasutajatoele v\u00e4ga kasulik:",
 		"he": "\u05d9\u05db\u05d5\u05dc \u05dc\u05d4\u05d9\u05d5\u05ea \u05e9\u05de\u05d9\u05d3\u05e2 \u05d4\u05d3\u05d1\u05d0\u05d2 \u05dc\u05de\u05d8\u05d4 \u05d9\u05e2\u05e0\u05d9\u05d9\u05df \u05d0\u05ea \u05de\u05e0\u05d4\u05dc \u05d4\u05de\u05e2\u05e8\u05db\u05ea \/ \u05ea\u05de\u05d9\u05db\u05d4 \u05d8\u05db\u05e0\u05d9\u05ea:",
 		"zh": "\u7ba1\u7406\u5458\u6216\u8005\u670d\u52a1\u53f0\u53ef\u80fd\u5bf9\u4e0b\u9762\u7684\u8c03\u8bd5\u4fe1\u606f\u5f88\u611f\u5174\u8da3",
@@ -239,7 +239,7 @@
 		"lt": "El. pa\u0161to adresas:",
 		"it": "Indirizzo di e-mail:",
 		"ja": "E\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9:",
-		"zh-tw": "\u96fb\u5b50\u90f5\u4ef6:",
+		"zh-tw": "Email",
 		"et": "E-posti aadress:",
 		"he": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d3\u05d5\u05d0\u05dc:",
 		"zh": "E-mail\u5730\u5740",
@@ -275,7 +275,7 @@
 		"lt": "Apra\u0161ykite kokius veiksmus atlikote, kuomet pasirod\u0117 \u0161i klaida...",
 		"it": "Descrivi cosa stavi facendo al momento dell'errore",
 		"ja": "\u4f55\u3092\u3057\u305f\u969b\u306b\u3053\u306e\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u305f\u304b\u3092\u8aac\u660e\u3057\u3066\u304f\u3060\u3055\u3044...",
-		"zh-tw": "\u89e3\u91cb\u7576\u4f60\u767c\u751f\u932f\u8aa4\u6642\u6240\u505a\u7684\u4e8b\u60c5...",
+		"zh-tw": "\u8acb\u8aaa\u660e\u4e00\u4e0b\uff0c\u7576\u60a8\u767c\u751f\u932f\u8aa4\u6642\u6240\u505a\u7684\u52d5\u4f5c...",
 		"et": "Kirjelda, millega tegelesid, kui see t\u00f5rge ilmnes...",
 		"he": "\u05d4\u05e1\u05d1\u05e8 \u05de\u05d4 \u05e2\u05e9\u05d9\u05ea \u05db\u05e9\u05d4\u05ea\u05e8\u05d7\u05e9\u05d4 \u05d4\u05e9\u05d2\u05d9\u05d0\u05d4...",
 		"zh": "\u8bf4\u660e\u4e00\u4e0b\uff0c\u4f60\u6b63\u5728\u505a\u4ec0\u4e48\u7684\u65f6\u5019\u53d1\u751f\u4e86\u8fd9\u4e2a\u9519\u8bef",
@@ -382,7 +382,7 @@
 		"lt": "\u0160i klaida tikriausiai susijusi d\u0117l SimpleSAMLphp neteisingo sukonfig\u016bravimo. Susisiekite su \u0161ios sistemos administratoriumi ir nusi\u0173skite \u017eemiau rodom\u0105 klaidos prane\u0161im\u0105.",
 		"it": "Questo errore \u00e8 probabilmente dovuto a qualche comportamento inatteso di SimpleSAMLphp o ad un errore di configurazione. Contatta l'amministratore di questo servizio di login con una copia del messaggio di errore riportato qui sopra.",
 		"ja": "\u3053\u306e\u30a8\u30e9\u30fc\u306f\u6050\u3089\u304f\u672a\u77e5\u306e\u554f\u984c\u304bSimpleSAMLphp\u306e\u8a2d\u5b9a\u30df\u30b9\u3067\u3059\u3002\u30ed\u30b0\u30a4\u30f3\u30b5\u30fc\u30d3\u30b9\u306e\u7ba1\u7406\u8005\u306b\u4e0a\u8a18\u306e\u30a8\u30e9\u30fc\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u9023\u7d61\u3057\u3066\u4e0b\u3055\u3044\u3002",
-		"zh-tw": "\u9019\u500b\u554f\u984c\u53ef\u80fd\u662f\u56e0\u70ba SimpleSAMLphp \u7684\u67d0\u4e9b\u4f8b\u5916\u7684\u884c\u70ba\u6216\u7121\u6548\u8a2d\u5b9a\u3002\u9023\u7d61\u9019\u500b\u767b\u5165\u670d\u52d9\u7684\u7ba1\u7406\u54e1\uff0c\u4ee5\u53ca\u50b3\u9001\u9019\u4e9b\u932f\u8aa4\u8a0a\u606f\u3002",
+		"zh-tw": "\u9019\u500b\u554f\u984c\u53ef\u80fd\u662f\u56e0\u70ba SimpleSAMLphp \u7684\u67d0\u4e9b\u4f8b\u5916\u884c\u70ba\u6216\u7121\u6548\u8a2d\u5b9a\u6240\u5c0e\u81f4\u3002\u8acb\u806f\u7e6b\u9019\u500b\u767b\u5165\u670d\u52d9\u7684\u7ba1\u7406\u54e1\uff0c\u4e26\u50b3\u9001\u9019\u4e9b\u932f\u8aa4\u8a0a\u606f\u3002",
 		"et": "See t\u00f5rge ilmnes t\u00f5en\u00e4oliselt SimpleSAMLphp ootamatu k\u00e4itumise v\u00f5i valesti seadistamise t\u00f5ttu. V\u00f5ta \u00fchendust selle sisselogimisteenuse administraatoriga ja saada talle \u00fclalolev veateade.",
 		"he": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d6\u05d5 \u05d4\u05d9\u05d0 \u05db\u05db\u05dc \u05d4\u05e0\u05e8\u05d0\u05d4 \u05d1\u05e9\u05dc \u05d4\u05ea\u05e0\u05d4\u05d2\u05d5\u05ea \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4 \u05d0\u05d5 \u05e9\u05d2\u05d5\u05d9\u05d4 \u05e9\u05dc SimpleSAMLphp. \u05e6\u05d5\u05e8 \u05e7\u05e9\u05e8 \u05e2\u05dd \u05de\u05e0\u05d4\u05dc \u05d4\u05de\u05e2\u05e8\u05db\u05ea \u05e9\u05dc \u05e9\u05d9\u05e8\u05d5\u05ea \u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05d4\u05d6\u05d4, \u05d5\u05e9\u05dc\u05d7 \u05dc\u05d5 \u05d0\u05ea \u05d4\u05e9\u05d2\u05d9\u05d0\u05d4 \u05dc\u05de\u05e2\u05dc\u05d4.",
 		"zh": "\u8fd9\u4e2a\u9519\u8bef\u53ef\u80fd\u662f\u7531\u4e8e\u4e00\u4e9b\u610f\u60f3\u4e0d\u5230\u7684\u884c\u4e3a\u6216\u8005\u662fSimpleSAMLphp\u7684\u914d\u7f6e\u9519\u8bef\u5bfc\u81f4\u7684\uff0c\u8bf7\u8054\u7cfb\u8fd9\u4e2a\u767b\u5f55\u670d\u52a1\u5668\u7684\u7ba1\u7406\u5458\u5e76\u628a\u4e0a\u9762\u7684\u9519\u8bef\u6d88\u606f\u53d1\u9001\u7ed9\u4ed6\u4eec",
@@ -419,7 +419,7 @@
 		"lt": "Klaida kuriant u\u017eklaus\u0105",
 		"it": "Errore durante la generazione della richiesta",
 		"ja": "\u30ea\u30af\u30a8\u30b9\u30c8\u306e\u751f\u6210\u30a8\u30e9\u30fc",
-		"zh-tw": "\u932f\u8aa4\u7522\u751f\u8acb\u6c42",
+		"zh-tw": "\u5efa\u7acb\u8acb\u6c42\u932f\u8aa4",
 		"et": "T\u00f5rge p\u00e4ringu loomisel",
 		"he": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05d9\u05e6\u05d9\u05e8\u05ea \u05d4\u05d1\u05e7\u05e9\u05d4",
 		"zh": "\u521b\u5efa\u8bf7\u6c42\u51fa\u9519",
@@ -524,7 +524,7 @@
 		"tr": "Tan\u0131ma servisine g\u00f6nderilen parametreler tan\u0131mlananlara g\u00f6re de\u011fildi.",
 		"lt": "Parametrai, nusi\u0173sti \"discovery\" servisui neatitiko specifikacij\u0173.",
 		"it": "I parametri inviati al discovery service non rispettano le specifiche.",
-		"zh-tw": "\u50b3\u905e\u81f3\u641c\u5c0b\u670d\u52d9\u7684\u53c3\u6578\u4e26\u975e\u6309\u7167\u898f\u683c\u6240\u8a02\u3002",
+		"zh-tw": "\u50b3\u905e\u81f3\u641c\u5c0b\u670d\u52d9\u7684\u53c3\u6578\u4e26\u4e0d\u7b26\u5408\u898f\u7bc4\u8981\u6c42\u3002",
 		"ja": "\u30b5\u30fc\u30d3\u30b9\u30c7\u30a3\u30b9\u30ab\u30d0\u30ea\u306b\u9001\u4fe1\u3057\u305f\u30d1\u30e9\u30e1\u30fc\u30bf\u304c\u4ed5\u69d8\u306b\u5f93\u3063\u3066\u3044\u307e\u305b\u3093\u3002",
 		"et": "Tuvastusteenusele saadetud parameetrid ei vastanud n\u00f5uetele.",
 		"he": "\u05d4\u05e4\u05e8\u05de\u05d8\u05e8\u05d9\u05dd \u05e9\u05e0\u05e9\u05dc\u05d7\u05d5 \u05dc\u05e9\u05d9\u05e8\u05d5\u05ea \u05d2\u05d9\u05dc\u05d5\u05d9 \u05dc\u05d0 \u05d4\u05d9\u05d5 \u05e2\u05dc \u05e4\u05d9 \u05de\u05e4\u05e8\u05d8.",
@@ -562,7 +562,7 @@
 		"lt": "Nepavyko sukurti autentikacijos atsakymo",
 		"it": "Impossibile generare una risposta di autenticazione",
 		"ja": "\u8a8d\u8a3c\u5fdc\u7b54\u3092\u751f\u6210\u51fa\u6765\u307e\u305b\u3093\u3067\u3057\u305f",
-		"zh-tw": "\u7121\u6cd5\u5efa\u7acb\u8a8d\u8b49\u56de\u61c9",
+		"zh-tw": "\u7121\u6cd5\u5efa\u7acb\u9a57\u8b49\u56de\u8986",
 		"et": "Autentimisvastuse loomine ei \u00f5nnestunud",
 		"he": "\u05d0\u05d9\u05df \u05d0\u05e4\u05e9\u05e8\u05d5\u05ea \u05dc\u05d9\u05e6\u05d5\u05e8 \u05ea\u05d2\u05d5\u05d1\u05ea \u05d4\u05d6\u05d3\u05d4\u05d5\u05ea",
 		"zh": "\u65e0\u6cd5\u521b\u5efa\u8ba4\u8bc1\u5e94\u7b54",
@@ -597,7 +597,7 @@
 		"tr": "Bu kimlik sa\u011flay\u0131c\u0131 bir kimlik do\u011frulama cevab\u0131 olu\u015fturuken hata olu\u015ftu.",
 		"lt": "\u0160iam tapatybi\u0173 teik\u0117jui bandant sukurti autentikacijos atsakym\u0105 \u012fvyko klaida.",
 		"it": "Si \u00e8 verificato un errore durante la fase di creazione della risposta di autenticazione da parte dell'Identity Provider.",
-		"zh-tw": "\u7576\u9019\u500b\u9a57\u8b49\u63d0\u4f9b\u8005\u5617\u8a66\u5efa\u7acb\u4e00\u500b\u9a57\u8b49\u56de\u61c9\u6642\uff0c\u6709\u500b\u932f\u8aa4\u767c\u751f\u3002",
+		"zh-tw": "\u7576\u9019\u500b\u9a57\u8b49\u63d0\u4f9b\u8005\u5617\u8a66\u5efa\u7acb\u4e00\u500b\u9a57\u8b49\u56de\u8986\u6642\uff0c\u6709\u500b\u932f\u8aa4\u767c\u751f\u3002",
 		"ja": "\u30a2\u30a4\u30c7\u30f3\u30c6\u30a3\u30c6\u30a3\u30d7\u30ed\u30d0\u30a4\u30c0\u306e\u8a8d\u8a3c\u30ec\u30b9\u30dd\u30f3\u30b9\u306e\u751f\u6210\u6642\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002",
 		"et": "T\u00f5rge tekkis, kui see identiteedipakkuja p\u00fc\u00fcdis luua autentimisvastust.",
 		"he": "\u05db\u05d0\u05e9\u05e8 \u05e1\u05e4\u05e7 \u05d4\u05d6\u05d4\u05d5\u05ea \u05e0\u05d9\u05e1\u05d4 \u05dc\u05d9\u05e6\u05d5\u05e8 \u05ea\u05d2\u05d5\u05d1\u05ea \u05d4\u05d6\u05d3\u05d4\u05d5\u05ea, \u05d0\u05d9\u05e8\u05e2\u05d4 \u05e9\u05d2\u05d9\u05d0\u05d4.",
@@ -670,7 +670,7 @@
 		"lt": "LDAP yra naudotoj\u0173 duomen\u0173 baz\u0117. Jums jungiantis, mums reikalinga prie jos prisijungti. Bandant tai padaryti \u012fvyko klaida.",
 		"it": "Gli utenti sono memorizzati nel server LDAP, che viene quindi contattato in fase di connessione dell'utente. Si \u00e8 verificato un errore proprio in questa fase.",
 		"ja": "\u3042\u306a\u305f\u304c\u30ed\u30b0\u30a4\u30f3\u3092\u884c\u3046\u6642\u3001LDAP\u3068\u3044\u3046\u30e6\u30fc\u30b6\u30fc\u30c7\u30fc\u30bf\u30fc\u30d9\u30fc\u30b9\u306b\u30a2\u30af\u30bb\u30b9\u3057\u307e\u3059\u3002\u3053\u306e\u6642\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002",
-		"zh-tw": "LDAP \u662f\u4f7f\u7528\u9019\u8cc7\u6599\u5eab\uff0c\u7576\u60a8\u5617\u8a66\u767b\u5165\u6642\uff0c\u6211\u5011\u5fc5\u9808\u9023\u7d50\u81f3\u4e00\u500b LDAP \u8cc7\u6599\u5eab\u3002\u800c\u5728\u5617\u8a66\u6642\u6709\u500b\u932f\u8aa4\u767c\u751f\u3002",
+		"zh-tw": "LDAP \u662f\u4f7f\u7528\u8005\u8cc7\u6599\u5eab\uff0c\u7576\u60a8\u5617\u8a66\u767b\u5165\u6642\uff0c\u6211\u5011\u5fc5\u9808\u9023\u7d50\u81f3\u4e00\u500b LDAP \u8cc7\u6599\u5eab\uff0c\u800c\u5728\u5617\u8a66\u767b\u5165\u6642\u6709\u500b\u932f\u8aa4\u767c\u751f\u3002",
 		"et": "LDAP on kasutajate andmebaas ja sisselogimisel p\u00fc\u00fctakse  LDAP-andmebaasi \u00fchendust luua. Seekord tekkis \u00fchenduse loomisel t\u00f5rge.",
 		"he": "LDAP \u05d4\u05d5\u05d0 \u05de\u05e1\u05d3 \u05d4\u05e0\u05ea\u05d5\u05e0\u05d9\u05dd \u05d4\u05de\u05db\u05d9\u05dc \u05d0\u05ea \u05d4\u05de\u05e9\u05ea\u05de\u05e9\u05d9\u05dd, \u05d5\u05db\u05d0\u05e9\u05e8 \u05d0\u05ea\u05d4 \u05de\u05e0\u05e1\u05d4 \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8, \u05e6\u05e8\u05d9\u05da \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05d0\u05dc\u05d9\u05d5. \u05e9\u05d2\u05d9\u05d0\u05d4 \u05e7\u05e8\u05ea\u05d4 \u05d1\u05d6\u05de\u05df \u05e0\u05d9\u05e1\u05d9\u05d5\u05df \u05d4\u05d7\u05d9\u05d1\u05d5\u05e8 \u05d4\u05e0\u05d5\u05db\u05d7\u05d9.",
 		"zh": "LDAP\u662f\u4e00\u4e2a\u7528\u6237\u6570\u636e\u5e93\uff0c\u5f53\u4f60\u8bd5\u56fe\u767b\u5f55\u65f6\uff0c\u6211\u4eec\u9700\u8981\u8fde\u63a5\u5230LDAP\u6570\u636e\u5e93\uff0c\u7136\u800c\u8fd9\u6b21\u6211\u4eec\u8bd5\u56fe\u94fe\u63a5\u65f6\u53d1\u751f\u4e86\u4e00\u4e2a\u9519\u8bef",
@@ -707,7 +707,7 @@
 		"lt": "Klaida vykdant atsijungimo u\u017eklaus\u0105",
 		"it": "Errore nell'elaborazione della richiesta di disconnessione (Logout Request).",
 		"ja": "\u30ed\u30b0\u30a2\u30a6\u30c8\u6d0b\u5f13\u306e\u51e6\u7406\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f",
-		"zh-tw": "\u767b\u51fa\u8acb\u6c42\u70ba\u932f\u8aa4\u7a0b\u5e8f",
+		"zh-tw": "\u8655\u7406\u767b\u51fa\u8acb\u6c42\u6642\u767c\u751f\u932f\u8aa4",
 		"et": "T\u00f5rge v\u00e4ljalogimisp\u00e4ringu t\u00f6\u00f6tlemisel",
 		"he": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05e2\u05d9\u05d1\u05d5\u05d3 \u05d1\u05e7\u05e9\u05ea \u05d4\u05ea\u05e0\u05ea\u05e7\u05d5\u05ea",
 		"zh": "\u5904\u7406\u9000\u51fa\u8bf7\u6c42\u65f6\u53d1\u751f\u9519\u8bef",
@@ -743,7 +743,7 @@
 		"lt": "Klaida \u012fvyko bandant \u012fvykdyti atsijungimo u\u017eklaus\u0105.",
 		"it": "Si \u00e8 verificato un errore quando si \u00e8 tentato di elaborare la richiesta di disconnessione (Logout Request).",
 		"ja": "\u30ed\u30b0\u30a2\u30a6\u30c8\u51e6\u7406\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002",
-		"zh-tw": "\u6709\u500b\u932f\u8aa4\u767c\u751f\u65bc\u6e96\u5099\u9032\u884c\u767b\u51fa\u8acb\u6c42\u6642\u3002",
+		"zh-tw": "\u6709\u500b\u932f\u8aa4\u767c\u751f\u65bc\u9032\u884c\u767b\u51fa\u8acb\u6c42\u8655\u7406\u6642\u3002",
 		"et": "V\u00e4ljalogimisp\u00e4ringu t\u00f6\u00f6tlemisel tekkis t\u00f5rge",
 		"he": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05d6\u05de\u05df  \u05d4\u05e0\u05d9\u05e1\u05d9\u05d5\u05df \u05dc\u05e2\u05d1\u05d3 \u05d0\u05ea \u05d1\u05e7\u05e9\u05ea \u05d4\u05ea\u05e0\u05ea\u05e7\u05d5\u05ea.",
 		"zh": "\u8bd5\u56fe\u5904\u7406\u9000\u51fa\u8bf7\u6c42\u65f6\u53d1\u751f\u4e86\u4e00\u4e2a\u9519\u8bef",
@@ -778,7 +778,7 @@
 		"lt": "Klaida siun\u010diant metaduomenis",
 		"it": "Errore nel caricamento dei metadati",
 		"ja": "\u76ee\u3089\u30fc\u30c7\u30fc\u30bf\u306e\u8aad\u307f\u8fbc\u307f\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f",
-		"zh-tw": "\u932f\u8aa4\u8f09\u5165\u8a6e\u91cb\u8cc7\u6599",
+		"zh-tw": "\u932f\u8aa4\u8f09\u5165 Metadata \u8cc7\u6599",
 		"et": "Metaandmete laadimise t\u00f5rge",
 		"he": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05d8\u05e2\u05d9\u05e0\u05ea \u05d4\u05de\u05d8\u05d0-\u05de\u05d9\u05d3\u05e2",
 		"zh": "\u8f7d\u5165\u5143\u4fe1\u606f\u65f6\u53d1\u751f\u9519\u8bef",
@@ -812,7 +812,7 @@
 		"tr": "SimpleSAMLphp kurulumunuzda baz\u0131 yanl\u0131\u015f ayarlamalar s\u00f6zkonusu. E\u011fer bu servisin y\u00f6neticisi sizseniz, \u00fcstveri (metadata) ayarlar\u0131n\u0131z\u0131n d\u00fczg\u00fcn bir \u015fekilde yap\u0131ld\u0131\u011f\u0131ndan emin olun.",
 		"lt": "Rastos J\u016bs\u0173 SimpleSAMLphp konfig\u016bravimo klaidos. Jei J\u016bs esate \u0161ios sistemos administratorius, tur\u0117tum\u0117te patikrinti, ar teisingai nustatyti metaduomenys.",
 		"it": "C'\u00e8 qualche errore di configurazione in questa installazione SimpleSAMLphp. Se sei l'amministratore di sistema, assicurati che la configurazione dei metadati sia corretta.",
-		"zh-tw": "\u6709\u4e00\u4e9b\u932f\u8aa4\u8a2d\u5b9a\u5728\u60a8\u6240\u5b89\u88dd\u7684 SimpleSAMLphp\u3002\u5982\u679c\u60a8\u662f\u9019\u500b\u670d\u52d9\u7684\u7ba1\u7406\u54e1\uff0c\u60a8\u53ef\u80fd\u9700\u8981\u78ba\u8a8d\u60a8\u7684\u8a6e\u91cb\u8cc7\u6599\u8a2d\u5b9a\u662f\u5426\u6b63\u78ba\u5730\u8a2d\u7f6e\u3002",
+		"zh-tw": "\u6709\u4e00\u4e9b\u932f\u8aa4\u8a2d\u5b9a\u5728\u60a8\u6240\u5b89\u88dd\u7684 SimpleSAMLphp\u3002\u5982\u679c\u60a8\u662f\u9019\u500b\u670d\u52d9\u7684\u7ba1\u7406\u54e1\uff0c\u60a8\u53ef\u80fd\u9700\u8981\u78ba\u8a8d\u60a8\u7684 Metadata \u8a2d\u5b9a\u662f\u5426\u6b63\u78ba\u3002",
 		"ja": "SimpleSAMLphp\u306e\u8a2d\u5b9a\u306b\u8aa4\u308a\u304c\u3042\u308a\u307e\u3057\u305f\u3002\u3082\u3057\u3042\u306a\u305f\u304c\u3053\u306e\u30b5\u30fc\u30d3\u30b9\u306e\u7ba1\u7406\u8005\u3067\u3042\u308c\u3070\u30e1\u30bf\u30c7\u30fc\u30bf\u8a2d\u5b9a\u3092\u6b63\u3057\u304f\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002",
 		"et": "Midagi on su SimpleSAMLphp paigalduses valesti seadistatud. Kui sa oled selle teenuse administraator, siis peaksid kontrollima, et metaandmete seadistused oleks korrektselt seadistatud.",
 		"he": "\u05d9\u05e9\u05e0\u05d4 \u05d1\u05e2\u05d9\u05d9\u05d4 \u05d1\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05e9\u05dc \u05d4\u05ea\u05e7\u05e0\u05ea \u05d4 SimpleSAMLphp \u05e9\u05dc\u05da. \u05d0\u05dd \u05d0\u05ea\u05d4 \u05de\u05e0\u05d4\u05dc \u05d4\u05de\u05e2\u05e8\u05db\u05ea \u05e9\u05dc \u05e9\u05d9\u05e8\u05d5\u05ea \u05d6\u05d4, \u05db\u05d3\u05d9 \u05e9\u05ea\u05d5\u05d5\u05d3\u05d0 \u05e9\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05de\u05d4\u05de\u05d8\u05d0-\u05de\u05d9\u05d3\u05e2 \u05e9\u05dc\u05da \u05e0\u05db\u05d5\u05e0\u05d5\u05ea.",
@@ -884,7 +884,7 @@
 		"tr": "Bu k\u0131s\u0131m kullan\u0131mda de\u011fil. SimpleSAMLphp ayarlar\u0131n\u0131z\u0131n etkinle\u015ftirme se\u00e7eneklerini kontrol edin.",
 		"lt": "Baigties ta\u0161kas ne\u012fjungtas. Patikrinkite savo SimpleSAMLphp konfig\u016bracij\u0105.",
 		"it": "Questo endpoint non \u00e8 abilitato. Verifica le opzioni di attivazione nella configurazione di SimpleSAMLphp.",
-		"zh-tw": "\u9019\u500b\u7aef\u9ede\u4e26\u672a\u555f\u7528\u3002\u6838\u53d6\u555f\u7528\u9078\u9805\u65bc\u60a8\u7684 SimpleSAMLphp \u8a2d\u5b9a\u4e2d\u3002",
+		"zh-tw": "\u9019\u500b\u7aef\u9ede\u4e26\u672a\u555f\u7528\uff0c\u8acb\u65bc\u60a8\u7684 SimpleSAMLphp \u8a2d\u5b9a\u4e2d\u6aa2\u67e5\u7aef\u9ede\u662f\u5426\u5df2\u555f\u7528\u3002",
 		"ja": "\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u304c\u6709\u52b9\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002SimpleSAMLphp\u306e\u8a2d\u5b9a\u3067\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u6709\u52b9\u306b\u3057\u3066\u304f\u3060\u3055\u3044\u3002",
 		"et": "See l\u00f5pp-punkt pole lubatud. Kontrolli oma simpleSAMPphp seadistust.",
 		"he": "\u05e7\u05e6\u05d4 \u05d6\u05d4 \u05d0\u05d9\u05e0\u05d5 \u05de\u05d5\u05e4\u05e2\u05dc. \u05d1\u05d3\u05d5\u05e7 \u05d0\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05d5\u05ea \u05d4\u05d4\u05e4\u05e2\u05dc\u05d4 \u05d1\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea  SimpleSAMLphp \u05e9\u05dc\u05da.",
@@ -1172,7 +1172,7 @@
 		"tr": "Tekli\u00c7\u0131k\u0131\u015fServis (SingleLogoutService) aray\u00fcz\u00fcne giri\u015f yapt\u0131n\u0131z, ancak bir SAML \u00c7\u0131k\u0131\u015f\u0130ste\u011fi ya da \u00c7\u0131k\u0131\u015fCevab\u0131 sa\u011flamad\u0131n\u0131z.",
 		"lt": "J\u016bs pasiek\u0117te SingleLogoutService paslaug\u0105, ta\u010diau nepateik\u0117te SAML LogoutRequest ar LogoutResponse u\u017eklaus\u0173.",
 		"it": "Hai acceduto all'interfaccia di SingleLogoutService, ma senza fornire un messaggio SAML di LogoutRequest o LogoutResponse.",
-		"zh-tw": "\u60a8\u9023\u7d50\u55ae\u4e00\u7c3d\u51fa\u670d\u52d9\u754c\u9762\uff0c\u4f46\u662f\u6c92\u6709\u63d0\u4f9b\u4e00\u500b SAML \u767b\u51fa\u8acb\u6c42\u6216\u767b\u51fa\u56de\u61c9\u3002",
+		"zh-tw": "\u60a8\u9023\u7d50\u55ae\u4e00\u767b\u51fa\u670d\u52d9\u754c\u9762\uff0c\u4f46\u662f\u6c92\u6709\u63d0\u4f9b\u4e00\u500b SAML \u767b\u51fa\u8acb\u6c42\u6216\u767b\u51fa\u56de\u61c9\u3002\u8acb\u6ce8\u610f\uff0c\u8a72\u7aef\u9ede\u4e26\u975e\u76f4\u63a5\u9023\u7dda\u3002",
 		"ja": "SingleLogoutService\u30a4\u30f3\u30bf\u30fc\u30d5\u30a7\u30fc\u30b9\u3078\u30a2\u30af\u30bb\u30b9\u3057\u307e\u3057\u305f\u304c\u3001SAML LogoutRequest \u3084 LogoutResponse \u304c\u63d0\u4f9b\u3055\u308c\u307e\u305b\u3093\u3067\u3057\u305f\u3002",
 		"et": "Sa k\u00fclastasid SingleLogoutService liidest, kui ei pakkunud SAML LogoutRequest v\u00f5i LogoutResponse.",
 		"he": "\u05e0\u05d9\u05d2\u05e9\u05ea \u05dc\u05de\u05de\u05e9\u05e7 \u05e9\u05d9\u05e8\u05d5\u05ea \u05d4\u05d4\u05ea\u05e0\u05ea\u05e7\u05d5\u05ea \u05d4\u05db\u05dc\u05dc\u05d9\u05ea, \u05d0\u05d1\u05dc \u05dc\u05d0 \u05e1\u05d9\u05e4\u05e7\u05ea \u05d1\u05e7\u05e9\u05ea \u05d0\u05d5 \u05ea\u05d2\u05d5\u05d1\u05ea \u05d4\u05ea\u05e0\u05ea\u05e7\u05d5\u05ea \u05e9\u05dc SAML.",
@@ -1244,7 +1244,7 @@
 		"tr": "Onay Al\u0131c\u0131 Servis (Assertion Consumer Service) aray\u00fcz\u00fcne giri\u015f yapt\u0131n\u0131z, ancak SAML Kimlik Do\u011frulama Cevab\u0131 sa\u011flamad\u0131n\u0131z.",
 		"lt": "J\u016bs pasiek\u0117te vartotoj\u0173 aptarnavimo servis\u0105, ta\u010diau nepateik\u0117te SAML autentikacijos atsakymo.",
 		"it": "Hai acceduto all'interfaccia di Assertion Consumer Service, ma senza fornire un messaggio SAML di Authentication Response.",
-		"zh-tw": "\u60a8\u9023\u7d50\u6d88\u8cbb\u8005\u8072\u660e\u670d\u52d9\u754c\u9762\uff0c\u4f46\u662f\u6c92\u6709\u63d0\u4f9b\u4e00\u500b SAML \u8a8d\u8b49\u56de\u61c9\u3002",
+		"zh-tw": "\u60a8\u9023\u7d50\u6d88\u8cbb\u8005\u8072\u660e\u670d\u52d9\u754c\u9762\uff0c\u4f46\u8a72\u4ecb\u9762\u672a\u63d0\u4f9b SAML \u6d88\u8cbb\u8005\u8072\u660e\u8a0a\u606f\u3002\u8acb\u6ce8\u610f\uff0c\u8a72\u7aef\u9ede\u4e26\u975e\u76f4\u63a5\u9023\u7dda\u3002",
 		"ja": "Assertion Consumer Service\u30a4\u30f3\u30bf\u30fc\u30d5\u30a7\u30fc\u30b9\u3078\u30a2\u30af\u30bb\u30b9\u3057\u307e\u3057\u305f\u304c\u3001SAML\u8a8d\u8a3c\u30ec\u30b9\u30dd\u30f3\u30b9\u304c\u63d0\u4f9b\u3055\u308c\u307e\u305b\u3093\u3067\u3057\u305f\u3002",
 		"et": "Sa k\u00fclastasid Assertion Consumer Service liidest, kuid ei pakkunud SAML autentimisvastust.",
 		"he": "\u05e0\u05d9\u05d2\u05e9\u05ea \u05dc\u05de\u05de\u05e9\u05e7 \u05d4\u05db\u05e8\u05d6\u05ea \u05e9\u05d9\u05e8\u05d5\u05ea \u05dc\u05dc\u05e7\u05d5\u05d7, \u05d0\u05d1\u05dc \u05dc\u05d0 \u05e1\u05d9\u05e4\u05e7\u05ea \u05ea\u05d2\u05d5\u05d1\u05ea \u05d4\u05d6\u05d3\u05d4\u05d5\u05ea SAML. ",
@@ -1260,6 +1260,18 @@
 		"af": "Jy het aansoek gedoen vir toegang na die Assertion Consumer Service koppelvlak, maar geen SAML Verifikasie Versoek is saam gestuur nie.",
 		"el": "\u039a\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03c0\u03b1\u03c6\u03ae AssertionConsumerService  \u03c0\u03b1\u03c1\u03b1\u03bb\u03b5\u03af\u03c8\u03b1\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03b1\u03c0\u03ac\u03bd\u03c4\u03b7\u03c3\u03b7 \u03c3\u03b5 \u03b1\u03af\u03c4\u03b7\u03bc\u03b1 \u03c4\u03b1\u03c5\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03c9\u03c4\u03bf\u03ba\u03cc\u03bb\u03bb\u03bf\u03c5 SAML. \u03a3\u03b7\u03bc\u03b5\u03b9\u03ce\u03c3\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c4\u03b5\u03bb\u03b9\u03ba\u03cc \u03c3\u03b7\u03bc\u03b5\u03af\u03bf (endpoint) \u03b4\u03b5\u03bd \u03c0\u03c1\u03bf\u03bf\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ac\u03bc\u03b5\u03c3\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b2\u03ac\u03c3\u03b9\u03bc\u03bf."
 	},
+	"title_SSOPARAMS": {
+		"zh-tw": "\u672a\u63d0\u4f9b SAML \u8acb\u6c42"
+	},
+	"descr_SSOPARAMS": {
+		"zh-tw": "\u60a8\u9023\u7d50\u55ae\u4e00\u7c3d\u5165\u670d\u52d9\u4ecb\u9762\uff0c\u4f46\u672a\u63d0\u4f9b\u4e00\u500b SAML \u9a57\u8b49\u8acb\u6c42\u3002\u8acb\u6ce8\u610f\uff0c\u8a72\u7aef\u9ede\u4e26\u975e\u76f4\u63a5\u9023\u7dda\u3002"
+	},
+	"title_ARSPARAMS": {
+		"zh-tw": "\u672a\u63d0\u4f9b SAML \u8a0a\u606f"
+	},
+	"descr_ARSPARAMS": {
+		"zh-tw": "\u60a8\u9023\u7d50\u4eba\u5de5\u8655\u7406\u670d\u52d9\u4ecb\u9762\uff0c\u4f46\u672a\u63d0\u4f9b SAML \u4eba\u5de5\u8655\u7406\u670d\u52d9\u8a0a\u606f\u3002\u8acb\u6ce8\u610f\uff0c\u8a72\u7aef\u9ede\u4e26\u975e\u76f4\u63a5\u9023\u7dda\u3002"
+	},
 	"title_CASERROR": {
 		"no": "CAS-feil",
 		"nn": "CAS-feil",
@@ -1317,7 +1329,7 @@
 		"lt": "Klaida bandant jungtis prie CAS serverio.",
 		"it": "Errore nella comunicazione con il server CAS.",
 		"ja": "CAS\u30b5\u30fc\u30d0\u30fc\u3068\u306e\u901a\u4fe1\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002",
-		"zh-tw": "\u7576\u9023\u7dda\u81f3 CAS \u4e3b\u6a5f\u6642\u932f\u8aa4\u3002",
+		"zh-tw": "\u8207 CAS \u4e3b\u6a5f\u901a\u8a0a\u6642\u767c\u751f\u932f\u8aa4\u3002",
 		"et": "CAS-serveriga suhtlemisel tekkis t\u00f5rge.",
 		"he": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05d4\u05ea\u05e7\u05e9\u05e8\u05d5\u05ea \u05e2\u05dd \u05e9\u05e8\u05ea \u05e9\u05d4\u05dd.",
 		"zh": "\u5728\u548cCAS\u670d\u52a1\u5668\u7684\u901a\u8baf\u4e2d\u53d1\u751f\u4e86\u9519\u8bef",
@@ -1460,7 +1472,7 @@
 		"tr": "Yap\u0131land\u0131rmadaki (auth.adminpassword) \u015fifrenin \u00f6ntan\u0131ml\u0131 de\u011feri de\u011fi\u015fmedi. L\u00fctfen yap\u0131land\u0131rma dosyas\u0131n\u0131 d\u00fczeltin.",
 		"lt": "Konfig\u016bracijoje esantis slapta\u017eodis (auth.adminpassword) nepakeistas i\u0161 pradin\u0117s reik\u0161m\u0117s. Pra\u0161ome pakeisti konfig\u016bracijos fail\u0105.",
 		"it": "La password definita nella configurazione (auth.adminpassword) non \u00e8 stata cambiata dal valore di default. Si prega di editare il file di configurazione.",
-		"zh-tw": "\u8a2d\u5b9a\u6a94\u88e1\u7684\u5bc6\u78bc(auth.adminpassword)\u9084\u662f\u9810\u8a2d\u503c\uff0c\u8acb\u7de8\u8f2f\u8a2d\u5b9a\u6a94\u3002",
+		"zh-tw": "\u8a2d\u5b9a\u6a94\u88e1\u7684\u5bc6\u78bc (auth.adminpassword) \u9084\u662f\u9810\u8a2d\u503c\uff0c\u8acb\u5148\u7de8\u8f2f\u8a2d\u5b9a\u6a94\u3002",
 		"ja": "\u8a2d\u5b9a\u306e\u30d1\u30b9\u30ef\u30fc\u30c9(auth.adminpassword)\u306f\u65e2\u5b9a\u5024\u304b\u3089\u5909\u66f4\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u3092\u7de8\u96c6\u3057\u3066\u304f\u3060\u3055\u3044\u3002",
 		"et": "Seadistustes on vaikimisi parool (auth.adminpassword) muutmata. Palun muuda seadistustefaili.",
 		"he": "\u05d4\u05e1\u05d9\u05e1\u05de\u05d4 \u05d1\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea (auth.adminpassword)  \u05dc\u05d0 \u05e9\u05d5\u05e0\u05ea\u05d4 \u05de\u05d4\u05e2\u05e8\u05da \u05d4\u05d4\u05ea\u05d7\u05dc\u05ea\u05d9. \u05d0\u05e0\u05d0 \u05e2\u05e8\u05d5\u05da \u05d0\u05ea \u05e7\u05d5\u05d1\u05e5 \u05d4\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea.",
@@ -1570,7 +1582,7 @@
 		"lt": "Prane\u0161imas apie klaid\u0105 i\u0161si\u0173stas",
 		"it": "Rapporto dell'errore inviato",
 		"ja": "\u30a8\u30e9\u30fc\u5831\u544a\u3092\u9001\u4fe1",
-		"zh-tw": "\u932f\u8aa4\u5831\u544a\u9001\u51fa",
+		"zh-tw": "\u932f\u8aa4\u5831\u544a\u5df2\u9001\u51fa",
 		"et": "T\u00f5rkeraport saadetud",
 		"he": "\u05e0\u05e9\u05dc\u05d7 \u05d3\u05d5\u05d7 \u05e9\u05d2\u05d9\u05d0\u05d4",
 		"zh": "\u53d1\u9001\u9519\u8bef\u62a5\u544a",
@@ -1638,7 +1650,7 @@
 		"lt": "Atsijungimo informacija prarasta",
 		"it": "Informazioni di disconnessione smarrite.",
 		"ja": "\u30ed\u30b0\u30a2\u30a6\u30c8\u60c5\u5831\u3092\u5931\u3044\u307e\u3057\u305f",
-		"zh-tw": "\u767b\u51fa\u8a0a\u606f\u907a\u5931",
+		"zh-tw": "\u767b\u51fa\u8cc7\u8a0a\u907a\u5931",
 		"et": "V\u00e4ljalogimisinfo l\u00e4ks kaotsi",
 		"he": "\u05de\u05d9\u05d3\u05e2 \u05d4\u05d4\u05ea\u05e0\u05ea\u05e7\u05d5\u05ea \u05d0\u05d1\u05d3",
 		"zh": "\u4e22\u5931\u4e86\u9000\u51fa\u6d88\u606f",
@@ -1672,7 +1684,7 @@
 		"tr": "Y\u00fcr\u00fcrl\u00fckteki \u00e7\u0131k\u0131\u015f i\u015flemi ile ilgili bilgi kayboldu. \u00c7\u0131kmak istedi\u011finiz servise geri d\u00f6n\u00fcn ve yeniden \u00e7\u0131kmay\u0131 denyin. Bu hata, \u00e7\u0131k\u0131\u015f bilgisinin s\u00fcresi doldu\u011fu i\u00e7in olu\u015fmu\u015f olabilir. \u00c7\u0131k\u0131\u015f bilgisi belirli bir s\u00fcre i\u00e7in tutulur - genellikle birka\u00e7 saat. Bu s\u00fcre normal bir \u00e7\u0131k\u0131\u015f i\u015fleminin tutaca\u011f\u0131ndan daha fazla bir s\u00fcredir; bu hata yap\u0131land\u0131rma ile ilgili ba\u015fka bir hatay\u0131 i\u015faret ediyor olabilir. E\u011fer sorun devam ederse, servis sa\u011flay\u0131c\u0131n\u0131zla ileti\u015fime ge\u00e7iniz.",
 		"lt": "Informacija apie atsijungimo operacij\u0105 prarasta. J\u016bs tur\u0117tum\u0117te sugr\u012f\u017eti \u012f t\u0105 paslaug\u0105, i\u0161 kurios band\u0117te atsijungti ir pabandyti atlikti tai dar kart\u0105. \u0160i klaida gal\u0117jo b\u016bti sukelta, nes baig\u0117si atsijungimo informacijos galiojimo laikas. Informacija apie atsijungim\u0105 yra saugoma ribot\u0105 laiko tarp\u0105 - da\u017eniausiai kelias valandas. Tai yra daugiau nei bet kokia normali atsijungimo informacija gali u\u017etrukti, taigi \u0161i klaida gali b\u016bti sukelta kitos klaidos, kuri \u012fvyko d\u0117l konfig\u016bracijos. Jei problema t\u0119siasi, susisiekite su savo paslaugos teik\u0117ju.",
 		"it": "Le informazioni riguardo all'attuale operazione di disconnessione sono andate perse. Si dovrebbe tornare al servizio da cui si cercava di disconnettersi e provare di nuovo. Questo errore pu\u00f2 essere causato dal termine della validit\u00e0 delle informazioni di disconnessione. Le informazioni per la disconnessione sono conservate per un breve arco temporale, in genere alcune ore. Questo \u00e8 un tempo superiore a quello che una operazione di disconnessione dovrebbe richiedere, quindi questo errore pu\u00f2 indicare un problema di configurazione di qualche altro tipo. Se il problema persiste, consultare il fornitore del service provider.",
-		"zh-tw": "\u907a\u5931\u6b63\u5728\u767b\u51fa\u7684\u76f8\u95dc\u64cd\u4f5c\u8cc7\u8a0a\uff0c\u60a8\u53ef\u80fd\u8981\u56de\u5230\u60a8\u6e96\u5099\u767b\u51fa\u7684\u670d\u52d9\u518d\u767b\u51fa\u4e00\u6b21\u3002\u9019\u500b\u932f\u8aa4\u53ef\u80fd\u662f\u56e0\u70ba\u767b\u51fa\u8cc7\u8a0a\u903e\u6642\u3002\u767b\u51fa\u8cc7\u8a0a\u50c5\u80fd\u5728\u6709\u9650\u7684\u6642\u9593\u88e1\u6709\u6548 - \u901a\u5e38\u662f\u5e7e\u5c0f\u6642\u3002\u9019\u5df2\u7d93\u5927\u65bc\u6b63\u5e38\u7684\u767b\u51fa\u64cd\u4f5c\u6240\u9700\u7684\u6642\u9593\uff0c\u6240\u4ee5\u9019\u500b\u932f\u8aa4\u4e5f\u8a31\u8aaa\u660e\u6709\u4e9b\u5176\u4ed6\u7684\u932f\u8aa4\u88ab\u8a2d\u5b9a\u3002\u5982\u679c\u9019\u500b\u932f\u8aa4\u6301\u7e8c\u5b58\u5728\uff0c\u8acb\u9023\u7d61\u60a8\u7684\u670d\u52d9\u63d0\u4f9b\u8005\u3002",
+		"zh-tw": "\u907a\u5931\u6b63\u5728\u767b\u51fa\u7684\u76f8\u95dc\u64cd\u4f5c\u8cc7\u8a0a\uff0c\u60a8\u53ef\u80fd\u8981\u56de\u5230\u60a8\u6e96\u5099\u767b\u51fa\u7684\u670d\u52d9\u518d\u767b\u51fa\u4e00\u6b21\u3002\u9019\u500b\u932f\u8aa4\u53ef\u80fd\u662f\u56e0\u70ba\u767b\u51fa\u8cc7\u8a0a\u903e\u6642\uff0c\u767b\u51fa\u8cc7\u8a0a\u50c5\u80fd\u5728\u6709\u9650\u7684\u6642\u9593\u88e1\u6709\u6548 - \u901a\u5e38\u662f\u5e7e\u5c0f\u6642\u3002\u9019\u5df2\u7d93\u5927\u65bc\u6b63\u5e38\u7684\u767b\u51fa\u64cd\u4f5c\u6240\u9700\u7684\u6642\u9593\uff0c\u6240\u4ee5\u9019\u500b\u932f\u8aa4\u4e5f\u8a31\u8aaa\u660e\u6709\u4e9b\u5176\u4ed6\u7684\u932f\u8aa4\u88ab\u8a2d\u5b9a\u3002\u5982\u679c\u9019\u500b\u932f\u8aa4\u6301\u7e8c\u5b58\u5728\uff0c\u8acb\u9023\u7d61\u60a8\u7684\u670d\u52d9\u63d0\u4f9b\u8005\u3002",
 		"ja": "The information about the current logout operation has been lost. You should return to the service you were trying to log out from and try to log out again. This error can be caused by the logout information expiring. The logout information is stored for a limited amout of time - usually a number of hours. This is longer than any normal logout operation should take, so this error may indicate some other error with the configuration. If the problem persists, contact your service provider.",
 		"et": "Teave aktiivse v\u00e4ljalogimisoperatsiooni kohta l\u00e4ks kaduma. P\u00f6\u00f6rdu tagasi teenuse juurde, millest soovisid v\u00e4lja logida ja proovi uuesti. See t\u00f5rge v\u00f5ib olla p\u00f5hjustatud v\u00e4ljalogimisinfo aegumisest. V\u00e4ljalogimisinfo salvestatakse piiratud ajaks, tavaliselt m\u00f5neks tunniks. See on kauem kui tavaline v\u00e4ljalogimine peaks aega v\u00f5tma, seega v\u00f5ib see t\u00f5rge anda m\u00e4rku ka m\u00f5nest teisest t\u00f5rkest seadistustes. Kui probleem ei kao, siis v\u00f5ta \u00fchendust oma teenusepakkujaga.",
 		"he": "\u05d4\u05de\u05d9\u05d3\u05e2 \u05e2\u05dc \u05e4\u05e2\u05d5\u05dc\u05ea \u05d4\u05d4\u05ea\u05e0\u05ea\u05e7\u05d5\u05ea \u05d4\u05e0\u05d5\u05db\u05d7\u05d9\u05ea \u05d0\u05d1\u05d3. \u05d0\u05ea\u05d4 \u05e6\u05e8\u05d9\u05da \u05dc\u05d7\u05d6\u05d5\u05e8 \u05dc\u05e9\u05d9\u05e8\u05d5\u05ea \u05de\u05de\u05e0\u05d5 \u05e0\u05d9\u05e1\u05d9\u05ea \u05dc\u05d4\u05ea\u05e0\u05ea\u05e7 \u05d5\u05dc\u05e0\u05e1\u05d5\u05ea \u05e9\u05d5\u05d1. \u05e9\u05d2\u05d9\u05d0\u05d4 \u05d6\u05d5 \u05d9\u05db\u05d5\u05dc\u05d4 \u05dc\u05d4\u05d9\u05d2\u05e8\u05dd \u05e2\u05dc \u05d9\u05d3\u05d9 \u05de\u05d9\u05d3\u05e2 \u05d4\u05ea\u05e0\u05ea\u05e7\u05d5\u05ea \u05e9\u05e4\u05d2 \u05ea\u05d5\u05e7\u05e4\u05d5. \u05de\u05d9\u05d3\u05e2 \u05d4\u05d4\u05ea\u05e0\u05ea\u05e7\u05d5\u05ea \u05de\u05d0\u05d5\u05db\u05e1\u05df \u05dc\u05d6\u05de\u05df \u05de\u05d5\u05d2\u05d1\u05dc - \u05d1\u05d3\u05e8\u05da \u05db\u05dc\u05dc \u05db\u05de\u05d4 \u05e9\u05e2\u05d5\u05ea. \u05e4\u05e8\u05e7 \u05d6\u05de\u05df \u05d0\u05e8\u05d5\u05da \u05d1\u05d4\u05e8\u05d1\u05d4 \u05de\u05db\u05dc \u05d1\u05e7\u05e9\u05ea \u05d4\u05ea\u05e0\u05ea\u05e7\u05d5\u05ea \u05e0\u05d5\u05e8\u05de\u05dc\u05d9\u05ea, \u05dc\u05db\u05df \u05e9\u05d2\u05d9\u05d0\u05d4 \u05d6\u05d5 \u05d9\u05db\u05d5\u05dc\u05d4 \u05dc\u05d4\u05d2\u05e8\u05dd \u05de\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05dc\u05d0 \u05e0\u05db\u05d5\u05e0\u05d5\u05ea. \u05d0\u05dd \u05d4\u05d1\u05e2\u05d9\u05d9\u05d4 \u05de\u05de\u05e9\u05d9\u05db\u05d4, \u05e6\u05d5\u05e8 \u05e7\u05e9\u05e8 \u05e2\u05dd \u05e1\u05e4\u05e7 \u05d4\u05e9\u05e8\u05d5\u05ea.",
@@ -1742,7 +1754,7 @@
 		"lt": "Ne\u017einoma klaida.",
 		"it": "E' stata generata un'eccezione che non \u00e8 stata gestita.",
 		"ja": "\u672a\u51e6\u7406\u4f8b\u5916\u304c\u6295\u3052\u3089\u308c\u307e\u3057\u305f\u3002",
-		"zh-tw": "\u767c\u751f\u4e86\u4e00\u500b\u7121\u6cd5\u9810\u671f\u7684\u4f8b\u5916",
+		"zh-tw": "\u767c\u751f\u4e86\u4e00\u500b\u7121\u6cd5\u9810\u671f\u7684\u4f8b\u5916\u72c0\u6cc1",
 		"et": "Ilmnes k\u00e4sitlemata t\u00f5rge.",
 		"he": "\u05d4\u05d5\u05e9\u05dc\u05db\u05d4 \u05d7\u05e8\u05d9\u05d2\u05d4 \u05dc\u05dc\u05d0 \u05d8\u05d9\u05e4\u05d5\u05dc",
 		"zh": "\u629b\u51fa\u4e00\u4e2a\u672a\u5904\u7406\u7684\u5f02\u5e38",
@@ -1884,7 +1896,7 @@
 		"lt": "\u0160is puslapis nerastas. Prie\u017eastis buvo: %REASON% Puslapio adresas buvo: %URL%",
 		"it": "La pagina data non \u00e8 stata trovata. Motivo: %REASON%, URL: %URL%",
 		"ja": "\u4e0e\u3048\u3089\u308c\u305f\u30da\u30fc\u30b8\u306f\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u7406\u7531\u306f: %REASON% URL\u306f: %URL%",
-		"zh-tw": "\u627e\u4e0d\u5230\u60a8\u6240\u8981\u5b58\u53d6\u7684\u9801\u9762\uff0c\u539f\u56e0\uff1a%REASON%\uff1b\u7db2\u5740\uff1a%URL%",
+		"zh-tw": "\u627e\u4e0d\u5230\u60a8\u6240\u8981\u5b58\u53d6\u7684\u9801\u9762\uff0c\u539f\u56e0\uff1a%REASON% \uff1b\u7db2\u5740\uff1a%URL%",
 		"et": "Seda lehek\u00fclge ei leitud. P\u00f5hjus oli %REASON%. Aadress oli: %URL%",
 		"he": "\u05d4\u05d3\u05e3 \u05d4\u05e0\u05d9\u05ea\u05df \u05dc\u05d0 \u05e0\u05de\u05e6\u05d0. \u05d4\u05e1\u05d9\u05d1\u05d4 \u05d4\u05d9\u05d9\u05ea\u05d4 %REASON% \u05d5\u05d4\u05db\u05ea\u05d5\u05d1\u05ea \u05d4\u05d9\u05d9\u05ea\u05d4 %URL%",
 		"zh": "\u7ed9\u5b9a\u7684\u9875\u9762\u6ca1\u6709\u627e\u5230\uff0c\u539f\u56e0: %REASON%; URL: %URL%",
@@ -2020,7 +2032,7 @@
 		"tr": "Ya bu kullan\u0131c\u0131 ad\u0131nda bir kullan\u0131c\u0131 bulunamad\u0131, yada \u015fifreniz yanl\u0131\u015f. L\u00fctfen kullan\u0131c\u0131 ad\u0131n\u0131 kontrol edin ve yeniden deneyin.",
 		"lt": "Naudotojas su tokiu prisijungimo vardu nerastas, arba neteisingai \u012fved\u0117te slapta\u017eod\u012f. Pasitikrinkite prisijungimo vard\u0105 ir bandykite dar kart\u0105.",
 		"it": "L'utente fornito non \u00e8 stato trovato, oppure la password fornita era sbagliata. Si prega di verificare il nome utente e provare di nuovo",
-		"zh-tw": "\u627e\u4e0d\u5230\u60a8\u6240\u63d0\u4f9b\u7684\u4f7f\u7528\u8005\u540d\u7a31\u4e4b\u4f7f\u7528\u8005\uff0c\u6216\u60a8\u7d66\u4e86\u932f\u8aa4\u5bc6\u78bc\u3002\u8acb\u6aa2\u67e5\u4f7f\u7528\u8005\u4e26\u518d\u8a66\u4e00\u6b21\u3002",
+		"zh-tw": "\u627e\u4e0d\u5230\u60a8\u6240\u63d0\u4f9b\u7684\u4f7f\u7528\u8005\u540d\u7a31\u4e4b\u4f7f\u7528\u8005\uff0c\u6216\u60a8\u7d66\u4e86\u932f\u8aa4\u5bc6\u78bc\uff0c\u8acb\u6aa2\u67e5\u4f7f\u7528\u8005\u5e33\u5bc6\u4e26\u518d\u8a66\u4e00\u6b21\u3002",
 		"ja": "\u30e6\u30fc\u30b6\u30fc\u540d\u304c\u898b\u3064\u304b\u3089\u306a\u304b\u3063\u305f\u304b\u3001\u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u9593\u9055\u3063\u3066\u3044\u308b\u304b\u306e\u4f55\u65b9\u304b\u3067\u3059\u3002\u30e6\u30fc\u30b6\u30fc\u540d\u3001\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u78ba\u8a8d\u3057\u3066\u8a66\u3057\u3066\u304f\u3060\u3055\u3044\u3002",
 		"et": "Kas sellise kasutajatunnusega kasutajat ei leitud v\u00f5i pole sinu poolt sisestatud parool \u00f5ige. Palun kontrolli kasutajatunnust ja parooli uuesti.",
 		"he": "\u05d0\u05d5 \u05e9\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0 \u05de\u05e9\u05ea\u05de\u05e9 \u05d1\u05e9\u05dd \u05d6\u05d4, \u05d0\u05d5 \u05e9\u05d4\u05e1\u05d9\u05e1\u05de\u05d4 \u05dc\u05d0 \u05d4\u05d9\u05d9\u05ea\u05d4 \u05e0\u05db\u05d5\u05e0\u05d4. \u05d1\u05d3\u05d5\u05e7 \u05d1\u05d1\u05e7\u05e9\u05d4 \u05d0\u05ea \u05e9\u05dd \u05d4\u05de\u05e9\u05ea\u05de\u05e9 \u05d5\u05e0\u05e1\u05d4 \u05e9\u05d5\u05d1. ",
@@ -2089,7 +2101,7 @@
 		"lt": "Tapatybi\u0173 teik\u0117jas atsak\u0117 klaidos prane\u0161imu. (Statuso kodas SAML atsakyme buvo nes\u0117kmingas)",
 		"it": "L'Identity Provider ha risposto con un errore. (Il codice di stato nel messaggio SAML Response non indicava un successo)",
 		"hu": "Hiba t\u00f6rt\u00e9nt az azonos\u00edt\u00f3 szervezet (IdP) oldal\u00e1n. Ismeretlen \u00e1llapotk\u00f3d.",
-		"zh-tw": "\u9a57\u8b49\u63d0\u4f9b\u8005\u56de\u61c9\u4e00\u500b\u932f\u8aa4\u3002(\u5728 SAML \u56de\u61c9\u88e1\u7684\u72c0\u614b\u78bc\u70ba\u4e0d\u6210\u529f)",
+		"zh-tw": "\u9a57\u8b49\u63d0\u4f9b\u8005\u56de\u61c9\u4e00\u500b\u932f\u8aa4\u3002(\u5728 SAML \u56de\u61c9\u88e1\u7684\u72c0\u614b\u78bc\u986f\u793a\u70ba\u4e0d\u6210\u529f)",
 		"ja": "\u30a2\u30a4\u30c7\u30f3\u30c6\u30a3\u30c6\u30a3\u30d7\u30ed\u30d0\u30a4\u30c0\u304c\u30a8\u30e9\u30fc\u3092\u53d7\u3051\u3068\u308a\u307e\u3057\u305f\u3002(SAML\u30ec\u30b9\u30dd\u30f3\u30b9\u306b\u5931\u6557\u3057\u305f\u30b9\u30c6\u30fc\u30bf\u30b9\u30b3\u30fc\u30c9)",
 		"et": "Identiteedipakkuja vastas t\u00f5rkega (SAML-vastuse olekukood polnud positiivne).",
 		"he": "\u05e1\u05e4\u05e7 \u05d4\u05d6\u05d9\u05d4\u05d5\u05ea \u05d4\u05d7\u05d6\u05d9\u05e8 \u05e9\u05d2\u05d9\u05d0\u05d4. (\u05e7\u05d5\u05d3 \u05d4\u05de\u05e6\u05d1 \u05d1\u05ea\u05d2\u05d5\u05d1\u05ea \u05d4 SAML \u05e9\u05d5\u05e0\u05d4 \u05de\u05d4\u05e6\u05dc\u05d7\u05d4)",
@@ -2220,7 +2232,7 @@
 		"ja": "\u8a8d\u8a3c\u5931\u6557: \u3042\u306a\u305f\u306e\u30d6\u30e9\u30a6\u30b6\u306f\u7121\u52b9\u304b\u8aad\u3080\u3053\u3068\u306e\u51fa\u6765\u306a\u3044\u8a3c\u660e\u66f8\u3092\u9001\u4fe1\u3057\u307e\u3057\u305f\u3002",
 		"da": "Authentifikation fejlede: Certifikatet som din browser har sendt er ugyldigt og kan ikke l\u00e6ses",
 		"hr": "Neuspje\u0161na autentifikacija: digitalni certifikat koji je poslao va\u0161 web preglednik nije ispravan ili se ne mo\u017ee pro\u010ditati",
-		"zh-tw": "\u9a57\u8b49\u5931\u6557\uff1a\u60a8\u7684\u700f\u89bd\u5668\u50b3\u9001\u7684\u6191\u8b49\u70ba\u7121\u6548\u6216\u7121\u6cd5\u8b80\u53d6",
+		"zh-tw": "\u9a57\u8b49\u5931\u6557\uff1a\u60a8\u7684\u700f\u89bd\u5668\u50b3\u9001\u4e4b\u6191\u8b49\u70ba\u7121\u6548\u6216\u7121\u6cd5\u8b80\u53d6",
 		"et": "Autentimine ei \u00f5nnestunud: brauseri poolt saadetud sertifikaat on vigane v\u00f5i pole loetav",
 		"he": "\u05d4\u05d4\u05d9\u05d6\u05d3\u05d4\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4: \u05d4\u05ea\u05e2\u05d5\u05d3\u05d4 \u05e9\u05d4\u05d3\u05e4\u05d3\u05e4\u05df \u05e9\u05dc\u05d7 \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9\u05ea \u05d0\u05d5 \u05dc\u05d0 \u05e0\u05d9\u05ea\u05e0\u05ea \u05dc\u05e7\u05e8\u05d9\u05d0\u05d4",
 		"pt-br": "Falha na Autentica\u00e7\u00e3o: O certificado que seu navegador (browser) enviou \u00e9 inv\u00e1lido ou n\u00e3o pode ser lido",
@@ -2374,7 +2386,7 @@
 		"sl": "Podatki o stanju so izgubljeni",
 		"no": "Tilstandsinformasjon tapt",
 		"ja": "\u72b6\u614b\u60c5\u5831\u3092\u5931\u3044\u307e\u3057\u305f",
-		"zh-tw": "\u907a\u5931\u72c0\u614b\u8cc7\u8a0a",
+		"zh-tw": "\u72c0\u614b\u8cc7\u8a0a\u907a\u5931",
 		"et": "Olekuinfo kadunud",
 		"he": "\u05d0\u05d1\u05d3 \u05de\u05d9\u05d3\u05e2 \u05d4\u05de\u05e6\u05d1",
 		"de": "Statusinformationen verloren",
@@ -2406,7 +2418,7 @@
 		"sl": "Podatki o stanju so izgubljeni, zato zahteve ni mogo\u010de obnoviti\/ponovno zagnati.",
 		"no": "Tilstandsinformasjon tapt, det er ikke mulig \u00e5 gjenoppta foresp\u00f8rselen",
 		"ja": "\u72b6\u614b\u60c5\u5831\u3092\u5931\u3044\u3001\u30ea\u30af\u30a8\u30b9\u30c8\u3092\u518d\u958b\u51fa\u6765\u307e\u305b\u3093",
-		"zh-tw": "\u907a\u5931\u72c0\u614b\u8cc7\u8a0a\uff0c\u4e14\u7121\u6cd5\u91cd\u65b0\u8acb\u6c42",
+		"zh-tw": "\u72c0\u614b\u8cc7\u8a0a\u907a\u5931\uff0c\u4e14\u7121\u6cd5\u91cd\u65b0\u555f\u52d5\u8acb\u6c42",
 		"et": "Olekuinfo l\u00e4ks kaduma ja p\u00e4ringut pole v\u00f5imalik uuesti k\u00e4ivitada",
 		"he": "\u05d0\u05d1\u05d3 \u05de\u05d9\u05d3\u05e2 \u05d4\u05de\u05e6\u05d1, \u05d5\u05d0\u05d9 \u05d0\u05e4\u05e9\u05e8 \u05dc\u05d4\u05ea\u05d7\u05dc \u05de\u05d7\u05d3\u05e9 \u05d0\u05ea \u05d4\u05d1\u05e7\u05e9\u05d4",
 		"de": "Die Statusinformationen gingen verloren und die Anfrage kann nicht neu gestartet werden",
@@ -2440,7 +2452,7 @@
 		"ja": "\u30e1\u30bf\u30c7\u30fc\u30bf\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093",
 		"et": "Metaandmeid ei leitud",
 		"he": "\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0 \u05de\u05d8\u05d0-\u05de\u05d9\u05d3\u05e2",
-		"zh-tw": "\u627e\u4e0d\u5230\u8a6e\u91cb\u8cc7\u6599",
+		"zh-tw": "\u627e\u4e0d\u5230 Metadata",
 		"de": "Keine Metadaten gefunden",
 		"zh": "\u6ca1\u6709\u627e\u5230\u5143\u4fe1\u606f",
 		"lt": "Metaduomenys nerasti",
@@ -2472,7 +2484,7 @@
 		"ja": "%ENTITYID% \u306e\u30e1\u30bf\u30c7\u30fc\u30bf\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093",
 		"et": "Olemi metaandmeid ei leitud: %ENTITYID%",
 		"he": "\u05dc\u05d0 \u05e0\u05d9\u05ea\u05df \u05dc\u05d0\u05ea\u05e8 \u05de\u05d8\u05d0-\u05de\u05d9\u05d3\u05e2 \u05e2\u05d1\u05d5\u05e8 %ENTITYID%",
-		"zh-tw": "\u7121\u6cd5\u627e\u5230\u8a6e\u91cb\u8cc7\u6599\u65bc %ENTITYID%",
+		"zh-tw": "\u7121\u6cd5\u627e\u5230 Metadata \u65bc %ENTITYID%",
 		"de": "Keine Metadaten f\u00fcr %ENTITYID% gefunden",
 		"zh": "\u65e0\u6cd5\u4e3a%ENTITYID%\u5b9a\u4f4d\u5143\u4fe1\u606f",
 		"lt": "Nepavyko rasti objekto %ENTITYID% metaduomen\u0173",
@@ -2559,9 +2571,11 @@
 		"el": "\u03a0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03ac\u03c3\u03c4\u03b7\u03ba\u03b5 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03af\u03b1 \u03bc\u03b5 \u03c4\u03b7\u03bd \u03c0\u03b7\u03b3\u03ae \u03c4\u03b1\u03c5\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 %AUTHSOURCE%: %REASON%"
 	},
 	"title_MEMCACHEDOWN": {
+		"zh-tw": "\u7121\u6cd5\u53d6\u5f97\u9023\u7dda Session \u8cc7\u8a0a",
 		"el": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b1\u03bd\u03ac\u03ba\u03c4\u03b7\u03c3\u03b7 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03c3\u03c5\u03bd\u03b5\u03b4\u03c1\u03af\u03b1\u03c2"
 	},
 	"descr_MEMCACHEDOWN": {
+		"zh-tw": "\u60a8\u7684\u9023\u7dda Session \u8cc7\u8a0a\u56e0\u70ba\u6280\u8853\u56f0\u96e3\u73fe\u5728\u7121\u6cd5\u53d6\u5f97\uff0c\u8acb\u7a0d\u5f85\u5e7e\u5206\u9418\u5f8c\u518d\u91cd\u8a66",
 		"el": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b1\u03bd\u03ac\u03ba\u03c4\u03b7\u03c3\u03b7 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03c3\u03c5\u03bd\u03b5\u03b4\u03c1\u03af\u03b1\u03c2 \u03bb\u03cc\u03b3\u03c9 \u03c4\u03b5\u03c7\u03bd\u03b9\u03ba\u03ce\u03bd \u03b4\u03c5\u03c3\u03ba\u03bf\u03bb\u03b9\u03ce\u03bd. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1"
 	}
 }
diff --git a/dictionaries/general.translation.json b/dictionaries/general.translation.json
index c11c6e5034dc94fbaae04ae5ecd1e34816a0f5a3..8d36241bceeb72b1f361b8b5dd16728c77d57dad 100644
--- a/dictionaries/general.translation.json
+++ b/dictionaries/general.translation.json
@@ -57,7 +57,7 @@
     "it": "No",
     "lt": "Ne",
     "ja": "\u3044\u3044\u3048",
-    "zh-tw": "\u4e0d\uff0c\u53d6\u6d88",
+    "zh-tw": "\u5426",
     "et": "Ei",
     "he": "\u05dc\u05d0",
     "ru": "\u041d\u0435\u0442",
@@ -165,7 +165,7 @@
     "it": "No, cancellare",
     "lt": "Ne, nutraukti",
     "ja": "\u3044\u3044\u3048\u3001\u30ad\u30e3\u30f3\u30bb\u30eb\u3057\u307e\u3059",
-    "zh-tw": "\u4e0d\uff0c\u53d6\u6d88",
+    "zh-tw": "\u5426\uff0c\u53d6\u6d88",
     "et": "Ei, loobu",
     "he": "\u05dc\u05d0, \u05d1\u05d8\u05dc",
     "ru": "\u041d\u0435\u0442, \u043e\u0442\u043c\u0435\u043d\u0438\u0442\u044c",
@@ -215,4 +215,4 @@
     "af": "Diens Verskaffer",
     "el": "\u03a0\u03ac\u03c1\u03bf\u03c7\u03bf\u03c2 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1\u03c2"
   }
-}
\ No newline at end of file
+}
diff --git a/dictionaries/login.translation.json b/dictionaries/login.translation.json
index 8e750d6a2418bb280b4b30f4df5607bda1bb2dfe..ac03e326cc5ba2f484c0afd01e3bd552fcf57ebc 100644
--- a/dictionaries/login.translation.json
+++ b/dictionaries/login.translation.json
@@ -95,7 +95,7 @@
 		"lt": "Paslauga pra\u0161o autentikacijos. \u017demiau \u012fveskite savo prisijungimo vard\u0105 ir slapta\u017eod\u012f.",
 		"it": "Un servizio ha richiesto l'autenticazione. Si prega di inserire le proprie credenziali nella maschera di login sottostante.",
 		"ja": "\u30b5\u30fc\u30d3\u30b9\u306f\u3042\u306a\u305f\u81ea\u8eab\u306e\u8a8d\u8a3c\u3092\u8981\u6c42\u3057\u3066\u3044\u307e\u3059\u3002\u4ee5\u4e0b\u306e\u30d5\u30a9\u30fc\u30e0\u306b\u30e6\u30fc\u30b6\u30fc\u540d\u3068\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002",
-		"zh-tw": "\u8acb\u4f7f\u7528\u5e33\u865f\u5bc6\u78bc\u767b\u5165\uff0c\u4ee5\u4fbf\u9032\u5165\u7cfb\u7d71\u3002",
+		"zh-tw": "\u6709\u500b\u670d\u52d9\u9700\u60a8\u9032\u884c\u9a57\u8b49\uff0c\u8acb\u65bc\u4e0b\u65b9\u8f38\u5165\u5e33\u865f\u5bc6\u78bc\u3002",
 		"et": "Teenus n\u00f5uab autentimist. Palun sisesta allpool olevasse vormi oma kasutajatunnus ja parool.",
 		"he": "\u05e9\u05d9\u05e8\u05d5\u05ea \u05d1\u05d9\u05e7\u05e9 \u05e9\u05ea\u05d6\u05d3\u05d4\u05d4. \u05d0\u05e0\u05d0 \u05d4\u05db\u05e0\u05e1 \u05d0\u05ea \u05e9\u05dd \u05d4\u05de\u05e9\u05ea\u05de\u05e9 \u05d5\u05d4\u05e1\u05d9\u05e1\u05de\u05d4 \u05e9\u05dc\u05da \u05d1\u05d8\u05d5\u05e4\u05e1 \u05de\u05ea\u05d7\u05ea.",
 		"ru": "\u0421\u043b\u0443\u0436\u0431\u0430 \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u043f\u0430\u0440\u043e\u043b\u044c.",
@@ -147,6 +147,9 @@
 		"af": "Meld aan",
 		"el": "\u0395\u03af\u03c3\u03bf\u03b4\u03bf\u03c2"
 	},
+	"processing": {
+		"zh-tw": "\u8655\u7406\u4e2d..."
+	},
 	"username": {
 		"no": "Brukernavn",
 		"nn": "Brukarnamn",
@@ -284,7 +287,7 @@
 		"lt": "Pagalbos! Nepamenu savo slapta\u017eod\u017eio.",
 		"it": "Aiuto! Non ricordo la mia password.",
 		"ja": "\u305f\u3059\u3051\u3066! \u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u601d\u3044\u51fa\u305b\u307e\u305b\u3093\u3002",
-		"zh-tw": "\u7cdf\u7cd5\uff01\u5fd8\u8a18\u5bc6\u78bc\u4e86\u3002",
+		"zh-tw": "\u6c42\u6551\uff01\u6211\u5fd8\u8a18\u5bc6\u78bc\u4e86\u3002",
 		"et": "Appi! Ma ei m\u00e4leta parooli.",
 		"he": "\u05d4\u05e6\u05d9\u05dc\u05d5! \u05e9\u05db\u05d7\u05ea\u05d9 \u05d0\u05ea \u05d4\u05e1\u05d9\u05e1\u05de\u05d4.",
 		"ru": "\u041f\u043e\u043c\u043e\u0433\u0438\u0442\u0435! \u042f \u043d\u0435 \u043f\u043e\u043c\u043d\u044e \u0441\u0432\u043e\u0439 \u043f\u0430\u0440\u043e\u043b\u044c.",
@@ -319,7 +322,7 @@
 		"lt": "Blogai - be prisijungimo vardo ir slapta\u017eod\u017eio negal\u0117site autentikuotis ir patekti \u012f reikiam\u0105 paslaug\u0105. Galb\u016bt yra kas Jums gal\u0117t\u0173 pad\u0117ti. Susisiekite su savo universiteto vartotoj\u0173 aptarnavimo specialistais.",
 		"it": "Senza il nome utente e la password, non \u00e8 possibile effettuare l'autenticazione al servizio. C'\u00e8 probabilmente qualcuno che pu\u00f2 fornire aiuto. Consultare il proprio help desk.",
 		"ja": "\u304a\u6c17\u306e\u6bd2\u3067\u3059! - \u30e6\u30fc\u30b6\u30fc\u540d\u3068\u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u7121\u304f\u3066\u306f\u30b5\u30fc\u30d3\u30b9\u306b\u30a2\u30af\u30bb\u30b9\u3059\u308b\u70ba\u306b\u3042\u306a\u305f\u81ea\u8eab\u3092\u8a8d\u8a3c\u3059\u308b\u4e8b\u304c\u51fa\u6765\u307e\u305b\u3093\u3002\u3042\u306a\u305f\u306e\u5927\u5b66\u306e\u30d8\u30eb\u30d7\u30c7\u30b9\u30af\u306b\u76f8\u8ac7\u3059\u308b\u3068\u3001\u3042\u306a\u305f\u306e\u52a9\u3051\u306b\u306a\u3063\u3066\u304f\u308c\u308b\u3067\u3057\u3087\u3046\u3002",
-		"zh-tw": "\u5594\u5594\uff01\u5982\u679c\u60a8\u7684\u5e33\u865f\u548c\u5bc6\u78bc\u932f\u8aa4\uff0c\u7cfb\u7d71\u5c07\u7121\u6cd5\u63d0\u4f9b\u76f8\u95dc\u670d\u52d9\uff01",
+		"zh-tw": "\u6c92\u6709\u60a8\u7684\u5e33\u865f\u5bc6\u78bc\u7684\u8a71\u5c07\u6703\u7121\u6cd5\u5b58\u53d6\u670d\u52d9\u5537\uff01\u8acb\u806f\u7e6b\u60a8\u7684\u7d44\u7e54\u670d\u52d9\u53f0\uff0c\u4e5f\u8a31\u6703\u6709\u4eba\u80fd\u5e6b\u52a9\u4f60\u3002",
 		"et": "Paha lugu! Ilma kasutajatunnust ja parooli teadmata pole v\u00f5imalik seda teenust kasutada. Loodetavasti saab sind keegi aidata. V\u00f5ta \u00fchendust oma \u00fclikooli kasutajatoeteenusega!",
 		"he": "\u05d7\u05d1\u05dc! - \u05d1\u05dc\u05d9 \u05e9\u05dd \u05d4\u05de\u05e9\u05ea\u05de\u05e9 \u05d5\u05d4\u05e1\u05d9\u05e1\u05de\u05d4 \u05e9\u05dc\u05da \u05d0\u05ea\u05d4 \u05dc\u05d0 \u05d9\u05db\u05d5\u05dc \u05dc\u05d4\u05d6\u05d3\u05d4\u05d5\u05ea \u05d1\u05db\u05d3\u05d9 \u05dc\u05d2\u05e9\u05ea \u05dc\u05e9\u05d9\u05e8\u05d5\u05ea. \u05d9\u05db\u05d5\u05dc \u05dc\u05d4\u05d9\u05d5\u05ea \u05e9\u05d9\u05e9 \u05de\u05d9\u05e9\u05d4\u05d5 \u05e9\u05d9\u05db\u05d5\u05dc \u05dc\u05e2\u05d6\u05d5\u05e8 \u05dc\u05da. \u05e4\u05e0\u05d4 \u05dc\u05ea\u05de\u05d9\u05db\u05d4 \u05d4\u05d8\u05db\u05e0\u05d9\u05ea \u05d1\u05d0\u05d5\u05e0\u05d9\u05d1\u05e8\u05e1\u05d9\u05d8\u05d4 \u05e9\u05dc\u05da!",
 		"ru": "\u041e\u0447\u0435\u043d\u044c \u043f\u043b\u043e\u0445\u043e! - \u0411\u0435\u0437 \u0432\u0430\u0448\u0438\u0445 \u0438\u043c\u0435\u043d\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u043f\u0430\u0440\u043e\u043b\u044f \u0432\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u0432\u0430\u0448\u0435 \u043f\u0440\u0430\u0432\u043e \u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0441\u043b\u0443\u0436\u0431\u0435. \u041c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0435\u0441\u0442\u044c \u043a\u0442\u043e-\u043d\u0438\u0431\u0443\u0434\u044c, \u043a\u0442\u043e \u0441\u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043c\u043e\u0447\u044c \u0432\u0430\u043c. \u041f\u0440\u043e\u043a\u043e\u043d\u0441\u0443\u043b\u044c\u0442\u0438\u0440\u0443\u0439\u0442\u0435\u0441\u044c \u0441\u043e \u0441\u0432\u043e\u0435\u0439 \u0441\u043b\u0443\u0436\u0431\u043e\u0439 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 \u0432 \u0432\u0430\u0448\u0435\u043c \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0438\u0442\u0435\u0442\u0435!",
@@ -355,7 +358,7 @@
 		"lt": "J\u016bs ka\u017ek\u0105 nusiunt\u0117te \u012f prisijungimo puslap\u012f, ta\u010diau d\u0117l ka\u017ekoki\u0173 prie\u017eas\u010di\u0173 slapta\u017eodis nebuvo nusi\u0173stas. Pra\u0161ome bandyti dar kart\u0105.",
 		"it": "Sono state inviate delle informazioni alla pagina di login, ma per qualche motivo la password risulta mancante. Si prega di riprovare.",
 		"ja": "\u3042\u306a\u305f\u306f\u30ed\u30b0\u30a4\u30f3\u30da\u30fc\u30b8\u3067\u4f55\u304b\u3092\u9001\u4fe1\u3057\u307e\u3057\u305f\u304c\u3001\u4f55\u3089\u304b\u306e\u7406\u7531\u3067\u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u9001\u4fe1\u3055\u308c\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u518d\u5ea6\u8a66\u3057\u3066\u307f\u3066\u304f\u3060\u3055\u3044\u3002",
-		"zh-tw": "\u60a8\u53ef\u80fd\u6709\u50b3\u9001\u81f3\u7db2\u9801\uff0c\u4f46\u662f\u5bc6\u78bc\u56e0\u70ba\u67d0\u4e9b\u539f\u56e0\u672a\u50b3\u9001\uff0c\u8acb\u91cd\u65b0\u767b\u5165\u3002",
+		"zh-tw": "\u60a8\u7684\u78ba\u50b3\u9001\u4e86\u4e00\u4e9b\u8a0a\u606f\u81f3\u767b\u5165\u9801\u9762\uff0c\u4f46\u662f\u5bc6\u78bc\u4f3c\u4e4e\u672a\u88ab\u9001\u51fa\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002",
 		"et": "Sa saatsid midagi sisselogimislehele, kuid miskip\u00e4rast parooli ei saadetud. Palun proovi uuesti.",
 		"he": "\u05e9\u05dc\u05d7\u05ea \u05de\u05e9\u05d4\u05d5 \u05dc\u05d3\u05e3 \u05d4\u05db\u05e0\u05d9\u05e1\u05d4 \u05dc\u05de\u05e2\u05e8\u05db\u05ea, \u05d0\u05d1\u05dc \u05d1\u05d2\u05dc\u05dc \u05e1\u05d9\u05d1\u05d4 \u05db\u05dc \u05e9\u05d4\u05d9\u05d0 \u05d4\u05e1\u05d9\u05e1\u05de\u05d4 \u05dc\u05d0 \u05e0\u05e9\u05dc\u05d7\u05d4. \u05d1\u05d1\u05e7\u05e9\u05d4 \u05e0\u05e1\u05d4 \u05e9\u05d5\u05d1.",
 		"ru": "\u0412\u044b \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043b\u0438 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0432\u0445\u043e\u0434\u0430, \u043d\u043e \u043f\u043e \u043a\u0430\u043a\u0438\u043c-\u0442\u043e \u043f\u0440\u0438\u0447\u0438\u043d\u0430\u043c \u043f\u0430\u0440\u043e\u043b\u044c \u043d\u0435 \u043f\u043e\u0441\u043b\u0430\u043d. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043d\u043e\u0432\u0430.",
@@ -463,7 +466,7 @@
 		"lt": "Pasirinkite savo organizacij\u0105",
 		"it": "Selezionare la propria organizzazione",
 		"ja": "\u3042\u306a\u305f\u306e\u7d44\u7e54\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044",
-		"zh-tw": "\u9078\u64c7\u60a8\u7684\u9810\u8a2d\u7d44\u7e54",
+		"zh-tw": "\u9078\u64c7\u60a8\u7684\u6240\u5c6c\u7d44\u7e54",
 		"et": "Vali oma koduorganisatsioon",
 		"he": "\u05d1\u05d7\u05e8 \u05d0\u05ea \u05d0\u05d9\u05e8\u05d2\u05d5\u05df \u05d4\u05d1\u05d9\u05ea \u05e9\u05dc\u05da",
 		"ru": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0432\u0430\u0448\u0443 \u0434\u043e\u043c\u0430\u0448\u043d\u044e\u044e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044e",
@@ -498,7 +501,7 @@
 		"lt": "Pakeisti savo organizacij\u0105",
 		"it": "Cambiare la propria organizzazione",
 		"ja": "\u3042\u306a\u305f\u306e\u7d44\u7e54\u3092\u5909\u66f4\u3057\u3066\u304f\u3060\u3055\u3044",
-		"zh-tw": "\u8b8a\u66f4\u60a8\u7684\u9810\u8a2d\u7d44\u7e54",
+		"zh-tw": "\u8b8a\u66f4\u60a8\u7684\u6240\u5c6c\u7d44\u7e54",
 		"et": "Muuda oma koduorganisatsiooni",
 		"he": "\u05d4\u05d7\u05dc\u05e3 \u05d0\u05ea \u05d0\u05d9\u05e8\u05d2\u05d5\u05df \u05d4\u05d1\u05d9\u05ea \u05e9\u05dc\u05da",
 		"ru": "\u0421\u043c\u0435\u043d\u0438\u0442\u044c \u0434\u043e\u043c\u0430\u0448\u043d\u044e\u044e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044e",
@@ -533,7 +536,7 @@
 		"lt": "J\u016bs savo nam\u0173 organizacija pasirinkote <b>%HOMEORG%<\/b>. Jei tai yra neteisingas pasirinkimas, galite pasirinkti kit\u0105.",
 		"it": "E' stata selezionata <b>%HOMEORG%<\/b> come propria organizzazione. Se \u00e8 sbagliata, \u00e8 possibile selezionarne un'altra.",
 		"ja": "\u3042\u306a\u305f\u306f <b>%HOMEORG%<\/b> \u3092\u7d44\u7e54\u3068\u3057\u3066\u9078\u629e\u3057\u307e\u3057\u305f\u3002\u3053\u308c\u306b\u554f\u984c\u304c\u3042\u308b\u5834\u5408\u306f\u4ed6\u306e\u3082\u306e\u3092\u9078\u3076\u4e8b\u3082\u53ef\u80fd\u3067\u3059\u3002",
-		"zh-tw": "\u60a8\u5df2\u9078\u64c7 <b>%HOMEORG%<\\\/b> \u4f5c\u70ba\u9810\u8a2d\u7d44\u7e54\u3002\u5982\u679c\u932f\u8aa4\uff0c\u60a8\u96a8\u6642\u90fd\u53ef\u4ee5\u91cd\u65b0\u9078\u64c7\u3002",
+		"zh-tw": "\u60a8\u5df2\u9078\u64c7 <b>%HOMEORG%<\/b> \u4f5c\u70ba\u6240\u5c6c\u7d44\u7e54\u3002\u5982\u679c\u932f\u8aa4\uff0c\u60a8\u96a8\u6642\u90fd\u53ef\u4ee5\u91cd\u65b0\u9078\u64c7\u5176\u4ed6\u7684\u3002",
 		"et": "Sa valisid oma koduorganisatsiooniks <b>%HOMEORG%<\/b>. Kui see pole \u00f5ige, siis v\u00f5id uuesti valida.",
 		"he": "\u05d1\u05d7\u05e8\u05ea \u05d0\u05ea <b>%HOMEORG%<\/b> \u05db\u05d0\u05d9\u05e8\u05d2\u05d5\u05df \u05d4\u05d1\u05d9\u05ea \u05e9\u05dc\u05da. \u05d0\u05dd \u05d4\u05de\u05d9\u05d3\u05e2 \u05de\u05d5\u05d8\u05e2\u05d4 \u05d0\u05ea\u05d4 \u05d9\u05db\u05d5\u05dc \u05dc\u05d1\u05d7\u05d5\u05e8 \u05d0\u05d9\u05e8\u05d2\u05d5\u05df \u05d0\u05d7\u05e8.",
 		"ru": "\u0412\u044b \u0432\u044b\u0431\u0440\u0430\u043b\u0438 <b>%HOMEORG%<\/b> \u043a\u0430\u043a \u0432\u0430\u0448\u0443 \u0434\u043e\u043c\u0430\u0448\u043d\u044e\u044e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044e. \u0415\u0441\u043b\u0438 \u0432\u044b \u043e\u0448\u0438\u0431\u043b\u0438\u0441\u044c - \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u0434\u0440\u0443\u0433\u0443\u044e.",
@@ -568,7 +571,7 @@
 		"lt": "Pasirinkite organizacij\u0105",
 		"it": "Selezionare la propria organizzazione",
 		"ja": "\u7d44\u7e54\u306e\u9078\u629e",
-		"zh-tw": "\u9078\u64c7\u9810\u8a2d\u7d44\u7e54",
+		"zh-tw": "\u9078\u64c7\u6240\u5c6c\u7d44\u7e54",
 		"et": "Vali koduorganisatsioon",
 		"he": "\u05d4\u05d7\u05dc\u05e3 \u05d0\u05d9\u05e8\u05d2\u05d5\u05df \u05d1\u05d9\u05ea",
 		"ru": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u043e\u043c\u0430\u0448\u043d\u044e\u044e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044e",
@@ -603,7 +606,7 @@
 		"lt": "Naudotoj\u0173 aptarnavimo puslapis",
 		"it": "Homepage del servizio di assistenza",
 		"ja": "\u30d8\u30eb\u30d7\u30c7\u30b9\u30af\u30da\u30fc\u30b8",
-		"zh-tw": "\u5354\u52a9\u9801\u9762",
+		"zh-tw": "\u670d\u52d9\u53f0\u9996\u9801",
 		"et": "Kasutajatoe koduleht",
 		"he": "\u05ea\u05de\u05d9\u05db\u05d4 \u05d8\u05db\u05e0\u05d9\u05ea",
 		"ru": "\u0414\u043e\u043c\u0430\u0448\u043d\u044f\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0441\u043b\u0443\u0436\u0431\u044b \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438",
diff --git a/dictionaries/logout.translation.json b/dictionaries/logout.translation.json
index 12d46abc95ba2815f923d7eaecc84a1f83e66ca2..dd7e2ead5e218163061b0da4104bf418b41e31dd 100644
--- a/dictionaries/logout.translation.json
+++ b/dictionaries/logout.translation.json
@@ -21,7 +21,7 @@
 		"it": "Disconnesso",
 		"lt": "Atsijungta",
 		"ja": "\u30ed\u30b0\u30a2\u30a6\u30c8",
-		"zh-tw": "\u6a19\u984c",
+		"zh-tw": "\u767b\u51fa",
 		"et": "Logis v\u00e4lja",
 		"he": "\u05d4\u05ea\u05e0\u05ea\u05e7\u05d5\u05ea \u05de\u05d4\u05de\u05e2\u05e8\u05db\u05ea",
 		"zh": "\u9000\u51fa",
@@ -56,7 +56,7 @@
 		"it": "Sei stato disconnesso",
 		"lt": "J\u016bs buvote atjungtas nuo sistemos.",
 		"ja": "\u30ed\u30b0\u30a2\u30a6\u30c8\u3057\u307e\u3057\u305f\u3002",
-		"zh-tw": "\u60a8\u5df2\u767b\u51fa",
+		"zh-tw": "\u60a8\u5df2\u767b\u51fa\u3002",
 		"et": "Sa oled v\u00e4lja logitud.",
 		"he": "\u05d4\u05ea\u05e0\u05ea\u05e7\u05ea \u05de\u05df \u05d4\u05de\u05e2\u05e8\u05db\u05ea",
 		"zh": "\u4f60\u5df2\u7ecf\u9000\u51fa\u4e86",
@@ -124,7 +124,7 @@
 		"lt": "Pra\u0161ome palaukti",
 		"it": "In attesa",
 		"ja": "\u4fdd\u7559",
-		"zh-tw": "\u66ab\u505c",
+		"zh-tw": "\u4fdd\u7559",
 		"et": "Ootel",
 		"he": "\u05d1\u05d4\u05e9\u05e2\u05d9\u05d9\u05d4",
 		"pt-br": "Aguardando",
@@ -159,7 +159,7 @@
 		"lt": "Atlikta",
 		"it": "Completato",
 		"ja": "\u5b8c\u4e86\u3057\u307e\u3057\u305f",
-		"zh-tw": "\u5df2\u5b8c\u6210",
+		"zh-tw": "\u5b8c\u6210",
 		"et": "L\u00f5petatud",
 		"he": "\u05d4\u05e1\u05ea\u05d9\u05d9\u05dd",
 		"pt-br": "Completado",
@@ -369,7 +369,7 @@
 		"hr": "Tako\u0111er ste prijavljeni u sljede\u0107im servisima:",
 		"lt": "J\u016bs taip pat esate prisijung\u0119s prie:",
 		"ja": "\u3042\u306a\u305f\u306f\u307e\u3060\u3053\u308c\u3089\u306e\u30b5\u30fc\u30d3\u30b9\u306b\u30ed\u30b0\u30a4\u30f3\u3057\u3066\u3044\u307e\u3059:",
-		"zh-tw": "\u60a8\u9084\u6301\u7e8c\u767b\u5165\u4e0b\u5217\u670d\u52d9\uff1a",
+		"zh-tw": "\u60a8\u540c\u6642\u4e5f\u767b\u5165\u4e86\u9019\u4e9b\u670d\u52d9\uff1a",
 		"et": "Sa oled sisse logitud ja nendesse teenustesse:",
 		"he": "\u05d0\u05ea\u05d4 \u05de\u05d7\u05d5\u05d1\u05e8 \u05d2\u05dd \u05dc\u05e9\u05e8\u05d5\u05ea\u05d9\u05dd \u05d4\u05d1\u05d0\u05d9\u05dd:",
 		"pt-br": "Voc\u00ea tamb\u00e9m est\u00e1 logado nestes servi\u00e7os:",
@@ -405,7 +405,7 @@
 		"hr": "\u017delite li se odjaviti iz svih gore navedenih servisa?",
 		"lt": "Ar norite atsijungti nuo vis\u0173 \u017eemiau i\u0161vardint\u0173 paslaug\u0173?",
 		"ja": "\u4e0a\u8a18\u306e\u5168\u3066\u306e\u30b5\u30fc\u30d3\u30b9\u304b\u3089\u30ed\u30b0\u30a2\u30a6\u30c8\u3057\u307e\u3059\u304b?",
-		"zh-tw": "\u662f\u5426\u767b\u51fa\u6240\u6709\u670d\u52d9\uff1f",
+		"zh-tw": "\u662f\u5426\u767b\u51fa\u4e0a\u8ff0\u6240\u6709\u670d\u52d9\uff1f",
 		"et": "Kas sa soovid k\u00f5igist \u00fclal loetletud teenustest v\u00e4lja logida?",
 		"he": "\u05d4\u05d0\u05dd \u05d0\u05ea\u05d4 \u05e8\u05d5\u05e6\u05d4 \u05dc\u05d4\u05ea\u05e0\u05ea\u05e7 \u05de\u05db\u05dc \u05d4\u05e9\u05e8\u05d5\u05ea\u05d9\u05dd \u05d4\u05de\u05d5\u05d6\u05db\u05e8\u05d9\u05dd \u05dc\u05de\u05e2\u05dc\u05d4?",
 		"pt-br": "Voc\u00ea quer sair de todos os servi\u00e7os acima?",
@@ -440,7 +440,7 @@
 		"hr": "Da, iz svih servisa",
 		"lt": "Taip, vis\u0173 paslaug\u0173",
 		"ja": "\u306f\u3044\u3001\u5168\u3066\u306e\u30b5\u30fc\u30d3\u30b9\u304b\u3089\u30ed\u30b0\u30a2\u30a6\u30c8\u3057\u307e\u3059",
-		"zh-tw": "Yea\uff0c\u767b\u51fa\u6240\u6709\u670d\u52d9",
+		"zh-tw": "\u662f\uff0c\u767b\u51fa\u6240\u6709\u670d\u52d9",
 		"et": "Jah, k\u00f5igist teenustest",
 		"he": "\u05db\u05df, \u05db\u05dc \u05d4\u05e9\u05e8\u05d5\u05ea\u05d9\u05dd",
 		"pt-br": "Sim, todos os servi\u00e7os",
@@ -475,7 +475,7 @@
 		"hr": "Ne, samo iz %SP%",
 		"lt": "Ne, tik %SP%",
 		"ja": "\u3044\u3044\u3048\u3001%SP% \u306e\u307f\u30ed\u30b0\u30a2\u30a6\u30c8\u3057\u307e\u3059",
-		"zh-tw": "\u4e0d\uff0c\u53ea\u6709 %SP%",
+		"zh-tw": "\u5426\uff0c\u53ea\u767b\u51fa %SP%",
 		"et": "Ei, ainult %SP%",
 		"he": "\u05dc\u05d0, \u05e8\u05e7 %SP%",
 		"pt-br": "N\u00e3o, apenas de %SP%",
@@ -509,7 +509,7 @@
 		"hr": "Jedan ili vi\u0161e servisa na koje ste prijavljeni <i>ne podr\u017eava odjavljivanje<\/i>. Da biste bili sigurni da su sve va\u0161e sjednice zavr\u0161ene, preporu\u010damo da <i>zatvorite web preglednik<\/i>.",
 		"lt": "Viena ar daugiau paslaug\u0173, prie kuri\u0173 esate prisijung\u0119s <i>nepalaiko atsijungimo<\/i>. Siekiant u\u017etikrinti s\u0117kming\u0105 darbo pabaig\u0105, rekomenduojame <i>u\u017edaryti nar\u0161ykl\u0119<\/i>.",
 		"ja": "<i>\u30ed\u30b0\u30a2\u30a6\u30c8\u3092\u30b5\u30dd\u30fc\u30c8\u3057\u3066\u3044\u306a\u3044<\/i>\u4e00\u3064\u4ee5\u4e0a\u306e\u30b5\u30fc\u30d3\u30b9\u306b\u30ed\u30b0\u30a4\u30f3\u4e2d\u3067\u3059\u3002\u78ba\u5b9f\u306b\u30bb\u30c3\u30b7\u30e7\u30f3\u3092\u7d42\u4e86\u3055\u305b\u308b\u306b\u306f\u3001<i>WEB\u30d6\u30e9\u30a6\u30b6\u3092\u9589\u3058\u308b<\/i>\u4e8b\u3092\u63a8\u5968\u3057\u307e\u3059\u3002",
-		"zh-tw": "\u60a8\u767b\u5165\u7684\u670d\u52d9\u4e2d\u6709\u4e00\u500b\u6216\u4ee5\u4e0a <i>\u4e0d\u652f\u63f4\u767b\u51fa<\\\/i>\u3002\u8acb\u78ba\u8a8d\u60a8\u5df2\u95dc\u9589\u6240\u6709\u9023\u7dda\uff0c\u4e26<i>\u95dc\u9589\u700f\u89bd\u5668<\\\/i>\u3002",
+		"zh-tw": "\u60a8\u767b\u5165\u7684\u670d\u52d9\u4e2d\u6709\u4e00\u500b\u6216\u4ee5\u4e0a<i>\u4e0d\u652f\u63f4\u767b\u51fa<\/i>\u3002\u70ba\u78ba\u4fdd\u60a8\u7684\u6240\u6709\u9023\u7dda\u7686\u5df2\u95dc\u9589\uff0c\u5efa\u8b70\u60a8<i>\u95dc\u9589\u700f\u89bd\u5668<\/i>\u3002",
 		"et": "\u00dcks v\u00f5i mitu teenust, millesse oled sisselogitud <i>ei toeta v\u00e4lja logimise<\/i>. Selleks, et olla kindel k\u00f5igi sessioonide l\u00f5petamises soovitame <i>sulgeda k\u00f5ik brauseri aknad<\/i>.",
 		"he": "\u05d0\u05d7\u05d3 \u05d0\u05d5 \u05d9\u05d5\u05ea\u05e8 \u05de\u05df \u05d4\u05e9\u05e8\u05d5\u05ea\u05d9\u05dd \u05e9\u05d0\u05ea\u05d4 \u05de\u05d7\u05d5\u05d1\u05e8 \u05d0\u05dc\u05d9\u05d4\u05dd <i>\u05dc\u05d0 \u05ea\u05d5\u05de\u05db\u05d9\u05dd \u05d1\u05d4\u05ea\u05e0\u05ea\u05e7\u05d5\u05ea<\/i> .\u05db\u05d3\u05d9 \u05dc\u05d5\u05d5\u05d3\u05d0 \u05e9\u05d4\u05ea\u05e0\u05ea\u05e7\u05ea \u05de\u05db\u05dc \u05d4\u05e9\u05d9\u05e8\u05d5\u05ea\u05d9\u05dd \u05de\u05de\u05d5\u05dc\u05e5 <i>\u05e9\u05ea\u05e1\u05d2\u05d5\u05e8 \u05d0\u05ea \u05d4\u05d3\u05e4\u05d3\u05e4\u05df<\/i>",
 		"pt-br": "Um ou mais dos servi\u00e7os que voc\u00ea est\u00e1 conectado <i>n\u00e3o suportam logout.<\/i> Para garantir que todas as suas sess\u00f5es ser\u00e3o fechadas, incentivamos voc\u00ea a <i>fechar seu navegador<\/i>.",
@@ -545,7 +545,7 @@
 		"hr": "Ne",
 		"lt": "Ne",
 		"ja": "\u3044\u3044\u3048",
-		"zh-tw": "\u53d6\u6d88",
+		"zh-tw": "\u5426",
 		"et": "Ei",
 		"he": "\u05dc\u05d0",
 		"pt-br": "N\u00e3o",
@@ -609,7 +609,7 @@
 		"hu": "Legal\u00e1bb egy szolg\u00e1ltat\u00e1sb\u00f3l nem siker\u00fclt kil\u00e9pni. Ahhoz, hogy biztosan lez\u00e1rja a megkezdett munkamenetet, k\u00e9rj\u00fck, <i>z\u00e1rja be b\u00f6ng\u00e9sz\u0151j\u00e9t<\/i>.",
 		"ja": "\u4e00\u3064\u4ee5\u4e0a\u306e\u30b5\u30fc\u30d3\u30b9\u304b\u305f\u30ed\u30b0\u30a2\u30a6\u30c8\u51fa\u6765\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u78ba\u5b9f\u306b\u30bb\u30c3\u30b7\u30e7\u30f3\u3092\u7d42\u4e86\u3055\u305b\u308b\u306b\u306f\u3001<i>WEB\u30d6\u30e9\u30a6\u30b6\u3092\u9589\u3058\u308b<\/i>\u4e8b\u3092\u63a8\u5968\u3057\u307e\u3059\u3002",
 		"nl": "Het was niet mogelijk bij een of meerdere diensten uit te loggen. Om alle sessies te sluiten, raden wij u aan uw <i>webbrowser te af te sluiten<\/i>.",
-		"zh-tw": "\u7121\u6cd5\u6b63\u5e38\u767b\u51fa\uff0c\u8acb\u78ba\u8a8d\u60a8\u5df2\u95dc\u9589\u6240\u6709\u9023\u7dda\uff0c<i>\u540c\u6642\u95dc\u9589\u6240\u6709\u700f\u89bd\u5668<\\\/i>\u3002",
+		"zh-tw": "\u7121\u6cd5\u6b63\u5e38\u767b\u51fa\uff0c\u70ba\u78ba\u4fdd\u60a8\u7684\u6240\u6709\u9023\u7dda\u7686\u5df2\u95dc\u9589\uff0c\u5efa\u8b70\u60a8<i>\u95dc\u9589\u700f\u89bd\u5668<\/i>\u3002",
 		"et": "\u00dchest v\u00f5i mitmest teenusest v\u00e4lja logimine ei \u00f5nnestunud. Selleks, et olla kindel k\u00f5igi sessioonide l\u00f5petamises soovitame <i>sulgeda k\u00f5ik brauseri aknad<\/i>.",
 		"he": "\u05d0\u05d9 \u05d0\u05e4\u05e9\u05e8 \u05dc\u05d4\u05ea\u05e0\u05ea\u05e7 \u05de\u05d0\u05d7\u05d3 \u05d0\u05d5 \u05d9\u05d5\u05ea\u05e8 \u05de\u05d4\u05e9\u05e8\u05d5\u05ea\u05d9\u05dd. \u05db\u05d3\u05d9 \u05dc\u05d5\u05d5\u05d3\u05d0 \u05e9\u05d4\u05ea\u05e0\u05ea\u05e7\u05ea <i>\u05de\u05d5\u05de\u05dc\u05e5 \u05dc\u05e1\u05d2\u05d5\u05e8 \u05d0\u05ea <\/i>.\u05d4\u05d3\u05e4\u05d3\u05e4\u05df \u05e9\u05dc\u05da",
 		"pt-br": "Incapaz de sair de um ou mais servi\u00e7os. Para garantir que todas as suas sess\u00f5es ser\u00e3o fechadas, incentivamos voc\u00ea a <i>fechar seu navegador<\/i>.",
diff --git a/dictionaries/status.translation.json b/dictionaries/status.translation.json
index 71cb98f5dae14c70969b00309f2e5c4000b4a416..a78f1e1e460a55fcea2d12424f68d185ee999e92 100644
--- a/dictionaries/status.translation.json
+++ b/dictionaries/status.translation.json
@@ -21,7 +21,7 @@
 		"it": "Demo di SAML 2.0 SP",
 		"lt": "SAML 2.0 SP Demonstracin\u0117s versijos Pavyzdys",
 		"ja": "SAML 2.0 SP \u30c7\u30e2\u4f8b",
-		"zh-tw": "SAML 2.0 SP \u5c55\u793a\u7bc4\u4f8b",
+		"zh-tw": "SAML 2.0 SP Demo \u7bc4\u4f8b",
 		"et": "SAML 2.0 SP demon\u00e4ide",
 		"he": "\u05d4\u05d3\u05d2\u05de\u05ea \u05d3\u05d5\u05d2\u05de\u05d4 \u05dc\u05e1\"\u05e9 \u05de\u05e1\u05d5\u05d2 SAML 2.0",
 		"zh": "SAML 2.0 SP\u6f14\u793a\u6848\u4f8b",
@@ -55,7 +55,7 @@
 		"it": "Demo di Shibboleth",
 		"lt": "Shibboleth demonstracin\u0117 versija",
 		"ja": "Shibboleth \u30c7\u30e2",
-		"zh-tw": "\u8001\u8abf\u7684\u5c55\u793a",
+		"zh-tw": "Shibboleth Demo",
 		"et": "Shibbolethi demo",
 		"he": "\u05d4\u05d3\u05d2\u05de\u05d4 \u05dc- Shibboleth",
 		"zh": "Shibboleth\u6f14\u793a",
@@ -89,7 +89,7 @@
 		"it": "Demo di WS-Fed SP",
 		"lt": "WS-Fed SP Demonstracin\u0117s versijos Pavyzdys",
 		"ja": "WS-Fed SP \u30c7\u30e2\u4f8b",
-		"zh-tw": "WS-Fed SP \u5c55\u793a\u7bc4\u4f8b",
+		"zh-tw": "WS-Fed SP Demo \u7bc4\u4f8b",
 		"et": "WS-Fed SP demon\u00e4ide",
 		"he": "\u05d4\u05d3\u05d2\u05de\u05ea \u05d3\u05d5\u05d2\u05de\u05d4 \u05dc\u05e1\"\u05e9 \u05de\u05e1\u05d5\u05d2 WS-Fed",
 		"zh": "WS-Fed SP \u6f14\u793a\u6848\u4f8b",
@@ -123,7 +123,7 @@
 		"it": "Diagnostici di SimpleSAMLphp",
 		"lt": "SimpleSAMLphp Diagnostika",
 		"ja": "SimpleSAMLphp \u8a3a\u65ad",
-		"zh-tw": "SimpleSAMLphp \u8a3a\u65b7\u5de5\u5177",
+		"zh-tw": "SimpleSAMLphp \u8a3a\u65b7",
 		"et": "SimpleSAMLphp diagnostika",
 		"he": "\u05d0\u05d9\u05d1\u05d7\u05d5\u05df SimpleSAMLphp",
 		"zh": "SimpleSAMLphp \u8bca\u65ad",
@@ -190,7 +190,7 @@
 		"it": "Salve, questa \u00e8 la pagina di stato di SimpleSAMLphp. Qui \u00e8 possiible vedere se la sessione \u00e8 scaduta, quanto \u00e8 durata prima di scadere e tutti gli attributi ad essa collegati.",
 		"lt": "Sveikia, \u010dia SimpleSAMLphp b\u016bsenos tinklapis. \u010cia galite pamatyti, ar J\u016bs\u0173 sesija turi laiko apribojim\u0105, kiek trunka tas laiko apribojimas bei kitus J\u016bs\u0173 sesijai priskirtus atributus.",
 		"ja": "\u3053\u3093\u306b\u3061\u306f\u3001\u3053\u3053\u306f SimpleSAMLphp\u306e\u30b9\u30c6\u30fc\u30bf\u30b9\u30da\u30fc\u30b8\u3067\u3059\u3002\u3053\u3053\u3067\u306f\u30bb\u30c3\u30b7\u30e7\u30f3\u306e\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8\u6642\u9593\u3084\u30bb\u30c3\u30b7\u30e7\u30f3\u306b\u7d50\u3073\u3064\u3051\u3089\u308c\u305f\u5c5e\u6027\u60c5\u5831\u3092\u898b\u308b\u3053\u3068\u304c\u51fa\u6765\u307e\u3059\u3002",
-		"zh-tw": "\u563f\uff0c\u9019\u662f SimpleSAMLphp \u72c0\u614b\u9801\uff0c\u5728\u9019\u908a\u60a8\u53ef\u4ee5\u770b\u5230\u60a8\u7684\u9023\u7dda\u662f\u5426\u903e\u6642\uff0c\u4ee5\u53ca\u9084\u6709\u591a\u4e45\u624d\u903e\u6642\uff0c\u6240\u6709\u5c6c\u6027\u503c(attributes)\u90fd\u6703\u9644\u52a0\u5728\u4f60\u7684\u9023\u7dda\u88e1(session)\u3002",
+		"zh-tw": "\u563f\uff0c\u9019\u662f SimpleSAMLphp \u72c0\u614b\u9801\uff0c\u5728\u9019\u908a\u60a8\u53ef\u4ee5\u770b\u5230\u60a8\u7684\u9023\u7dda\u662f\u5426\u903e\u6642\uff0c\u4ee5\u53ca\u9084\u6709\u591a\u4e45\u624d\u903e\u6642\uff0c\u6240\u6709\u5c6c\u6027\u503c (attributes) \u90fd\u6703\u9644\u52a0\u5728\u4f60\u7684\u9023\u7dda (session) \u88e1\u3002",
 		"et": "Tere! See on SimpleSAMLphp olekuteave. Siit on v\u00f5imalik n\u00e4ha, kas su sessioon on aegunud, kui kaua see veel kestab ja k\u00f5iki teisi sessiooniga seotud atribuute.",
 		"he": "\u05e9\u05dc\u05d5\u05dd, \u05d6\u05d4\u05d5 \u05d3\u05e3 \u05d4\u05de\u05e6\u05d1 \u05e9\u05dc SimpleSAMLphp. \u05db\u05d0\u05df \u05d0\u05e4\u05e9\u05e8 \u05dc\u05e8\u05d0\u05d5\u05ea \u05d0\u05dd \u05d4\u05e9\u05d9\u05d7\u05d4 \u05d4\u05d5\u05e4\u05e1\u05e7\u05d4, \u05db\u05de\u05d4 \u05d6\u05de\u05df \u05d4\u05d9\u05d0 \u05ea\u05de\u05e9\u05d9\u05da \u05e2\u05d3 \u05dc\u05d4\u05e4\u05e1\u05e7\u05ea\u05d4 \u05d5\u05db\u05dc \u05d4\u05ea\u05db\u05d5\u05e0\u05d5\u05ea \u05d4\u05de\u05e6\u05d5\u05e8\u05e4\u05d5\u05ea \u05dc\u05e9\u05d9\u05d7\u05d4.",
 		"zh": "\u55e8\uff0c\u8fd9\u662fSimpleSAMLphp\u72b6\u6001\u9875\u3002\u8fd9\u91cc\u4f60\u53ef\u4ee5\u770b\u5230\uff0c\u5982\u679c\u60a8\u7684\u4f1a\u8bdd\u8d85\u65f6\uff0c\u5b83\u6301\u7eed\u591a\u4e45\uff0c\u76f4\u5230\u8d85\u65f6\u548c\u8fde\u63a5\u5230\u60a8\u7684\u4f1a\u8bdd\u7684\u6240\u6709\u5c5e\u6027\u3002",
@@ -224,7 +224,7 @@
 		"it": "La tua sessione \u00e8 valida per ulteriori %SECONDS% secondi.",
 		"lt": "J\u016bs\u0173 sesija galioja %SECONDS% sekund\u017ei\u0173, skai\u010diuojant nuo \u0161io momento.",
 		"ja": "\u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u4eca\u304b\u3089 %SECONDS% \u79d2\u9593\u6709\u52b9\u3067\u3059",
-		"zh-tw": "\u60a8\u7684 session \u5f9e\u73fe\u5728\u8d77\u9084\u6709 %SECONDS% \u6709\u6548\u3002",
+		"zh-tw": "\u60a8\u7684\u9023\u7dda (session) \u5f9e\u73fe\u5728\u8d77\u9084\u6709 %SECONDS% \u79d2\u6709\u6548\u3002",
 		"et": "Sinu sessioon kehtib veel %SECONDS% sekundit.",
 		"he": "\u05d4\u05e9\u05d9\u05d7\u05d4 \u05e9\u05dc\u05da \u05d1\u05e8\u05ea-\u05ea\u05d5\u05e7\u05e3 \u05dc\u05e2\u05d5\u05d3 %SECONDS% \u05e9\u05e0\u05d9\u05d5\u05ea \u05de\u05e2\u05db\u05e9\u05d9\u05d5.",
 		"zh": "\u4f60\u7684\u4f1a\u8bdd\u5728%SECONDS%\u79d2\u5185\u6709\u6548",
@@ -293,7 +293,7 @@
 		"it": "I tuoi attributi",
 		"lt": "J\u016bs\u0173 atributai",
 		"ja": "\u5c5e\u6027",
-		"zh-tw": "\u60a8\u7684\u5c6c\u6027\u503c",
+		"zh-tw": "\u60a8\u7684\u5c6c\u6027",
 		"et": "Sinu atribuudid",
 		"he": "\u05d4\u05ea\u05db\u05d5\u05e0\u05d5\u05ea \u05e9\u05dc\u05da",
 		"ru": "\u0412\u0430\u0448\u0438 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b",
diff --git a/docs/simplesamlphp-changelog.md b/docs/simplesamlphp-changelog.md
index c35694ca6f1c6294caa9e20c8ef7a2f052d1b36a..7408bc95e0c7ed46c26d8946122d23e76a336bc8 100644
--- a/docs/simplesamlphp-changelog.md
+++ b/docs/simplesamlphp-changelog.md
@@ -6,9 +6,76 @@ SimpleSAMLphp changelog
 This document lists the changes between versions of SimpleSAMLphp.
 See the upgrade notes for specific information about upgrading.
 
+## Version 1.14.14
+
+Released 2017-05-05
+
+  * Resolved a security issue with in the authcrypt module (Htpasswd authentication source) and in SimpleSAMLphp's session validation. See [SSPSA 201705-01](https://simplesamlphp.org/security/201705-01).
+  * Resolved a security issue with in the multiauth module. See [SSPSA 201704-02](https://simplesamlphp.org/security/201704-02).
+
+## Version 1.14.13
+
+Released 2017-04-27
+
+  * Resolved a security issue with unauthenticated encryption in the SimpleSAML\Utils\Crypto class. See [SSPSA 201704-01](https://simplesamlphp.org/security/201704-01).
+  * Added requirement for the Multibyte String PHP extension and the corresponding checks.
+  * Set a default name for SimpleSAMLphp sessions in the configuration template for the PHP session handler.
+  
+## Version 1.14.12
+
+Released 2017-03-30
+
+  * Resolved a security issue in the authcrypt module (Htpasswd authentication source) and in SimpleSAMLphp's session validation. See [SSPSA 201703-01](https://simplesamlphp.org/security/201703-01).
+  * Resolved a security issue with IV generation in the  `SimpleSAML\Utils\Crypto::_aesEncrypt()` method. See [SSPSA 201703-02](https://simplesamlphp.org/security/201703-02).
+  * Fixed an issue with the authfacebook module, broken after a change in Facebook's API.
+  * Fixed an issue in the discopower module that ignored the `hide.from.discovery` metadata option.
+  * Fixed an issue with trusted URLs validation that prevented a URL from being accepted if a standard port was explicitly included but not specified in the configuration.
+  * Fixed an issue that prevented detecting a Memcache server being down when fetching Memcache statistics.
+  * Fixed an issue with operating system detection that made SimpleSAMLphp identify OSX as Windows.
+
+## Version 1.14.11
+
+Released 2016-12-12
+
+  * Resolved a security issue involving signature validation of SAML 1.1 messages. See [SSPSA 201612-02](https://simplesamlphp.org/security/201612-02).
+  * Fixed an issue when the user identifier used to generate a persistent NameID was missing due to a misconfiguration, causing SimpleSAMLphp to generate the nameID based on the null data type.
+  * Fixed an issue when persistent NameIDs were generated out of attributes with empty strings or multiple values.
+  * Fixed issue #530. An empty SubjectConfirmation element was causing SimpleSAMLphp to crash. On the other hand, invalid SubjectConfirmation elements were ignored in PHP 7.0.
+
+## Version 1.14.10
+
+Released 2016-12-02
+
+  * Resolved a security issue involving signature validation. See [SSPSA 201612-01](https://simplesamlphp.org/security/201612-01).
+  * Fixed issue #517. A misconfigured session when acting as a service provider was leading to a PHP fatal error.
+  * Fixed issue #519. Prevent persistent NameIDs from being generated from empty strings.
+  * Fixed issue #520. It was impossible to verify Apache's custom MD5 passwords when using the Htpasswd authentication source.
+  * Fixed issue #523. Avoid problems caused by different line-ending strategies in the project files.
+  * Other minor fixes and enhancements.
+
+## Version 1.14.9
+
+Released 2016-11-10
+
+  * Fixed an issue that resulted in PHP 7 errors being masked.
+  * Fixed the smartattributes:SmartName authentication processing filter.
+  * Fixed issue #500. When parsing metadata, two 'attributes.required' options were generated.
+  * Fixed the list of requirements in composer, the documentation, and the configuration page.
+  * Fixed issue #479. There were several minor issues with XHTML compliance.
+  * Other minor fixes.
+
+## Version 1.14.8
+
+Released 2016-08-23
+
+  * Fixed an issue in AuthMemCookie causing it to crash when an attribute received contains XML as its value.
+  * Fixed an issue in AuthMemCookie that made it impossible to set its own cookie.
+  * Fixed an issue when acting as a proxy and receiving attributes that contain XML as their values.
+  * Fixed an issue that led to incorrect URL guessing when a script is invoked with a URI that doesn't include its name.
+
 ## Version 1.14.7
 
-Released TBD
+Released 2016-08-01
 
   * Fixed issue #424. Attributes containing XML as their values (like eduPersonTargetedID) were empty.
 
diff --git a/docs/simplesamlphp-install.md b/docs/simplesamlphp-install.md
index 7e02d930af2cc151ffb0b0cb264fe7d274dc4cb7..1261e5864a44204d52d548e29cccc5dfd56fceb3 100644
--- a/docs/simplesamlphp-install.md
+++ b/docs/simplesamlphp-install.md
@@ -43,6 +43,8 @@ Prerequisites
    * When using databases:
      * Always: `PDO`
      * Database driver: (`mysql`, `pgsql`, ...)
+ * Support for the following PHP packages:
+   * When saving session information to a Redis server: `predis`
 
 What actual packages are required for the various extensions varies between different platforms and distributions.
 
diff --git a/docs/simplesamlphp-maintenance.md b/docs/simplesamlphp-maintenance.md
index ed46ae74771942b8208e0aef565d11d9b54e60fe..463b33c0e94012f27c3fa556d4f3eca13f693917 100644
--- a/docs/simplesamlphp-maintenance.md
+++ b/docs/simplesamlphp-maintenance.md
@@ -29,6 +29,7 @@ The `store.type` configuration option in `config.php` allows you to select which
   * `phpsession` uses the built in session management in PHP. This is the default, and is simplest to use. It will not work in a load-balanced environment in most configurations.
   * `memcache` uses the memcache software to cache sessions in memory. Sessions can be distributed and replicated among several memcache servers, enabling both load-balancing and fail-over.
   * `sql` stores the session in an SQL database.
+  * `redis` stores the session in Redis.
 
     'store.type' => 'phpsession',
 
@@ -156,6 +157,12 @@ Username and password for accessing the database can be configured in the `store
 
 The required tables are created automatically. If you are storing data from multiple separate SimpleSAMLphp installations in the same database, you can use the `store.sql.prefix` option to prevent conflicts.
 
+### Configuring Redis storage
+
+To store sessions in Redis, set the `store.type` option to `redis`.
+
+By default SimpleSAMLphp will attempt to connect to Redis on the `localhost` at port `6379`. These can be configured via the `store.redis.host` and `store.redis.port` options, respectively. You may also set a key prefix with the `store.redis.prefix` option.
+
 ## Metadata storage
 
 Several metadata storage backends are available by default, including `flatfile`, `serialize`, `mdq` and
diff --git a/docs/simplesamlphp-modules.md b/docs/simplesamlphp-modules.md
index e52af5fe7b954127762990954a5040c681f937d5..ccbb851c81223793ed5dc742b2e26efe3014c460 100644
--- a/docs/simplesamlphp-modules.md
+++ b/docs/simplesamlphp-modules.md
@@ -17,18 +17,32 @@ configured, and how to write new modules.
 Overview
 --------
 
-There are currently three parts of SimpleSAMLphp which can be stored in modules - authentication sources, authentication processing filters and themes. There is also support for defining hooks - functions run at specific times. More than one thing can be stored in a single module. There is also support for storing supporting files, such as templates and dictionaries, in modules.
-
-The different functionalities which can be created as modules will be described in more detail in the following sections; what follows is a short introduction to what you can du with them:
-
- - Authentication sources implement different methods for authenticating users, for example simple login forms which authenticate against a database backend, or login methods which use client-side certificates. 
- - Authentication processing filters perform various tasks after the user is authenticated and has a set of attributes. They can add, remove and modify attributes, do additional authentication checks, ask questions of the user, +++. 
- - Themes allow you to package custom templates for multiple modules into a single module.
+There are currently three parts of SimpleSAMLphp which can be stored in 
+modules - authentication sources, authentication processing filters and 
+themes. There is also support for defining hooks - functions run at 
+specific times. More than one thing can be stored in a single module. 
+There is also support for storing supporting files, such as templates 
+and dictionaries, in modules.
+
+The different functionalities which can be created as modules will be 
+described in more detail in the following sections; what follows is a 
+short introduction to what you can do with them:
+
+ - Authentication sources implement different methods for 
+   authenticating users, for example simple login forms which 
+   authenticate against a database backend, or login methods which use 
+   client-side certificates. 
+ - Authentication processing filters perform various tasks after the 
+   user is authenticated and has a set of attributes. They can add, 
+   remove and modify attributes, do additional authentication checks, 
+   ask questions of the user, +++. 
+ - Themes allow you to package custom templates for multiple modules 
+   into a single module.
 
 
 ## Module layout
 
-Each SimpleSAMLphp module is stored in a directory under the the
+Each SimpleSAMLphp module is stored in a directory under the
 `modules`-directory. The module directory contains the following
 directories and files:
 
@@ -46,12 +60,12 @@ dictionaries
 :   This directory contains dictionaries which belong to this
     module. To use a dictionary stored in a module, the extended tag
     names can be used:
-    `{<module name>:<dictionary             name>:<tag name>}` For
+    `{<module name>:<dictionary name>:<tag name>}` For
     example, `{example:login:hello}` will look up `hello` in
     `modules/example/dictionaries/login.php`.
 
 :   It is also possible to specify
-    `<module             name>:<dictionary name>` as the default
+    `<module name>:<dictionary name>` as the default
     dictionary when instantiating the `SimpleSAML_XHTML_Template`
     class.
 
@@ -74,7 +88,7 @@ lib
 
 templates
 :   These are module-specific templates. To use one of these
-    templates, specify `<module name>:<template             file>.php`
+    templates, specify `<module name>:<template file>.php`
     as the template file in the constructor of
     `SimpleSAML_XHTML_Template`. For example, `example:login-form.php`
     is translated to the file
@@ -104,7 +118,7 @@ themes
 www
 :   All files stored in this directory will be available by
     accessing the URL
-    `https://.../simplesamlphp/module.php/<module             name>/<file name>`.
+    `https://.../simplesamlphp/module.php/<module name>/<file name>`.
     For example, if a script named `login.php` is stored in
     `modules/example/www/`, it can be accessed by the URL
     `https://.../simplesamlphp/module.php/example/login.php`.
@@ -118,17 +132,30 @@ www
 
 ## Authentication sources
 
-An authentication source is used to authenticate a user and receive a set of attributes belonging to this user. In a single-signon setup, the authentication source will only be called once, and the attributes belonging to the user will be cached until the user logs out.
+An authentication source is used to authenticate a user and receive a 
+set of attributes belonging to this user. In a single-signon setup, the 
+authentication source will only be called once, and the attributes 
+belonging to the user will be cached until the user logs out.
 
-Authentication sources are defined in `config/authsources.php`. This file contains an array of `name => configuration` pairs. The name is used to refer to the authentication source in metadata. When configuring an IdP to authenticate against an authentication source, the `auth` option should be set to this name. The configuration for an authentication source is an array. The first element in the array identifies the class which implements the authentication source. The remaining elements in the array are configuration entries for the authentication source.
+Authentication sources are defined in `config/authsources.php`. This 
+file contains an array of `name => configuration` pairs. The name is 
+used to refer to the authentication source in metadata. When 
+configuring an IdP to authenticate against an authentication source, 
+\the `auth` option should be set to this name. The configuration for an 
+authentication source is an array. The first element in the array 
+identifies the class which implements the authentication source. The 
+remaining elements in the array are configuration entries for the 
+authentication source.
 
-A typical configuration entry for an authentication source looks like this:
+A typical configuration entry for an authentication source looks like 
+this:
 
     'example-static' => array(
       /* This maps to modules/exampleauth/lib/Auth/Source/Static.php */
       'exampleauth:Static',
     
-      /* The following is configuration which is passed on to the exampleauth:Static authentication source. */
+      /* The following is configuration which is passed on to
+       * the exampleauth:Static authentication source. */
       'uid' => 'testuser',
       'eduPersonAffiliation' => array('member', 'employee'),
       'cn' => array('Test User'),
@@ -162,17 +189,37 @@ Authentication processing filters
 
 ## Themes
 
-This feature allows you to collect all your custom templates in one place. The directory structure is like this: `modules/<thememodule>/themes/<theme>/<module>/<template>` `thememodule` is the module where you store your theme, while `theme` is the name of the theme. A theme is activated by setting the `theme.use` configuration option to `<thememodule>:<theme>`. `module` is the module the template belongs to, and `template` is the template in that module.
-
-For example, `modules/example/themes/test/core/loginuserpass.php` replaces `modules/core/templates/default/loginuserpass.php`. `modules/example/themes/test/default/frontpage.php` replaces `templates/default/frontpage.php`. This theme can be activated by setting `theme.use` to `example:test`.
+This feature allows you to collect all your custom templates in one 
+place. The directory structure is like this: 
+`modules/<thememodule>/themes/<theme>/<module>/<template>` 
+`thememodule` is the module where you store your theme, while `theme` 
+is the name of the theme. A theme is activated by setting the 
+`theme.use` configuration option to `<thememodule>:<theme>`. `module` 
+is the module the template belongs to, and `template` is the template 
+in that module.
+
+For example, `modules/example/themes/test/core/loginuserpass.php` 
+replaces `modules/core/templates/default/loginuserpass.php`. 
+`modules/example/themes/test/default/frontpage.php` replaces 
+`templates/default/frontpage.php`. This theme can be activated by 
+setting `theme.use` to `example:test`.
 
 ## Hook interface
 
-The hook interface allows you to call a hook function in all enabled modules which define that hook. Hook functions are stored in a directory called 'hooks' in each module directory. Each hook is stored in a file named `hook_<hook name>.php`, and each file defines a function named `<module name>_hook_<hook name>`.
+The hook interface allows you to call a hook function in all enabled 
+modules which define that hook. Hook functions are stored in a 
+directory called 'hooks' in each module directory. Each hook is 
+stored in a file named `hook_<hook name>.php`, and each file defines a 
+function named `<module name>_hook_<hook name>`.
 
-Each hook function accepts a single argument. This argument will be passed by reference, which allows each hook to update that argument.
+Each hook function accepts a single argument. This argument will be 
+passed by reference, which allows each hook to update that argument.
 
-There is currently a single user of the hook interface - the front page. The front page defines a hook named `frontpage`, which allows modules to add things to the different sections on the front page. For an example of this, see the `modules/modinfo/hooks/hook_frontpage.php` file in the
+There is currently a single user of the hook interface - the front 
+page. The front page defines a hook named `frontpage`, which allows 
+modules to add things to the different sections on the front page. For 
+an example of this, see the `modules/modinfo/hooks/hook_frontpage.php` 
+file in the
 [modinfo module](https://github.com/simplesamlphp/simplesamlphp-module-modinfo).
 
 
diff --git a/docs/simplesamlphp-sp-migration.md b/docs/simplesamlphp-sp-migration.md
index 267f0e56ca4a97e24abac85282272f68793da248..ff6f3e1ee100970412fd24165f6144d614ea561e 100644
--- a/docs/simplesamlphp-sp-migration.md
+++ b/docs/simplesamlphp-sp-migration.md
@@ -22,7 +22,7 @@ Create a new authentication source
 
 In this step we are going to create an authentication source which uses the `saml` module for authentication.
 To do this, we open `config/authsources.php`. Create the file if it does not exist.
-If you create the file, it should looke like this:
+If you create the file, it should look like this:
 
     <?php
     $config = array(
diff --git a/docs/simplesamlphp-theming.md b/docs/simplesamlphp-theming.md
index 6f1d65de65310efff94d7159575a1ce0f2508403..0c66fbde91ea3f948e96767901fa788113294a3d 100644
--- a/docs/simplesamlphp-theming.md
+++ b/docs/simplesamlphp-theming.md
@@ -1,8 +1,8 @@
 Theming the user interface in SimpleSAMLphp
 ===========================================
 
-<!-- 
-	This file is written in Markdown syntax. 
+<!--
+	This file is written in Markdown syntax.
 	For more information about how to use the Markdown syntax, read here:
 	http://daringfireball.net/projects/markdown/syntax
 -->
@@ -86,7 +86,7 @@ For example, to override the `preprodwarning` template, (the file is located in
 
 
 Say in a module `foomodule`, some code requests to present the `bar.php` template, SimpleSAMLphp will:
-	
+
  1. first look in your theme for a replacement: `modules/mymodule/themes/fancytheme/foomodule/bar.php`.
  2. If not found, it will use the base template of that module: `modules/foomodule/templates/bar.php`
 
@@ -96,3 +96,21 @@ Adding resource files
 
 You can put resource files within the www folder of your module, to make your module completely independent with included css, icons etc.
 
+```
+modules
+└───mymodule
+    └───themes
+    └───www
+        └───logo.png
+        └───style.css
+```
+
+Reference these resources in your custom PHP templates under `themes/fancytheme` by using a generator for the URL:
+```php
+<?php echo SimpleSAML_Module::getModuleURL('mymodule/logo.png'); ?>
+```
+
+Example for a custom CSS stylesheet file:
+```html
+<link rel="stylesheet" type="text/css" href="<?php echo SimpleSAML_Module::getModuleURL('mymodule/style.css'); ?>" />
+```
diff --git a/docs/simplesamlphp-upgrade-notes-1.14.md b/docs/simplesamlphp-upgrade-notes-1.14.md
index dc7eda6b4e4120b5d934e4f26e8096a1c17e55cd..9931225c49e46085c52f80ee5a5037862d537059 100644
--- a/docs/simplesamlphp-upgrade-notes-1.14.md
+++ b/docs/simplesamlphp-upgrade-notes-1.14.md
@@ -5,6 +5,8 @@ The `mcrypt` extension is no longer required by SimpleSAMLphp, so if no signatur
 can be skipped. It is still a requirement for `xmlseclibs` though, so for those verifying or creating signed
 documents, or using encryption, it is still needed.
 
+The `mbstring` extension is now required starting on SimpleSAMLphp 1.14.12.
+
 PHP session cookies are now set to HTTP-only by default. This relates to the `session.phpsession.httponly`
 configuration option.
 
diff --git a/lib/SimpleSAML/Bindings/Shib13/Artifact.php b/lib/SimpleSAML/Bindings/Shib13/Artifact.php
index 09c6365328ec5e06aedf0e7d1aacd136660f9906..df23ffd9caa7dc218e07701ba0eae9fd70fa2836 100644
--- a/lib/SimpleSAML/Bindings/Shib13/Artifact.php
+++ b/lib/SimpleSAML/Bindings/Shib13/Artifact.php
@@ -5,174 +5,186 @@
  *
  * @package SimpleSAMLphp
  */
-class SimpleSAML_Bindings_Shib13_Artifact {
-
-	/**
-	 * Parse the query string, and extract the SAMLart parameters.
-	 *
-	 * This function is required because each query contains multiple
-	 * artifact with the same parameter name.
-	 *
-	 * @return array  The artifacts.
-	 */
-	private static function getArtifacts() {
-		assert('array_key_exists("QUERY_STRING", $_SERVER)');
-
-		// We need to process the query string manually, to capture all SAMLart parameters
-
-		$artifacts = array();
-
-		$elements = explode('&', $_SERVER['QUERY_STRING']);
-		foreach ($elements as $element) {
-			list($name, $value) = explode('=', $element, 2);
-			$name = urldecode($name);
-			$value = urldecode($value);
-
-			if ($name === 'SAMLart') {
-				$artifacts[] = $value;
-			}
-		}
-
-		return $artifacts;
-	}
-
-
-	/**
-	 * Build the request we will send to the IdP.
-	 *
-	 * @param array $artifacts  The artifacts we will request.
-	 * @return string  The request, as an XML string.
-	 */
-	private static function buildRequest(array $artifacts) {
-
-		$msg = '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">' .
-			'<SOAP-ENV:Body>' .
-			'<samlp:Request xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol"' .
-			' RequestID="' . SimpleSAML\Utils\Random::generateID() . '"' .
-			' MajorVersion="1" MinorVersion="1"' .
-			' IssueInstant="' . SimpleSAML\Utils\Time::generateTimestamp() . '"' .
-			'>';
-
-		foreach ($artifacts as $a) {
-			$msg .= '<samlp:AssertionArtifact>' . htmlspecialchars($a) . '</samlp:AssertionArtifact>';
-		}
-
-		$msg .= '</samlp:Request>' .
-			'</SOAP-ENV:Body>' .
-			'</SOAP-ENV:Envelope>';
-
-		return $msg;
-	}
-
-
-	/**
-	 * Extract the response element from the SOAP response.
-	 *
-	 * @param string $soapResponse  The SOAP response.
-	 * @return string  The <saml1p:Response> element, as a string.
-	 */
-	private static function extractResponse($soapResponse) {
-		assert('is_string($soapResponse)');
-
-		try {
-			$doc = \SAML2\DOMDocumentFactory::fromString($soapResponse);
-		} catch(\Exception $e) {
-			throw new SimpleSAML_Error_Exception('Error parsing SAML 1 artifact response.');
-		}
-
-		$soapEnvelope = $doc->firstChild;
-		if (!SimpleSAML\Utils\XML::isDOMElementOfType($soapEnvelope, 'Envelope', 'http://schemas.xmlsoap.org/soap/envelope/')) {
-			throw new SimpleSAML_Error_Exception('Expected artifact response to contain a <soap:Envelope> element.');
-		}
-
-		$soapBody = SimpleSAML\Utils\XML::getDOMChildren($soapEnvelope, 'Body', 'http://schemas.xmlsoap.org/soap/envelope/');
-		if (count($soapBody) === 0) {
-			throw new SimpleSAML_Error_Exception('Couldn\'t find <soap:Body> in <soap:Envelope>.');
-		}
-		$soapBody = $soapBody[0];
-
-
-		$responseElement = SimpleSAML\Utils\XML::getDOMChildren($soapBody, 'Response', 'urn:oasis:names:tc:SAML:1.0:protocol');
-		if (count($responseElement) === 0) {
-			throw new SimpleSAML_Error_Exception('Couldn\'t find <saml1p:Response> in <soap:Body>.');
-		}
-		$responseElement = $responseElement[0];
-
-		/*
-		 * Save the <saml1p:Response> element. Note that we need to import it
-		 * into a new document, in order to preserve namespace declarations.
-		 */
-		$newDoc = \SAML2\DOMDocumentFactory::create();
-		$newDoc->appendChild($newDoc->importNode($responseElement, TRUE));
-		$responseXML = $newDoc->saveXML();
-
-		return $responseXML;
-	}
-
-
-	/**
-	 * This function receives a SAML 1.1 artifact.
-	 *
-	 * @param SimpleSAML_Configuration $spMetadata  The metadata of the SP.
-	 * @param SimpleSAML_Configuration $idpMetadata  The metadata of the IdP.
-	 * @return string  The <saml1p:Response> element, as an XML string.
-	 */
-	public static function receive(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata) {
-
-		$artifacts = self::getArtifacts();
-		$request = self::buildRequest($artifacts);
-
-		\SimpleSAML\Utils\XML::debugSAMLMessage($request, 'out');
-
-		$url = $idpMetadata->getDefaultEndpoint('ArtifactResolutionService', array('urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding'));
-		$url = $url['Location'];
-
-		$peerPublicKeys = $idpMetadata->getPublicKeys('signing', TRUE);
-		$certData = '';
-		foreach ($peerPublicKeys as $key) {
-			if ($key['type'] !== 'X509Certificate') {
-				continue;
-			}
-			$certData .= "-----BEGIN CERTIFICATE-----\n" .
-				chunk_split($key['X509Certificate'], 64) .
-				"-----END CERTIFICATE-----\n";
-		}
-
-		$file = SimpleSAML\Utils\System::getTempDir() . DIRECTORY_SEPARATOR . sha1($certData) . '.crt';
-		if (!file_exists($file)) {
-            SimpleSAML\Utils\System::writeFile($file, $certData);
-		}
-
-		$spKeyCertFile = \SimpleSAML\Utils\Config::getCertPath($spMetadata->getString('privatekey'));
-
-		$opts = array(
-			'ssl' => array(
-				'verify_peer' => TRUE,
-				'cafile' => $file,
-				'local_cert' => $spKeyCertFile,
-				'capture_peer_cert' => TRUE,
-				'capture_peer_chain' => TRUE,
-			),
-			'http' => array(
-				'method' => 'POST',
-				'content' => $request,
-				'header' => 'SOAPAction: http://www.oasis-open.org/committees/security' . "\r\n" .
-					'Content-Type: text/xml',
-			),
-		);
-
-		// Fetch the artifact
-		$response = \SimpleSAML\Utils\HTTP::fetch($url, $opts);
-		if ($response === FALSE) {
-			throw new SimpleSAML_Error_Exception('Failed to retrieve assertion from IdP.');
-		}
-
-		\SimpleSAML\Utils\XML::debugSAMLMessage($response, 'in');
-
-		// Find the response in the SOAP message
-		$response = self::extractResponse($response);
-
-		return $response;
-	}
-
-}
\ No newline at end of file
+
+namespace SimpleSAML\Bindings\Shib13;
+
+use SAML2\DOMDocumentFactory;
+use SimpleSAML\Utils\Config;
+use SimpleSAML\Utils\HTTP;
+use SimpleSAML\Utils\Random;
+use SimpleSAML\Utils\System;
+use SimpleSAML\Utils\Time;
+use SimpleSAML\Utils\XML;
+
+class Artifact
+{
+
+    /**
+     * Parse the query string, and extract the SAMLart parameters.
+     *
+     * This function is required because each query contains multiple
+     * artifact with the same parameter name.
+     *
+     * @return array  The artifacts.
+     */
+    private static function getArtifacts()
+    {
+        assert('array_key_exists("QUERY_STRING", $_SERVER)');
+
+        // We need to process the query string manually, to capture all SAMLart parameters
+
+        $artifacts = array();
+
+        $elements = explode('&', $_SERVER['QUERY_STRING']);
+        foreach ($elements as $element) {
+            list($name, $value) = explode('=', $element, 2);
+            $name = urldecode($name);
+            $value = urldecode($value);
+
+            if ($name === 'SAMLart') {
+                $artifacts[] = $value;
+            }
+        }
+
+        return $artifacts;
+    }
+
+
+    /**
+     * Build the request we will send to the IdP.
+     *
+     * @param array $artifacts  The artifacts we will request.
+     * @return string  The request, as an XML string.
+     */
+    private static function buildRequest(array $artifacts)
+    {
+        $msg = '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">' .
+            '<SOAP-ENV:Body>' .
+            '<samlp:Request xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol"' .
+            ' RequestID="' . Random::generateID() . '"' .
+            ' MajorVersion="1" MinorVersion="1"' .
+            ' IssueInstant="' . Time::generateTimestamp() . '"' .
+            '>';
+
+        foreach ($artifacts as $a) {
+            $msg .= '<samlp:AssertionArtifact>' . htmlspecialchars($a) . '</samlp:AssertionArtifact>';
+        }
+
+        $msg .= '</samlp:Request>' .
+            '</SOAP-ENV:Body>' .
+            '</SOAP-ENV:Envelope>';
+
+        return $msg;
+    }
+
+
+    /**
+     * Extract the response element from the SOAP response.
+     *
+     * @param string $soapResponse The SOAP response.
+     * @return string The <saml1p:Response> element, as a string.
+     * @throws \SimpleSAML_Error_Exception
+     */
+    private static function extractResponse($soapResponse)
+    {
+        assert('is_string($soapResponse)');
+
+        try {
+            $doc = DOMDocumentFactory::fromString($soapResponse);
+        } catch (\Exception $e) {
+            throw new \SimpleSAML_Error_Exception('Error parsing SAML 1 artifact response.');
+        }
+
+        $soapEnvelope = $doc->firstChild;
+        if (!XML::isDOMNodeOfType($soapEnvelope, 'Envelope', 'http://schemas.xmlsoap.org/soap/envelope/')) {
+            throw new \SimpleSAML_Error_Exception('Expected artifact response to contain a <soap:Envelope> element.');
+        }
+
+        $soapBody = XML::getDOMChildren($soapEnvelope, 'Body', 'http://schemas.xmlsoap.org/soap/envelope/');
+        if (count($soapBody) === 0) {
+            throw new \SimpleSAML_Error_Exception('Couldn\'t find <soap:Body> in <soap:Envelope>.');
+        }
+        $soapBody = $soapBody[0];
+
+
+        $responseElement = XML::getDOMChildren($soapBody, 'Response', 'urn:oasis:names:tc:SAML:1.0:protocol');
+        if (count($responseElement) === 0) {
+            throw new \SimpleSAML_Error_Exception('Couldn\'t find <saml1p:Response> in <soap:Body>.');
+        }
+        $responseElement = $responseElement[0];
+
+        /*
+         * Save the <saml1p:Response> element. Note that we need to import it
+         * into a new document, in order to preserve namespace declarations.
+         */
+        $newDoc = DOMDocumentFactory::create();
+        $newDoc->appendChild($newDoc->importNode($responseElement, true));
+        $responseXML = $newDoc->saveXML();
+
+        return $responseXML;
+    }
+
+
+    /**
+     * This function receives a SAML 1.1 artifact.
+     *
+     * @param \SimpleSAML_Configuration $spMetadata The metadata of the SP.
+     * @param \SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
+     * @return string The <saml1p:Response> element, as an XML string.
+     * @throws \SimpleSAML_Error_Exception
+     */
+    public static function receive(\SimpleSAML_Configuration $spMetadata, \SimpleSAML_Configuration $idpMetadata)
+    {
+        $artifacts = self::getArtifacts();
+        $request = self::buildRequest($artifacts);
+
+        XML::debugSAMLMessage($request, 'out');
+
+        $url = $idpMetadata->getDefaultEndpoint('ArtifactResolutionService', array('urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding'));
+        $url = $url['Location'];
+
+        $peerPublicKeys = $idpMetadata->getPublicKeys('signing', true);
+        $certData = '';
+        foreach ($peerPublicKeys as $key) {
+            if ($key['type'] !== 'X509Certificate') {
+                continue;
+            }
+            $certData .= "-----BEGIN CERTIFICATE-----\n" .
+                chunk_split($key['X509Certificate'], 64) .
+                "-----END CERTIFICATE-----\n";
+        }
+
+        $file = System::getTempDir() . DIRECTORY_SEPARATOR . sha1($certData) . '.crt';
+        if (!file_exists($file)) {
+            System::writeFile($file, $certData);
+        }
+
+        $spKeyCertFile = Config::getCertPath($spMetadata->getString('privatekey'));
+
+        $opts = array(
+            'ssl' => array(
+                'verify_peer' => true,
+                'cafile' => $file,
+                'local_cert' => $spKeyCertFile,
+                'capture_peer_cert' => true,
+                'capture_peer_chain' => true,
+            ),
+            'http' => array(
+                'method' => 'POST',
+                'content' => $request,
+                'header' => 'SOAPAction: http://www.oasis-open.org/committees/security' . "\r\n" .
+                    'Content-Type: text/xml',
+            ),
+        );
+
+        // Fetch the artifact
+        $response = HTTP::fetch($url, $opts);
+        /** @var string $response */
+        XML::debugSAMLMessage($response, 'in');
+
+        // Find the response in the SOAP message
+        $response = self::extractResponse($response);
+
+        return $response;
+    }
+}
diff --git a/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php b/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php
index 30ddf25ae196ec9c8b5407ef83ae112aa5306829..58824747e30440a9c1303670b9c99e4e10c57e84 100644
--- a/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php
+++ b/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php
@@ -7,29 +7,39 @@
  * @author Andreas Ã…kre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
  * @package SimpleSAMLphp
  */
-class SimpleSAML_Bindings_Shib13_HTTPPost
+
+namespace SimpleSAML\Bindings\Shib13;
+
+use SAML2\DOMDocumentFactory;
+use SimpleSAML\Utils\Crypto;
+use SimpleSAML\Utils\HTTP;
+use SimpleSAML\Utils\XML;
+use SimpleSAML\XML\Shib13\AuthnResponse;
+use SimpleSAML\XML\Signer;
+
+class HTTPPost
 {
 
     /**
-     * @var SimpleSAML_Configuration
+     * @var \SimpleSAML_Configuration
      */
     private $configuration = null;
 
     /**
-     * @var SimpleSAML_Metadata_MetaDataStorageHandler
+     * @var \SimpleSAML_Metadata_MetaDataStorageHandler
      */
     private $metadata = null;
 
 
     /**
-     * Constructor for the SimpleSAML_Bindings_Shib13_HTTPPost class.
+     * Constructor for the \SimpleSAML\Bindings\Shib13\HTTPPost class.
      *
-     * @param SimpleSAML_Configuration                   $configuration The configuration to use.
-     * @param SimpleSAML_Metadata_MetaDataStorageHandler $metadatastore A store where to find metadata.
+     * @param \SimpleSAML_Configuration                   $configuration The configuration to use.
+     * @param \SimpleSAML_Metadata_MetaDataStorageHandler $metadatastore A store where to find metadata.
      */
     public function __construct(
-        SimpleSAML_Configuration $configuration,
-        SimpleSAML_Metadata_MetaDataStorageHandler $metadatastore
+        \SimpleSAML_Configuration $configuration,
+        \SimpleSAML_Metadata_MetaDataStorageHandler $metadatastore
     ) {
         $this->configuration = $configuration;
         $this->metadata = $metadatastore;
@@ -39,26 +49,25 @@ class SimpleSAML_Bindings_Shib13_HTTPPost
     /**
      * Send an authenticationResponse using HTTP-POST.
      *
-     * @param string                   $response The response which should be sent.
-     * @param SimpleSAML_Configuration $idpmd The metadata of the IdP which is sending the response.
-     * @param SimpleSAML_Configuration $spmd The metadata of the SP which is receiving the response.
-     * @param string|null              $relayState The relaystate for the SP.
-     * @param string                   $shire The shire which should receive the response.
+     * @param string                    $response The response which should be sent.
+     * @param \SimpleSAML_Configuration $idpmd The metadata of the IdP which is sending the response.
+     * @param \SimpleSAML_Configuration $spmd The metadata of the SP which is receiving the response.
+     * @param string|null               $relayState The relaystate for the SP.
+     * @param string                    $shire The shire which should receive the response.
      */
     public function sendResponse(
         $response,
-        SimpleSAML_Configuration $idpmd,
-        SimpleSAML_Configuration $spmd,
+        \SimpleSAML_Configuration $idpmd,
+        \SimpleSAML_Configuration $spmd,
         $relayState,
         $shire
     ) {
+        XML::checkSAMLMessage($response, 'saml11');
 
-        \SimpleSAML\Utils\XML::checkSAMLMessage($response, 'saml11');
+        $privatekey = Crypto::loadPrivateKey($idpmd, true);
+        $publickey = Crypto::loadPublicKey($idpmd, true);
 
-        $privatekey = SimpleSAML\Utils\Crypto::loadPrivateKey($idpmd, true);
-        $publickey = SimpleSAML\Utils\Crypto::loadPublicKey($idpmd, true);
-
-        $responsedom = \SAML2\DOMDocumentFactory::fromString(str_replace("\r", "", $response));
+        $responsedom = DOMDocumentFactory::fromString(str_replace("\r", "", $response));
 
         $responseroot = $responsedom->getElementsByTagName('Response')->item(0);
         $firstassertionroot = $responsedom->getElementsByTagName('Assertion')->item(0);
@@ -80,7 +89,7 @@ class SimpleSAML_Bindings_Shib13_HTTPPost
             $signResponse = true;
         }
 
-        $signer = new SimpleSAML_XML_Signer(array(
+        $signer = new Signer(array(
             'privatekey_array' => $privatekey,
             'publickey_array'  => $publickey,
             'id'               => ($signResponse ? 'ResponseID' : 'AssertionID'),
@@ -93,7 +102,7 @@ class SimpleSAML_Bindings_Shib13_HTTPPost
         if ($signResponse) {
             // sign the response - this must be done after encrypting the assertion
             // we insert the signature before the saml2p:Status element
-            $statusElements = SimpleSAML\Utils\XML::getDOMChildren($responseroot, 'Status', '@saml1p');
+            $statusElements = XML::getDOMChildren($responseroot, 'Status', '@saml1p');
             assert('count($statusElements) === 1');
             $signer->sign($responseroot, $responseroot, $statusElements[0]);
         } else {
@@ -103,9 +112,9 @@ class SimpleSAML_Bindings_Shib13_HTTPPost
 
         $response = $responsedom->saveXML();
 
-        \SimpleSAML\Utils\XML::debugSAMLMessage($response, 'out');
+        XML::debugSAMLMessage($response, 'out');
 
-        \SimpleSAML\Utils\HTTP::submitPOSTData($shire, array(
+        HTTP::submitPOSTData($shire, array(
             'TARGET'       => $relayState,
             'SAMLResponse' => base64_encode($response),
         ));
@@ -116,26 +125,24 @@ class SimpleSAML_Bindings_Shib13_HTTPPost
      * Decode a received response.
      *
      * @param array $post POST data received.
-     *
-     * @return SimpleSAML_XML_Shib13_AuthnResponse The response decoded into an object.
-     *
-     * @throws Exception If there is no SAMLResponse parameter.
+     * @return \SimpleSAML\XML\Shib13\AuthnResponse The response decoded into an object.
+     * @throws \Exception If there is no SAMLResponse parameter.
      */
     public function decodeResponse($post)
     {
         assert('is_array($post)');
 
         if (!array_key_exists('SAMLResponse', $post)) {
-            throw new Exception('Missing required SAMLResponse parameter.');
+            throw new \Exception('Missing required SAMLResponse parameter.');
         }
         $rawResponse = $post['SAMLResponse'];
         $samlResponseXML = base64_decode($rawResponse);
 
-        \SimpleSAML\Utils\XML::debugSAMLMessage($samlResponseXML, 'in');
+        XML::debugSAMLMessage($samlResponseXML, 'in');
 
-        \SimpleSAML\Utils\XML::checkSAMLMessage($samlResponseXML, 'saml11');
+        XML::checkSAMLMessage($samlResponseXML, 'saml11');
 
-        $samlResponse = new SimpleSAML_XML_Shib13_AuthnResponse();
+        $samlResponse = new AuthnResponse();
         $samlResponse->setXML($samlResponseXML);
 
         if (array_key_exists('TARGET', $post)) {
diff --git a/lib/SimpleSAML/Locale/Language.php b/lib/SimpleSAML/Locale/Language.php
index 939bcba355a429c2fa6fb6850fa1a182dcbec26a..5972a74bc6948b50f6164d04d7147f421a4c0896 100644
--- a/lib/SimpleSAML/Locale/Language.php
+++ b/lib/SimpleSAML/Locale/Language.php
@@ -153,9 +153,9 @@ class Language
 
 
     /**
-     * Wash configured (available) languages against installed languages
+     * Filter configured (available) languages against installed languages.
      *
-     * @return array The set of langauges both in 'language.available' and $this->language_names
+     * @return array The set of languages both in 'language.available' and $this->language_names.
      */
     private function getInstalledLanguages()
     {
@@ -172,10 +172,12 @@ class Language
     }
 
 
-    /*
-     * Rename to non-idiosyncratic language code
+    /**
+     * Rename to non-idiosyncratic language code.
+     *
+     * @param string $language Language code for the language to rename, if necessary.
      *
-     * @param string $language Language code for the language to rename, if neccesary.
+     * @return string The language code.
      */
     public function getPosixLanguage($language)
     {
@@ -327,14 +329,14 @@ class Language
 
 
     /**
-     * Return an alias for a langcode, if any
+     * Return an alias for a language code, if any.
      *
-     * @return string The alias, or null if alias not found
+     * @return string The alias, or null if the alias was not found.
      */
     public function getLanguageCodeAlias($langcode)
     {
-        if (isset($this->defaultLanguageMap[$langcode])) {
-            return $this->defaultLanguageMap[$langcode];
+        if (isset(self::$defaultLanguageMap[$langcode])) {
+            return self::$defaultLanguageMap[$langcode];
         }
         // No alias found, which is fine
         return null;
diff --git a/lib/SimpleSAML/Memcache.php b/lib/SimpleSAML/Memcache.php
index 63e4c9cc60fc1c9f07de0bf7ee66a42ec32e94a5..a901465d293dc6e11b4e77137062082442622361 100644
--- a/lib/SimpleSAML/Memcache.php
+++ b/lib/SimpleSAML/Memcache.php
@@ -458,8 +458,10 @@ class SimpleSAML_Memcache
 
         foreach (self::getMemcacheServers() as $sg) {
             $stats = $sg->getExtendedStats();
-            if ($stats === false) {
-                throw new Exception('Failed to get memcache server status.');
+            foreach ($stats as $server => $data) {
+                if ($data === false) {
+                    throw new Exception('Failed to get memcache server status.');
+                }
             }
 
             $stats = SimpleSAML\Utils\Arrays::transpose($stats);
diff --git a/lib/SimpleSAML/Metadata/SAMLParser.php b/lib/SimpleSAML/Metadata/SAMLParser.php
index 7acccd27b25f4a915c784f62aa4755cbeaa89cc4..bd8886e6808a84d4178c4bbead984d2ed170ca3d 100644
--- a/lib/SimpleSAML/Metadata/SAMLParser.php
+++ b/lib/SimpleSAML/Metadata/SAMLParser.php
@@ -362,11 +362,9 @@ class SimpleSAML_Metadata_SAMLParser
             throw new Exception('Document was empty.');
         }
 
-        assert('$element instanceof DOMElement');
-
-        if (SimpleSAML\Utils\XML::isDOMElementOfType($element, 'EntityDescriptor', '@md') === true) {
+        if (SimpleSAML\Utils\XML::isDOMNodeOfType($element, 'EntityDescriptor', '@md') === true) {
             return self::processDescriptorsElement(new \SAML2\XML\md\EntityDescriptor($element));
-        } elseif (SimpleSAML\Utils\XML::isDOMElementOfType($element, 'EntitiesDescriptor', '@md') === true) {
+        } elseif (SimpleSAML\Utils\XML::isDOMNodeOfType($element, 'EntitiesDescriptor', '@md') === true) {
             return self::processDescriptorsElement(new \SAML2\XML\md\EntitiesDescriptor($element));
         } else {
             throw new Exception('Unexpected root node: ['.$element->namespaceURI.']:'.$element->localName);
@@ -1420,7 +1418,7 @@ class SimpleSAML_Metadata_SAMLParser
             throw new Exception('Failed to load SAML metadata from empty XML document.');
         }
 
-        if (SimpleSAML\Utils\XML::isDOMElementOfType($ed, 'EntityDescriptor', '@md') === false) {
+        if (SimpleSAML\Utils\XML::isDOMNodeOfType($ed, 'EntityDescriptor', '@md') === false) {
             throw new Exception('Expected first element in the metadata document to be an EntityDescriptor element.');
         }
 
diff --git a/lib/SimpleSAML/Module.php b/lib/SimpleSAML/Module.php
index 515f4ab14ce077a908c7beaba76cb37f2e73a412..a40ef48a7ad6bcc6e3c54d355f75df805a316069 100644
--- a/lib/SimpleSAML/Module.php
+++ b/lib/SimpleSAML/Module.php
@@ -12,10 +12,26 @@ namespace SimpleSAML;
 class Module
 {
 
+    /**
+     * A list containing the modules currently installed.
+     *
+     * @var array
+     */
+    public static $modules = array();
+
+    /**
+     * A cache containing specific information for modules, like whether they are enabled or not, or their hooks.
+     *
+     * @var array
+     */
+    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.
@@ -29,8 +45,8 @@ class Module
         }
 
         $modNameEnd = strpos($className, '_', $modulePrefixLength);
-        $module     = substr($className, $modulePrefixLength, $modNameEnd - $modulePrefixLength);
-        $path       = explode('_', substr($className, $modNameEnd + 1));
+        $module = substr($className, $modulePrefixLength, $modNameEnd - $modulePrefixLength);
+        $path = explode('_', substr($className, $modNameEnd + 1));
 
         if (!self::isModuleEnabled($module)) {
             return;
@@ -46,7 +62,8 @@ class Module
             // 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)) {
+                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\\".
@@ -124,19 +141,32 @@ class Module
      */
     public static function isModuleEnabled($module)
     {
+        $config = \SimpleSAML_Configuration::getOptionalConfig();
+        return self::isModuleEnabledWithConf($module, $config->getArray('module.enable', array()));
+    }
+
+
+    private static function isModuleEnabledWithConf($module, $mod_config)
+    {
+        if (isset(self::$module_info[$module]['enabled'])) {
+            return self::$module_info[$module]['enabled'];
+        }
+
+        if (!empty(self::$modules) && !in_array($module, self::$modules)) {
+            return false;
+        }
 
         $moduleDir = self::getModuleDir($module);
 
         if (!is_dir($moduleDir)) {
+            self::$module_info[$module]['enabled'] = false;
             return false;
         }
 
-        $globalConfig = \SimpleSAML_Configuration::getOptionalConfig();
-        $moduleEnable = $globalConfig->getArray('module.enable', array());
-
-        if (isset($moduleEnable[$module])) {
-            if (is_bool($moduleEnable[$module]) === true) {
-                return $moduleEnable[$module];
+        if (isset($mod_config[$module])) {
+            if (is_bool($mod_config[$module])) {
+                self::$module_info[$module]['enabled'] = $mod_config[$module];
+                return $mod_config[$module];
             }
 
             throw new \Exception("Invalid module.enable value for the '$module' module.");
@@ -150,13 +180,16 @@ class Module
         }
 
         if (file_exists($moduleDir.'/enable')) {
+            self::$module_info[$module]['enabled'] = true;
             return true;
         }
 
         if (!file_exists($moduleDir.'/disable') && file_exists($moduleDir.'/default-enable')) {
+            self::$module_info[$module]['enabled'] = true;
             return true;
         }
 
+        self::$module_info[$module]['enabled'] = false;
         return false;
     }
 
@@ -170,17 +203,18 @@ class Module
      */
     public static function getModules()
     {
+        if (!empty(self::$modules)) {
+            return self::$modules;
+        }
 
         $path = self::getModuleDir('.');
 
-        $dh = opendir($path);
+        $dh = scandir($path);
         if ($dh === false) {
             throw new \Exception('Unable to open module directory "'.$path.'".');
         }
 
-        $modules = array();
-
-        while (($f = readdir($dh)) !== false) {
+        foreach ($dh as $f) {
             if ($f[0] === '.') {
                 continue;
             }
@@ -189,12 +223,10 @@ class Module
                 continue;
             }
 
-            $modules[] = $f;
+            self::$modules[] = $f;
         }
 
-        closedir($dh);
-
-        return $modules;
+        return self::$modules;
     }
 
 
@@ -281,6 +313,44 @@ class Module
     }
 
 
+    /**
+     * Get the available hooks for a given module.
+     *
+     * @param string $module The module where we should look for hooks.
+     *
+     * @return array An array with the hooks available for this module. Each element is an array with two keys: 'file'
+     * points to the file that contains the hook, and 'func' contains the name of the function implementing that hook.
+     * When there are no hooks defined, an empty array is returned.
+     */
+    public static function getModuleHooks($module)
+    {
+        if (isset(self::$modules[$module]['hooks'])) {
+            return self::$modules[$module]['hooks'];
+        }
+
+        $hook_dir = self::getModuleDir($module).'/hooks';
+        if (!is_dir($hook_dir)) {
+            return array();
+        }
+
+        $hooks = array();
+        $files = scandir($hook_dir);
+        foreach ($files as $file) {
+            if ($file[0] === '.') {
+                continue;
+            }
+
+            if (!preg_match('/hook_(\w+)\.php/', $file, $matches)) {
+                continue;
+            }
+            $hook_name = $matches[1];
+            $hook_func = $module.'_hook_'.$hook_name;
+            $hooks[$hook_name] = array('file' => $hook_dir.'/'.$file, 'func' => $hook_func);
+        }
+        return $hooks;
+    }
+
+
     /**
      * Call a hook in all enabled modules.
      *
@@ -288,29 +358,37 @@ class Module
      *
      * @param string $hook The name of the hook.
      * @param mixed  &$data The data which should be passed to each hook. Will be passed as a reference.
+     *
+     * @throws \SimpleSAML_Error_Exception If an invalid hook is found in a module.
      */
     public static function callHooks($hook, &$data = null)
     {
         assert('is_string($hook)');
 
         $modules = self::getModules();
+        $config = \SimpleSAML_Configuration::getOptionalConfig()->getArray('module.enable', array());
         sort($modules);
         foreach ($modules as $module) {
-            if (!self::isModuleEnabled($module)) {
+            if (!self::isModuleEnabledWithConf($module, $config)) {
                 continue;
             }
 
-            $hookfile = self::getModuleDir($module).'/hooks/hook_'.$hook.'.php';
-            if (!file_exists($hookfile)) {
+            if (!isset(self::$module_info[$module]['hooks'])) {
+                self::$module_info[$module]['hooks'] = self::getModuleHooks($module);
+            }
+
+            if (!isset(self::$module_info[$module]['hooks'][$hook])) {
                 continue;
             }
 
-            require_once($hookfile);
+            require_once(self::$module_info[$module]['hooks'][$hook]['file']);
 
-            $hookfunc = $module.'_hook_'.$hook;
-            assert('is_callable($hookfunc)');
+            if (!is_callable(self::$module_info[$module]['hooks'][$hook]['func'])) {
+                throw new \SimpleSAML_Error_Exception('Invalid hook \''.$hook.'\' for module \''.$module.'\'.');
+            }
 
-            $hookfunc($data);
+            $fn = self::$module_info[$module]['hooks'][$hook]['func'];
+            $fn($data);
         }
     }
 }
diff --git a/lib/SimpleSAML/Session.php b/lib/SimpleSAML/Session.php
index cc755d84e3abe8048bb7c62ea31990a61d10c0fc..5492a951414ae2537f944fb61a9fc08523f3c016 100644
--- a/lib/SimpleSAML/Session.php
+++ b/lib/SimpleSAML/Session.php
@@ -152,7 +152,7 @@ class SimpleSAML_Session implements Serializable
         }
 
         if ($transient) { // transient session
-            $sh = SimpleSAML_SessionHandler::getSessionHandler();
+            $sh = \SimpleSAML\SessionHandler::getSessionHandler();
             $this->trackid = 'TR'.bin2hex(openssl_random_pseudo_bytes(4));
             SimpleSAML\Logger::setTrackId($this->trackid);
             $this->transient = true;
@@ -166,7 +166,7 @@ class SimpleSAML_Session implements Serializable
                 $this->sessionId = $sh->newSessionId();
             }
         } else { // regular session
-            $sh = SimpleSAML_SessionHandler::getSessionHandler();
+            $sh = \SimpleSAML\SessionHandler::getSessionHandler();
             $this->sessionId = $sh->newSessionId();
             $sh->setCookie($sh->getSessionCookieName(), $this->sessionId, $sh->getCookieParams());
 
@@ -270,7 +270,7 @@ class SimpleSAML_Session implements Serializable
         }
 
         // if getSession() found it, use it
-        if ($session !== null) {
+        if ($session instanceof SimpleSAML_Session) {
             return self::load($session);
         }
 
@@ -311,14 +311,14 @@ class SimpleSAML_Session implements Serializable
      *
      * @param string|null $sessionId The session we should get, or null to get the current session.
      *
-     * @return SimpleSAML_Session The session that is stored in the session handler, or null if the session wasn't
+     * @return SimpleSAML_Session|null The session that is stored in the session handler, or null if the session wasn't
      * found.
      */
     public static function getSession($sessionId = null)
     {
         assert('is_string($sessionId) || is_null($sessionId)');
 
-        $sh = SimpleSAML_SessionHandler::getSessionHandler();
+        $sh = \SimpleSAML\SessionHandler::getSessionHandler();
 
         if ($sessionId === null) {
             $checkToken = true;
@@ -353,7 +353,7 @@ class SimpleSAML_Session implements Serializable
                     SimpleSAML\Logger::warning('Missing AuthToken cookie.');
                     return null;
                 }
-                if ($_COOKIE[$authTokenCookieName] !== $session->authToken) {
+                if (!SimpleSAML\Utils\Crypto::secureCompare($session->authToken, $_COOKIE[$authTokenCookieName])) {
                     SimpleSAML\Logger::warning('Invalid AuthToken cookie.');
                     return null;
                 }
@@ -439,7 +439,7 @@ class SimpleSAML_Session implements Serializable
         $this->dirty = false;
         $this->callback_registered = false;
 
-        $sh = SimpleSAML_SessionHandler::getSessionHandler();
+        $sh = \SimpleSAML\SessionHandler::getSessionHandler();
 
         try {
             $sh->saveSession($this);
@@ -462,8 +462,8 @@ class SimpleSAML_Session implements Serializable
     public function cleanup()
     {
         $this->save();
-        $sh = SimpleSAML_SessionHandler::getSessionHandler();
-        if ($sh instanceof SimpleSAML_SessionHandlerPHP) {
+        $sh = \SimpleSAML\SessionHandler::getSessionHandler();
+        if ($sh instanceof \SimpleSAML\SessionHandlerPHP) {
             $sh->restorePrevious();
         }
     }
@@ -633,7 +633,7 @@ class SimpleSAML_Session implements Serializable
         $this->authData[$authority] = $data;
 
         $this->authToken = SimpleSAML\Utils\Random::generateID();
-        $sessionHandler = SimpleSAML_SessionHandler::getSessionHandler();
+        $sessionHandler = \SimpleSAML\SessionHandler::getSessionHandler();
 
         if (!$this->transient && (!empty($data['RememberMe']) || $this->rememberMeExpire) &&
             $globalConfig->getBoolean('session.rememberme.enable', false)
@@ -760,7 +760,7 @@ class SimpleSAML_Session implements Serializable
      */
     public function updateSessionCookies($params = null)
     {
-        $sessionHandler = SimpleSAML_SessionHandler::getSessionHandler();
+        $sessionHandler = \SimpleSAML\SessionHandler::getSessionHandler();
 
         if ($this->sessionId !== null) {
             $sessionHandler->setCookie($sessionHandler->getSessionCookieName(), $this->sessionId, $params);
@@ -1040,7 +1040,7 @@ class SimpleSAML_Session implements Serializable
      */
     public function hasSessionCookie()
     {
-        $sh = SimpleSAML_SessionHandler::getSessionHandler();
+        $sh = \SimpleSAML\SessionHandler::getSessionHandler();
         return $sh->hasSessionCookie();
     }
 
diff --git a/lib/SimpleSAML/SessionHandler.php b/lib/SimpleSAML/SessionHandler.php
index f6c4300c3ee7ac3daad53b4e574594bf7195fada..7c18ac210c4afbe0b1da6118e442877b38e8e317 100644
--- a/lib/SimpleSAML/SessionHandler.php
+++ b/lib/SimpleSAML/SessionHandler.php
@@ -1,6 +1,5 @@
 <?php
 
-
 /**
  * This file is part of SimpleSAMLphp. See the file COPYING in the
  * root of the distribution for licence information.
@@ -12,7 +11,10 @@
  * @author Olav Morken, UNINETT AS. <andreas.solberg@uninett.no>
  * @package SimpleSAMLphp
  */
-abstract class SimpleSAML_SessionHandler
+
+namespace SimpleSAML;
+
+abstract class SessionHandler
 {
 
 
@@ -21,7 +23,7 @@ abstract class SimpleSAML_SessionHandler
      * instance of the session handler. This variable will be NULL if
      * we haven't instantiated a session handler yet.
      *
-     * @var SimpleSAML_SessionHandler
+     * @var \SimpleSAML\SessionHandler
      */
     protected static $sessionHandler = null;
 
@@ -31,7 +33,7 @@ abstract class SimpleSAML_SessionHandler
      * The session handler will be instantiated if this is the first call
      * to this function.
      *
-     * @return SimpleSAML_SessionHandler The current session handler.
+     * @return \SimpleSAML\SessionHandler The current session handler.
      */
     public static function getSessionHandler()
     {
@@ -44,7 +46,7 @@ abstract class SimpleSAML_SessionHandler
 
 
     /**
-     * This constructor is included in case it is needed in the the
+     * This constructor is included in case it is needed in the
      * future. Including it now allows us to write parent::__construct() in
      * the subclasses of this class.
      */
@@ -80,17 +82,17 @@ abstract class SimpleSAML_SessionHandler
     /**
      * Save the session.
      *
-     * @param SimpleSAML_Session $session The session object we should save.
+     * @param \SimpleSAML_Session $session The session object we should save.
      */
-    abstract public function saveSession(SimpleSAML_Session $session);
+    abstract public function saveSession(\SimpleSAML_Session $session);
 
 
     /**
      * Load the session.
      *
-     * @param string|NULL $sessionId The ID of the session we should load, or null to use the default.
+     * @param string|null $sessionId The ID of the session we should load, or null to use the default.
      *
-     * @return SimpleSAML_Session|null The session object, or null if it doesn't exist.
+     * @return \SimpleSAML_Session|null The session object, or null if it doesn't exist.
      */
     abstract public function loadSession($sessionId = null);
 
@@ -117,13 +119,12 @@ abstract class SimpleSAML_SessionHandler
      */
     private static function createSessionHandler()
     {
-
         $store = \SimpleSAML\Store::getInstance();
         if ($store === false) {
-            self::$sessionHandler = new SimpleSAML_SessionHandlerPHP();
+            self::$sessionHandler = new SessionHandlerPHP();
         } else {
             /** @var \SimpleSAML\Store $store At this point, $store can only be an object */
-            self::$sessionHandler = new SimpleSAML_SessionHandlerStore($store);
+            self::$sessionHandler = new SessionHandlerStore($store);
         }
     }
 
@@ -149,7 +150,7 @@ abstract class SimpleSAML_SessionHandler
      */
     public function getCookieParams()
     {
-        $config = SimpleSAML_Configuration::getInstance();
+        $config = \SimpleSAML_Configuration::getInstance();
 
         return array(
             'lifetime' => $config->getInteger('session.cookie.lifetime', 0),
diff --git a/lib/SimpleSAML/SessionHandlerCookie.php b/lib/SimpleSAML/SessionHandlerCookie.php
index e5c02bff6b9b671cedc800342da26f3fde2c0903..5f82e76b382b9362d0c0c6e980159f349a625e44 100644
--- a/lib/SimpleSAML/SessionHandlerCookie.php
+++ b/lib/SimpleSAML/SessionHandlerCookie.php
@@ -11,7 +11,12 @@
  * @package SimpleSAMLphp
  * @abstract
  */
-abstract class SimpleSAML_SessionHandlerCookie extends SimpleSAML_SessionHandler
+
+namespace SimpleSAML;
+
+use SimpleSAML\Utils\HTTP;
+
+abstract class SessionHandlerCookie extends SessionHandler
 {
 
     /**
@@ -39,7 +44,7 @@ abstract class SimpleSAML_SessionHandlerCookie extends SimpleSAML_SessionHandler
         // call the constructor in the base class in case it should become necessary in the future
         parent::__construct();
 
-        $config = SimpleSAML_Configuration::getInstance();
+        $config = \SimpleSAML_Configuration::getInstance();
         $this->cookie_name = $config->getString('session.cookie.name', 'SimpleSAMLSessionID');
     }
 
@@ -52,7 +57,7 @@ abstract class SimpleSAML_SessionHandlerCookie extends SimpleSAML_SessionHandler
     public function newSessionId()
     {
         $this->session_id = self::createSessionID();
-        SimpleSAML_Session::createSession($this->session_id);
+        \SimpleSAML_Session::createSession($this->session_id);
 
         return $this->session_id;
     }
@@ -163,6 +168,6 @@ abstract class SimpleSAML_SessionHandlerCookie extends SimpleSAML_SessionHandler
             $params = $this->getCookieParams();
         }
 
-        \SimpleSAML\Utils\HTTP::setCookie($sessionName, $sessionID, $params, true);
+        HTTP::setCookie($sessionName, $sessionID, $params, true);
     }
 }
diff --git a/lib/SimpleSAML/SessionHandlerPHP.php b/lib/SimpleSAML/SessionHandlerPHP.php
index 7964af748f2d6d1dda8dd2dab8b15747701674e5..16f2f7d7a22871ef37456dd4fe52bbce01f29acf 100644
--- a/lib/SimpleSAML/SessionHandlerPHP.php
+++ b/lib/SimpleSAML/SessionHandlerPHP.php
@@ -1,6 +1,5 @@
 <?php
 
-
 /**
  * This file is part of SimpleSAMLphp. See the file COPYING in the root of the distribution for licence information.
  *
@@ -9,7 +8,13 @@
  * @author Olav Morken, UNINETT AS. <andreas.solberg@uninett.no>
  * @package SimpleSAMLphp
  */
-class SimpleSAML_SessionHandlerPHP extends SimpleSAML_SessionHandler
+
+namespace SimpleSAML;
+
+use SimpleSAML\Error\CannotSetCookie;
+use SimpleSAML\Utils\HTTP;
+
+class SessionHandlerPHP extends SessionHandler
 {
 
     /**
@@ -34,14 +39,14 @@ class SimpleSAML_SessionHandlerPHP extends SimpleSAML_SessionHandler
 
     /**
      * Initialize the PHP session handling. This constructor is protected because it should only be called from
-     * SimpleSAML_SessionHandler::createSessionHandler(...).
+     * \SimpleSAML\SessionHandler::createSessionHandler(...).
      */
     protected function __construct()
     {
         // call the parent constructor in case it should become necessary in the future
         parent::__construct();
 
-        $config = SimpleSAML_Configuration::getInstance();
+        $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
@@ -52,7 +57,7 @@ class SimpleSAML_SessionHandlerPHP extends SimpleSAML_SessionHandler
 
         if ($previous_session) {
             if (session_name() === $this->cookie_name || $this->cookie_name === null) {
-                SimpleSAML\Logger::warning(
+                Logger::warning(
                     'There is already a PHP session with the same name as SimpleSAMLphp\'s session, or the '.
                     "'session.phpsession.cookiename' configuration option is not set. Make sure to set ".
                     "SimpleSAMLphp's cookie name with a value not used by any other applications."
@@ -167,7 +172,7 @@ class SimpleSAML_SessionHandlerPHP extends SimpleSAML_SessionHandler
     {
         // generate new (secure) session id
         $sessionId = bin2hex(openssl_random_pseudo_bytes(16));
-        SimpleSAML_Session::createSession($sessionId);
+        \SimpleSAML_Session::createSession($sessionId);
 
         return $sessionId;
     }
@@ -178,7 +183,7 @@ class SimpleSAML_SessionHandlerPHP extends SimpleSAML_SessionHandler
      *
      * @return string|null The session id saved in the cookie or null if no session cookie was set.
      *
-     * @throws SimpleSAML_Error_Exception If the cookie is marked as secure but we are not using HTTPS.
+     * @throws \SimpleSAML_Error_Exception If the cookie is marked as secure but we are not using HTTPS.
      */
     public function getCookieSessionId()
     {
@@ -191,8 +196,8 @@ class SimpleSAML_SessionHandlerPHP extends SimpleSAML_SessionHandler
 
         $session_cookie_params = session_get_cookie_params();
 
-        if ($session_cookie_params['secure'] && !\SimpleSAML\Utils\HTTP::isHTTPS()) {
-            throw new SimpleSAML_Error_Exception('Session start with secure cookie not allowed on http.');
+        if ($session_cookie_params['secure'] && !HTTP::isHTTPS()) {
+            throw new \SimpleSAML_Error_Exception('Session start with secure cookie not allowed on http.');
         }
 
         $this->sessionStart();
@@ -214,9 +219,9 @@ class SimpleSAML_SessionHandlerPHP extends SimpleSAML_SessionHandler
     /**
      * Save the current session to the PHP session array.
      *
-     * @param SimpleSAML_Session $session The session object we should save.
+     * @param \SimpleSAML_Session $session The session object we should save.
      */
-    public function saveSession(SimpleSAML_Session $session)
+    public function saveSession(\SimpleSAML_Session $session)
     {
         $_SESSION['SimpleSAMLphp_SESSION'] = serialize($session);
     }
@@ -227,9 +232,9 @@ class SimpleSAML_SessionHandlerPHP extends SimpleSAML_SessionHandler
      *
      * @param string|null $sessionId The ID of the session we should load, or null to use the default.
      *
-     * @return SimpleSAML_Session|null The session object, or null if it doesn't exist.
+     * @return \SimpleSAML_Session|null The session object, or null if it doesn't exist.
      *
-     * @throws SimpleSAML_Error_Exception If it wasn't possible to disable session cookies or we are trying to load a
+     * @throws \SimpleSAML_Error_Exception If it wasn't possible to disable session cookies or we are trying to load a
      * PHP session with a specific identifier and it doesn't match with the current session identifier.
      */
     public function loadSession($sessionId = null)
@@ -241,13 +246,13 @@ class SimpleSAML_SessionHandlerPHP extends SimpleSAML_SessionHandler
                 // session not initiated with getCookieSessionId(), start session without setting cookie
                 $ret = ini_set('session.use_cookies', '0');
                 if ($ret === false) {
-                    throw new SimpleSAML_Error_Exception('Disabling PHP option session.use_cookies failed.');
+                    throw new \SimpleSAML_Error_Exception('Disabling PHP option session.use_cookies failed.');
                 }
 
                 session_id($sessionId);
                 $this->sessionStart();
             } elseif ($sessionId !== session_id()) {
-                throw new SimpleSAML_Error_Exception('Cannot load PHP session with a specific ID.');
+                throw new \SimpleSAML_Error_Exception('Cannot load PHP session with a specific ID.');
             }
         } elseif (session_id() === '') {
             self::getCookieSessionId();
@@ -261,9 +266,8 @@ class SimpleSAML_SessionHandlerPHP extends SimpleSAML_SessionHandler
         assert('is_string($session)');
 
         $session = unserialize($session);
-        assert('$session instanceof SimpleSAML_Session');
 
-        return $session;
+        return ($session !== false) ? $session : null;
     }
 
 
@@ -288,17 +292,17 @@ class SimpleSAML_SessionHandlerPHP extends SimpleSAML_SessionHandler
      * @return array The cookie parameters for our sessions.
      * @link http://www.php.net/manual/en/function.session-get-cookie-params.php
      *
-     * @throws SimpleSAML_Error_Exception If both 'session.phpsession.limitedpath' and 'session.cookie.path' options
+     * @throws \SimpleSAML_Error_Exception If both 'session.phpsession.limitedpath' and 'session.cookie.path' options
      * are set at the same time in the configuration.
      */
     public function getCookieParams()
     {
-        $config = SimpleSAML_Configuration::getInstance();
+        $config = \SimpleSAML_Configuration::getInstance();
 
         $ret = parent::getCookieParams();
 
         if ($config->hasValue('session.phpsession.limitedpath') && $config->hasValue('session.cookie.path')) {
-            throw new SimpleSAML_Error_Exception(
+            throw new \SimpleSAML_Error_Exception(
                 'You cannot set both the session.phpsession.limitedpath and session.cookie.path options.'
             );
         } elseif ($config->hasValue('session.phpsession.limitedpath')) {
@@ -329,17 +333,17 @@ class SimpleSAML_SessionHandlerPHP extends SimpleSAML_SessionHandler
             $cookieParams = session_get_cookie_params();
         }
 
-        if ($cookieParams['secure'] && !\SimpleSAML\Utils\HTTP::isHTTPS()) {
-            throw new \SimpleSAML\Error\CannotSetCookie(
+        if ($cookieParams['secure'] && !HTTP::isHTTPS()) {
+            throw new CannotSetCookie(
                 'Setting secure cookie on plain HTTP is not allowed.',
-                \SimpleSAML\Error\CannotSetCookie::SECURE_COOKIE
+                CannotSetCookie::SECURE_COOKIE
             );
         }
 
         if (headers_sent()) {
-            throw new \SimpleSAML\Error\CannotSetCookie(
+            throw new CannotSetCookie(
                 'Headers already sent.',
-                \SimpleSAML\Error\CannotSetCookie::HEADERS_SENT
+                CannotSetCookie::HEADERS_SENT
             );
         }
 
diff --git a/lib/SimpleSAML/SessionHandlerStore.php b/lib/SimpleSAML/SessionHandlerStore.php
index 002e7ffc189648d2a53fb3454fc7b601a3536eae..fc40bf04bc22ac73c4fe6db610a7426cbea51241 100644
--- a/lib/SimpleSAML/SessionHandlerStore.php
+++ b/lib/SimpleSAML/SessionHandlerStore.php
@@ -6,7 +6,10 @@
  *
  * @package SimpleSAMLphp
  */
-class SimpleSAML_SessionHandlerStore extends SimpleSAML_SessionHandlerCookie
+
+namespace SimpleSAML;
+
+class SessionHandlerStore extends SessionHandlerCookie
 {
 
     /**
@@ -22,7 +25,7 @@ class SimpleSAML_SessionHandlerStore extends SimpleSAML_SessionHandlerCookie
      *
      * @param \SimpleSAML\Store $store The store to use.
      */
-    protected function __construct(\SimpleSAML\Store $store)
+    protected function __construct(Store $store)
     {
         parent::__construct();
 
@@ -35,7 +38,7 @@ class SimpleSAML_SessionHandlerStore extends SimpleSAML_SessionHandlerCookie
      *
      * @param string|null $sessionId The ID of the session we should load, or null to use the default.
      *
-     * @return SimpleSAML_Session|null The session object, or null if it doesn't exist.
+     * @return \SimpleSAML_Session|null The session object, or null if it doesn't exist.
      */
     public function loadSession($sessionId = null)
     {
@@ -62,18 +65,16 @@ class SimpleSAML_SessionHandlerStore extends SimpleSAML_SessionHandlerCookie
     /**
      * Save a session to the data store.
      *
-     * @param SimpleSAML_Session $session The session object we should save.
+     * @param \SimpleSAML_Session $session The session object we should save.
      */
-    public function saveSession(SimpleSAML_Session $session)
+    public function saveSession(\SimpleSAML_Session $session)
     {
-
         $sessionId = $session->getSessionId();
 
-        $config = SimpleSAML_Configuration::getInstance();
+        $config = \SimpleSAML_Configuration::getInstance();
         $sessionDuration = $config->getInteger('session.duration', 8 * 60 * 60);
         $expire = time() + $sessionDuration;
 
         $this->store->set('session', $sessionId, $session, $expire);
     }
-
 }
diff --git a/lib/SimpleSAML/Store.php b/lib/SimpleSAML/Store.php
index 5ee6e43760d93df31f4d2651cbbd6c857bac4cc8..8f25b59c9194f2fc20843e7c30a428e33aa5f01e 100644
--- a/lib/SimpleSAML/Store.php
+++ b/lib/SimpleSAML/Store.php
@@ -51,6 +51,9 @@ abstract class Store
             case 'sql':
                 self::$instance = new Store\SQL();
                 break;
+            case 'redis':
+                self::$instance = new Store\Redis();
+                break;
             default:
                 // datastore from module
                 try {
diff --git a/lib/SimpleSAML/Store/Redis.php b/lib/SimpleSAML/Store/Redis.php
new file mode 100644
index 0000000000000000000000000000000000000000..f7fc0fa847298dfc807e78b853aedb9581b4f50c
--- /dev/null
+++ b/lib/SimpleSAML/Store/Redis.php
@@ -0,0 +1,116 @@
+<?php
+
+namespace SimpleSAML\Store;
+
+use \SimpleSAML_Configuration as Configuration;
+use \SimpleSAML\Store;
+
+/**
+ * A data store using Redis to keep the data.
+ *
+ * @package SimpleSAMLphp
+ */
+class Redis extends Store
+{
+    /**
+     * Initialize the Redis data store.
+     */
+    public function __construct($redis = null)
+    {
+        assert('is_null($redis) || is_subclass_of($redis, "Predis\\Client")');
+
+        if (!class_exists('\Predis\Client')) {
+            throw new \SimpleSAML\Error\CriticalConfigurationError('predis/predis is not available.');
+        }
+
+        if (is_null($redis)) {
+            $config = Configuration::getInstance();
+
+            $host = $config->getString('store.redis.host', 'localhost');
+            $port = $config->getInteger('store.redis.port', 6379);
+            $prefix = $config->getString('store.redis.prefix', 'SimpleSAMLphp');
+
+            $redis = new \Predis\Client(
+                array(
+                    'scheme' => 'tcp',
+                    'host' => $host,
+                    'post' => $port,
+                ),
+                array(
+                    'prefix' => $prefix,
+                )
+            );
+        }
+
+        $this->redis = $redis;
+    }
+
+    /**
+     * Deconstruct the Redis data store.
+     */
+    public function __destruct()
+    {
+        if (method_exists($this->redis, 'disconnect')) {
+            $this->redis->disconnect();
+        }
+    }
+
+    /**
+     * Retrieve a value from the data store.
+     *
+     * @param string $type The type of the data.
+     * @param string $key The key to retrieve.
+     *
+     * @return mixed|null The value associated with that key, or null if there's no such key.
+     */
+    public function get($type, $key)
+    {
+        assert('is_string($type)');
+        assert('is_string($key)');
+
+        $result = $this->redis->get("{$type}.{$key}");
+
+        if ($result === false) {
+            return null;
+        }
+
+        return unserialize($result);
+    }
+
+    /**
+     * Save a value in the data store.
+     *
+     * @param string $type The type of the data.
+     * @param string $key The key to insert.
+     * @param mixed $value The value itself.
+     * @param int|null $expire The expiration time (unix timestamp), or null if it never expires.
+     */
+    public function set($type, $key, $value, $expire = null)
+    {
+        assert('is_string($type)');
+        assert('is_string($key)');
+        assert('is_null($expire) || (is_int($expire) && $expire > 2592000)');
+
+        $serialized = serialize($value);
+
+        if (is_null($expire)) {
+            $this->redis->set("{$type}.{$key}", $serialized);
+        } else {
+            $this->redis->setex("{$type}.{$key}", $expire, $serialized);
+        }
+    }
+
+    /**
+     * Delete an entry from the data store.
+     *
+     * @param string $type The type of the data
+     * @param string $key The key to delete.
+     */
+    public function delete($type, $key)
+    {
+        assert('is_string($type)');
+        assert('is_string($key)');
+
+        $this->redis->del("{$type}.{$key}");
+    }
+}
diff --git a/lib/SimpleSAML/Utilities.php b/lib/SimpleSAML/Utilities.php
index e9cda0ae60db538cb8915ce804badbd53e505ae5..46be6ca60e86bb1280f3c06ccee38ee3cb346db2 100644
--- a/lib/SimpleSAML/Utilities.php
+++ b/lib/SimpleSAML/Utilities.php
@@ -297,12 +297,12 @@ class SimpleSAML_Utilities
 
 
     /**
-     * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::isDOMElementOfType()
+     * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::isDOMNodeOfType()
      *     instead.
      */
     public static function isDOMElementOfType(DOMNode $element, $name, $nsURI)
     {
-        return SimpleSAML\Utils\XML::isDOMElementOfType($element, $name, $nsURI);
+        return SimpleSAML\Utils\XML::isDOMNodeOfType($element, $name, $nsURI);
     }
 
 
@@ -583,7 +583,7 @@ class SimpleSAML_Utilities
      */
     public static function validateCA($certificate, $caFile)
     {
-        SimpleSAML_XML_Validator::validateCertificate($certificate, $caFile);
+        \SimpleSAML\XML\Validator::validateCertificate($certificate, $caFile);
     }
 
 
diff --git a/lib/SimpleSAML/Utils/Crypto.php b/lib/SimpleSAML/Utils/Crypto.php
index 100e7da436067f1fc6edb2e1c86866422d2589d2..c3279991658a13653be9c3f8a4b222f4754c398f 100644
--- a/lib/SimpleSAML/Utils/Crypto.php
+++ b/lib/SimpleSAML/Utils/Crypto.php
@@ -13,7 +13,7 @@ class Crypto
     /**
      * Decrypt data using AES-256-CBC and the key provided as a parameter.
      *
-     * @param string $ciphertext The IV and the encrypted data, concatenated.
+     * @param string $ciphertext The HMAC of the encrypted data, the IV used and the encrypted data, concatenated.
      * @param string $secret The secret to use to decrypt the data.
      *
      * @return string The decrypted data.
@@ -24,28 +24,45 @@ class Crypto
      */
     private static function _aesDecrypt($ciphertext, $secret)
     {
-        if (!is_string($ciphertext)) {
-            throw new \InvalidArgumentException('Input parameter "$ciphertext" must be a string.');
+        if (!is_string($ciphertext) || mb_strlen($ciphertext, '8bit') < 48) {
+            throw new \InvalidArgumentException(
+                'Input parameter "$ciphertext" must be a string with more than 48 characters.'
+            );
         }
         if (!function_exists("openssl_decrypt")) {
             throw new \SimpleSAML_Error_Exception("The openssl PHP module is not loaded.");
         }
 
-        $raw    = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true;
-        $key    = openssl_digest($secret, 'sha256');
-        $method = 'AES-256-CBC';
-        $ivSize = 16;
-        $iv     = substr($ciphertext, 0, $ivSize);
-        $data   = substr($ciphertext, $ivSize);
+        // derive encryption and authentication keys from the secret
+        $key  = openssl_digest($secret, 'sha512');
+
+        $hmac = mb_substr($ciphertext, 0, 32, '8bit');
+        $iv   = mb_substr($ciphertext, 32, 16, '8bit');
+        $msg  = mb_substr($ciphertext, 48, mb_strlen($ciphertext, '8bit') - 48, '8bit');
+
+        // authenticate the ciphertext
+        if (self::secureCompare(hash_hmac('sha256', $iv.$msg, substr($key, 64, 64), true), $hmac)) {
+            $plaintext = openssl_decrypt(
+                $msg,
+                'AES-256-CBC',
+                substr($key, 0, 64),
+                defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true,
+                $iv
+            );
+
+            if ($plaintext != false) {
+                return $plaintext;
+            }
+        }
 
-        return openssl_decrypt($data, $method, $key, $raw, $iv);
+        throw new \SimpleSAML_Error_Exception("Failed to decrypt ciphertext.");
     }
 
 
     /**
      * Decrypt data using AES-256-CBC and the system-wide secret salt as key.
      *
-     * @param string $ciphertext The IV used and the encrypted data, concatenated.
+     * @param string $ciphertext The HMAC of the encrypted data, the IV used and the encrypted data, concatenated.
      *
      * @return string The decrypted data.
      * @throws \InvalidArgumentException If $ciphertext is not a string.
@@ -66,7 +83,7 @@ class Crypto
      * @param string $data The data to encrypt.
      * @param string $secret The secret to use to encrypt the data.
      *
-     * @return string The IV and encrypted data concatenated.
+     * @return string An HMAC of the encrypted data, the IV and the encrypted data, concatenated.
      * @throws \InvalidArgumentException If $data is not a string.
      * @throws \SimpleSAML_Error_Exception If the openssl module is not loaded.
      *
@@ -82,13 +99,27 @@ class Crypto
             throw new \SimpleSAML_Error_Exception('The openssl PHP module is not loaded.');
         }
 
-        $raw    = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true;
-        $key    = openssl_digest($secret, 'sha256');
-        $method = 'AES-256-CBC';
-        $ivSize = 16;
-        $iv     = substr($key, 0, $ivSize);
+        // derive encryption and authentication keys from the secret
+        $key = openssl_digest($secret, 'sha512');
+
+        // generate a random IV
+        $iv = openssl_random_pseudo_bytes(16);
+
+        // encrypt the message
+        $ciphertext = $iv.openssl_encrypt(
+            $data,
+            'AES-256-CBC',
+            substr($key, 0, 64),
+            defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true,
+            $iv
+        );
+
+        if ($ciphertext === false) {
+            throw new \SimpleSAML_Error_Exception("Failed to encrypt plaintext.");
+        }
 
-        return $iv.openssl_encrypt($data, $method, $key, $raw, $iv);
+        // return the ciphertext with proper authentication
+        return hash_hmac('sha256', $ciphertext, substr($key, 64, 64), true).$ciphertext;
     }
 
 
@@ -97,7 +128,7 @@ class Crypto
      *
      * @param string $data The data to encrypt.
      *
-     * @return string The IV and encrypted data concatenated.
+     * @return string An HMAC of the encrypted data, the IV and the encrypted data, concatenated.
      * @throws \InvalidArgumentException If $data is not a string.
      * @throws \SimpleSAML_Error_Exception If the openssl module is not loaded.
      *
@@ -142,6 +173,8 @@ class Crypto
      * missing key will cause an exception. Defaults to false.
      * @param string                    $prefix The prefix which should be used when reading from the metadata
      * array. Defaults to ''.
+     * @param bool                      $full_path Whether the filename found in the configuration contains the
+     * full path to the private key or not. Default to false.
      *
      * @return array|NULL Extracted private key, or NULL if no private key is present.
      * @throws \InvalidArgumentException If $required is not boolean or $prefix is not a string.
@@ -151,9 +184,9 @@ class Crypto
      * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
      * @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
      */
-    public static function loadPrivateKey(\SimpleSAML_Configuration $metadata, $required = false, $prefix = '')
+    public static function loadPrivateKey(\SimpleSAML_Configuration $metadata, $required = false, $prefix = '', $full_path = false)
     {
-        if (!is_bool($required) || !is_string($prefix)) {
+        if (!is_bool($required) || !is_string($prefix) || !is_bool($full_path)) {
             throw new \InvalidArgumentException('Invalid input parameters.');
         }
 
@@ -167,7 +200,10 @@ class Crypto
             }
         }
 
-        $file = Config::getCertPath($file);
+        if (!$full_path) {
+            $file = Config::getCertPath($file);
+        }
+
         $data = @file_get_contents($file);
         if ($data === false) {
             throw new \SimpleSAML_Error_Exception('Unable to load private key from file "'.$file.'"');
@@ -349,6 +385,38 @@ class Crypto
     }
 
 
+    /**
+     * Compare two strings securely.
+     *
+     * This method checks if two strings are equal in constant time, avoiding timing attacks. Use it every time we need
+     * to compare a string with a secret that shouldn't be leaked, i.e. when verifying passwords, one-time codes, etc.
+     *
+     * @param string $known A known string.
+     * @param string $user A user-provided string to compare with the known string.
+     *
+     * @return bool True if both strings are equal, false otherwise.
+     */
+    public static function secureCompare($known, $user)
+    {
+        if (function_exists('hash_equals')) {
+            // use hash_equals() if available (PHP >= 5.6)
+            return hash_equals($known, $user);
+        }
+
+        // compare manually in constant time
+        $len = mb_strlen($known, '8bit'); // see mbstring.func_overload
+        if ($len !== mb_strlen($user, '8bit')) {
+            return false; // length differs
+        }
+        $diff = 0;
+        for ($i = 0; $i < $len; $i++) {
+            $diff |= ord($known[$i]) ^ ord($user[$i]);
+        }
+        // if all the bytes in $a and $b are identical, $diff should be equal to 0
+        return $diff === 0;
+    }
+
+
     /**
      * This function checks if a password is valid
      *
@@ -374,7 +442,7 @@ class Crypto
 
             // hash w/o salt
             if (in_array(strtolower($alg), hash_algos())) {
-                return $hash === self::pwHash($password, $alg);
+                return self::secureCompare($hash, self::pwHash($password, $alg));
             }
 
             // hash w/ salt
@@ -384,7 +452,7 @@ class Crypto
                 // get hash length of this algorithm to learn how long the salt is
                 $hash_length = strlen(hash($php_alg, '', true));
                 $salt = substr(base64_decode($matches[2]), $hash_length);
-                return ($hash === self::pwHash($password, $alg, $salt));
+                return self::secureCompare($hash, self::pwHash($password, $alg, $salt));
             }
         } else {
             return $hash === $password;
diff --git a/lib/SimpleSAML/Utils/HTTP.php b/lib/SimpleSAML/Utils/HTTP.php
index 1acdea6726ac4508eb71a2b4c2a2bb108f7b8db7..a254e1ed713b14cf1e4a39abfe2455021b86cc18 100644
--- a/lib/SimpleSAML/Utils/HTTP.php
+++ b/lib/SimpleSAML/Utils/HTTP.php
@@ -374,7 +374,8 @@ class HTTP
      * @param array   $context Extra context options. This parameter is optional.
      * @param boolean $getHeaders Whether to also return response headers. Optional.
      *
-     * @return mixed array if $getHeaders is set, string otherwise
+     * @return string|array An array if $getHeaders is set, containing the data and the headers respectively; string
+     *  otherwise.
      * @throws \InvalidArgumentException If the input parameters are invalid.
      * @throws \SimpleSAML_Error_Exception If the file or URL cannot be retrieved.
      *
diff --git a/lib/SimpleSAML/Utils/System.php b/lib/SimpleSAML/Utils/System.php
index 61e6bfe7e63d3aebfa742356dd8755efcf6ad3b5..ec8b8ba1a2d36c5825861c20e00a6938e7705c74 100644
--- a/lib/SimpleSAML/Utils/System.php
+++ b/lib/SimpleSAML/Utils/System.php
@@ -31,12 +31,12 @@ class System
         if (stristr(PHP_OS, 'LINUX')) {
             return self::LINUX;
         }
-        if (stristr(PHP_OS, 'WIN')) {
-            return self::WINDOWS;
-        }
         if (stristr(PHP_OS, 'DARWIN')) {
             return self::OSX;
         }
+        if (stristr(PHP_OS, 'WIN')) {
+            return self::WINDOWS;
+        }
         if (stristr(PHP_OS, 'BSD')) {
             return self::BSD;
         }
@@ -203,5 +203,9 @@ class System
                 'Error moving "'.$tmpFile.'" to "'.$filename.'": '.$error['message']
             );
         }
+
+        if (function_exists('opcache_invalidate')) {
+            opcache_invalidate($filename);
+        }
     }
 }
diff --git a/lib/SimpleSAML/Utils/XML.php b/lib/SimpleSAML/Utils/XML.php
index abaa00543cfd2aa433a6472a0fc5b5c4066a205f..d3c6dd77347bffd35ad0267fa93fdca7c30ebc9e 100644
--- a/lib/SimpleSAML/Utils/XML.php
+++ b/lib/SimpleSAML/Utils/XML.php
@@ -8,6 +8,7 @@
 namespace SimpleSAML\Utils;
 
 use SimpleSAML\Logger;
+use SimpleSAML\XML\Errors;
 
 class XML
 {
@@ -258,20 +259,20 @@ class XML
      * This function finds direct descendants of a DOM element with the specified
      * localName and namespace. They are returned in an array.
      *
-     * This function accepts the same shortcuts for namespaces as the isDOMElementOfType function.
+     * This function accepts the same shortcuts for namespaces as the isDOMNodeOfType function.
      *
-     * @param \DOMElement $element The element we should look in.
-     * @param string      $localName The name the element should have.
-     * @param string      $namespaceURI The namespace the element should have.
+     * @param \DOMNode $element The element we should look in.
+     * @param string   $localName The name the element should have.
+     * @param string   $namespaceURI The namespace the element should have.
      *
-     * @return array  Array with the matching elements in the order they are found. An empty array is
+     * @return array Array with the matching elements in the order they are found. An empty array is
      *         returned if no elements match.
      * @throws \InvalidArgumentException If $element is not an instance of DOMElement, $localName is not a string or
      *     $namespaceURI is not a string.
      */
-    public static function getDOMChildren(\DOMElement $element, $localName, $namespaceURI)
+    public static function getDOMChildren(\DOMNode $element, $localName, $namespaceURI)
     {
-        if (!($element instanceof \DOMElement) || !is_string($localName) || !is_string($namespaceURI)) {
+        if (!is_string($localName) || !is_string($namespaceURI)) {
             throw new \InvalidArgumentException('Invalid input parameters.');
         }
 
@@ -285,7 +286,7 @@ class XML
                 continue;
             }
 
-            if (self::isDOMElementOfType($child, $localName, $namespaceURI) === true) {
+            if (self::isDOMNodeOfType($child, $localName, $namespaceURI) === true) {
                 $ret[] = $child;
             }
         }
@@ -349,9 +350,9 @@ class XML
      * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
      * @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
      */
-    public static function isDOMElementOfType(\DOMNode $element, $name, $nsURI)
+    public static function isDOMNodeOfType(\DOMNode $element, $name, $nsURI)
     {
-        if (!($element instanceof \DOMElement) || !is_string($name) || !is_string($nsURI) || strlen($nsURI) === 0) {
+        if (!is_string($name) || !is_string($nsURI) || strlen($nsURI) === 0) {
             // most likely a comment-node
             return false;
         }
@@ -409,7 +410,7 @@ class XML
             throw new \InvalidArgumentException('Invalid input parameters.');
         }
 
-        \SimpleSAML_XML_Errors::begin();
+        Errors::begin();
 
         if ($xml instanceof \DOMDocument) {
             $dom = $xml;
@@ -431,7 +432,7 @@ class XML
 
             $res = $dom->schemaValidate($schemaFile);
             if ($res) {
-                \SimpleSAML_XML_Errors::end();
+                Errors::end();
                 return true;
             }
 
@@ -440,8 +441,8 @@ class XML
             $errorText = "Failed to parse XML string for schema validation:\n";
         }
 
-        $errors = \SimpleSAML_XML_Errors::end();
-        $errorText .= \SimpleSAML_XML_Errors::formatErrors($errors);
+        $errors = Errors::end();
+        $errorText .= Errors::formatErrors($errors);
 
         return $errorText;
     }
diff --git a/lib/SimpleSAML/XHTML/Template.php b/lib/SimpleSAML/XHTML/Template.php
index 97c17d4661e9f361ae1584ccd41ef2e954649ed2..38a8066c3083d6d5377d31062b02ec8d772313c6 100644
--- a/lib/SimpleSAML/XHTML/Template.php
+++ b/lib/SimpleSAML/XHTML/Template.php
@@ -65,18 +65,34 @@ class SimpleSAML_XHTML_Template
      */
     private $twig_template;
 
-    /*
-     * Main Twig namespace, to avoid misspelling it *again*
+    /**
+     * Current module, if any.
      */
-    private $twig_namespace = \Twig_Loader_Filesystem::MAIN_NAMESPACE;
-
+    private $module;
 
-    /*
-     * Current module, if any
+    /**
+     * A template controller, if any.
+     *
+     * Used to intercept certain parts of the template handling, while keeping away unwanted/unexpected hooks. Set
+     * the 'theme.controller' configuration option to a class that implements the
+     * SimpleSAML\XHTML\TemplateControllerInterface interface to use it.
+     *
+     * @var SimpleSAML\XHTML\TemplateControllerInterface
      */
-    private $module;
+    private $controller;
 
 
+    /**
+     * Whether we are using a non-default theme or not.
+     *
+     * If we are using a theme, this variable holds an array with two keys: "module" and "name", those being the name
+     * of the module and the name of the theme, respectively. If we are using the default theme, the variable defaults
+     * to false.
+     *
+     * @var bool|array
+     */
+    private $theme;
+
     /**
      * Constructor
      *
@@ -90,10 +106,27 @@ class SimpleSAML_XHTML_Template
         $this->template = $template;
         // TODO: do not remove the slash from the beginning, change the templates instead!
         $this->data['baseurlpath'] = ltrim($this->configuration->getBasePath(), '/');
-        $result = $this->findModuleAndTemplateName($template);
-        $this->module = $result[0];
+
+        // parse module and template name
+        list($this->module) = $this->findModuleAndTemplateName($template);
+
+        // parse config to find theme and module theme is in, if any
+        list($this->theme['module'], $this->theme['name']) = self::findModuleAndTemplateName(
+            $this->configuration->getString('theme.use', 'default')
+        );
+
+        // initialize internationalization system
         $this->translator = new SimpleSAML\Locale\Translate($configuration, $defaultDictionary);
         $this->localization = new \SimpleSAML\Locale\Localization($configuration);
+
+        // check if we need to attach a theme controller
+        $controller = $this->configuration->getString('theme.controller', false);
+        if ($controller && class_exists($controller) &&
+            class_implements($controller, '\SimpleSAML\XHTML\TemplateControllerInterface')
+        ) {
+            $this->controller = new $controller();
+        }
+
         $this->twig = $this->setupTwig();
     }
 
@@ -132,21 +165,17 @@ class SimpleSAML_XHTML_Template
         $filename = $this->normalizeTemplateName($this->template);
 
         // get namespace if any
-        $namespace = '';
-        $split = explode(':', $filename, 2);
-        if (count($split) === 2) {
-            $namespace = $split[0];
-            $filename = $split[1];
-        }
-        $this->twig_template = $namespace ? '@'.$namespace.'/'.$filename : $filename;
+        list($namespace, $filename) = self::findModuleAndTemplateName($filename);
+        $this->twig_template = ($namespace !== null) ? '@'.$namespace.'/'.$filename : $filename;
         $loader = new \Twig_Loader_Filesystem();
-        $templateDirs = array_merge(
-            $this->findThemeTemplateDirs(),
-            $this->findModuleTemplateDirs()
-        );
+        $templateDirs = $this->findThemeTemplateDirs();
+        if ($this->module) {
+            $templateDirs[] = array($this->module => $this->getModuleTemplateDir($this->module));
+        }
+
         // default, themeless templates are checked last
         $templateDirs[] = array(
-            $this->twig_namespace => $this->configuration->resolvePath('templates')
+            \Twig_Loader_Filesystem::MAIN_NAMESPACE => $this->configuration->resolvePath('templates')
         );
         foreach ($templateDirs as $entry) {
             $loader->addPath($entry[key($entry)], key($entry));
@@ -161,11 +190,7 @@ class SimpleSAML_XHTML_Template
     private function setupTwig()
     {
         $auto_reload = $this->configuration->getBoolean('template.auto_reload', true);
-        $cache = false;
-        if (!$auto_reload) {
-            // Cache only used if auto_reload = false
-            $cache = $this->configuration->getString('template.cache', $this->configuration->resolvePath('cache'));
-        }
+        $cache = $this->configuration->getString('template.cache', false);
         // set up template paths
         $loader = $this->setupTwigTemplatepaths();
         // abort if twig template does not exist
@@ -173,11 +198,13 @@ class SimpleSAML_XHTML_Template
             return false;
         }
 
-
         // load extra i18n domains
         if ($this->module) {
             $this->localization->addModuleDomain($this->module);
         }
+        if ($this->theme['module'] !== null && $this->theme['module'] !== $this->module) {
+            $this->localization->addModuleDomain($this->theme['module']);
+        }
 
         $options = array(
             'cache' => $cache,
@@ -197,71 +224,102 @@ class SimpleSAML_XHTML_Template
 
         $twig = new Twig_Environment($loader, $options);
         $twig->addExtension(new Twig_Extensions_Extension_I18n());
+
+        // initialize some basic context
+        $langParam = $this->configuration->getString('language.parameter.name', 'language');
+        $twig->addGlobal('languageParameterName', $langParam);
+        $twig->addGlobal('localeBackend', $this->configuration->getString('language.i18n.backend', 'SimpleSAMLphp'));
+        $twig->addGlobal('currentLanguage', $this->translator->getLanguage()->getLanguage());
+        $twig->addGlobal('isRTL', false); // language RTL configuration
+        if ($this->translator->getLanguage()->isLanguageRTL()) {
+            $twig->addGlobal('isRTL', true);
+        }
+        $queryParams = $_GET; // add query parameters, in case we need them in the template
+        if (isset($queryParams[$langParam])) {
+            unset($queryParams[$langParam]);
+        }
+        $twig->addGlobal('queryParams', $queryParams);
+        $twig->addGlobal('templateId', str_replace('.twig', '', $this->normalizeTemplateName($this->template)));
+
+        if ($this->controller) {
+            $this->controller->setUpTwig($twig);
+        }
+
         return $twig;
     }
 
-    /*
-     * Add overriding templates in configured theme
+    /**
+     * Add overriding templates from the configured theme.
      *
-     * @return array an array of module => templatedir lookups
+     * @return array An array of module => templatedir lookups.
      */
     private function findThemeTemplateDirs()
     {
-        // parse config to find theme and module theme is in, if any
-        $tmp = explode(':', $this->configuration->getString('theme.use', 'default'), 2);
-        if (count($tmp) === 2) {
-            $themeModule = $tmp[0];
-            $themeName = $tmp[1];
-        } else {
-            $themeModule = null;
-            $themeName = $tmp[0];
+        if ($this->theme['module'] === null) { // no module involved
+            return array();
         }
-        // default theme in use, abort
-        if ($themeName == 'default') {
+
+        // setup directories & namespaces
+        $themeDir = \SimpleSAML\Module::getModuleDir($this->theme['module']).'/themes/'.$this->theme['name'];
+        $subdirs = scandir($themeDir);
+        if (!$subdirs) { // no subdirectories in the theme directory, nothing to do here
+            // this is probably wrong, log a message
+            \SimpleSAML\Logger::warning('Emtpy theme directory for theme "'.$this->theme['name'].'".');
             return array();
         }
-        if ($themeModule !== null) {
-            $moduleDir = \SimpleSAML\Module::getModuleDir($themeModule);
-            $themeDir = $moduleDir.'/themes/'.$themeName;
-            $files = scandir($themeDir);
-            if ($files) {
-                $themeTemplateDirs = array();
-                foreach ($files as $file) {
-                    if ($file == '.' || $file == '..') {
-                        continue;
-                    }
-                    // set correct name for default namespace
-                    $ns = $file == 'default' ? $this->twig_namespace : $file;
-                    $themeTemplateDirs[] = array($ns => $themeDir.'/'.$file);
-                }
-                return $themeTemplateDirs;
+
+        $themeTemplateDirs = array();
+        foreach ($subdirs as $entry) {
+            // discard anything that's not a directory. Expression is negated to profit from lazy evaluation
+            if (!($entry !== '.' && $entry !== '..' && is_dir($themeDir.'/'.$entry))) {
+                continue;
             }
+
+            // set correct name for the default namespace
+            $ns = ($entry === 'default') ? \Twig_Loader_Filesystem::MAIN_NAMESPACE : $entry;
+            $themeTemplateDirs[] = array($ns => $themeDir.'/'.$entry);
         }
-        // theme not found
-        return array();
+        return $themeTemplateDirs;
     }
 
-    /*
-     * Which enabled modules have templates?
+    /**
+     * Get the template directory of a module, if it exists.
      *
-     * @return array an array of module => templatedir lookups
+     * @return string The templates directory of a module.
+     *
+     * @throws InvalidArgumentException If the module is not enabled or it has no templates directory.
      */
-    private function findModuleTemplateDirs()
+    private function getModuleTemplateDir($module)
     {
-        $all_modules = \SimpleSAML\Module::getModules();
-        $modules = array();
-        foreach ($all_modules as $module) {
-            if (!\SimpleSAML\Module::isModuleEnabled($module)) {
-                continue;
-            }
-            $moduledir = \SimpleSAML\Module::getModuleDir($module);
-            // check if module has a /templates dir, if so, append
-            $templatedir = $moduledir.'/templates';
-            if (is_dir($templatedir)) {
-                $modules[] = array($module => $templatedir);
-            }
+        if (!\SimpleSAML\Module::isModuleEnabled($module)) {
+            throw new InvalidArgumentException('The module \''.$module.'\' is not enabled.');
         }
-        return $modules;
+        $moduledir = \SimpleSAML\Module::getModuleDir($module);
+        // check if module has a /templates dir, if so, append
+        $templatedir = $moduledir.'/templates';
+        if (!is_dir($templatedir)) {
+            throw new InvalidArgumentException('The module \''.$module.'\' has no templates directory.');
+
+        }
+        return $templatedir;
+    }
+
+
+    /**
+     * Add the templates from a given module.
+     *
+     * Note that the module must be installed, enabled, and contain a "templates" directory.
+     *
+     * @param string $module The module where we need to search for templates.
+     *
+     * @throws InvalidArgumentException If the module is not enabled or it has no templates directory.
+     */
+    public function addTemplatesFromModule($module)
+    {
+        $dir = $this->getModuleTemplateDir($module);
+        /** @var Twig_Loader_Filesystem $loader */
+        $loader = $this->twig->getLoader();
+        $loader->addPath($dir, $module);
     }
 
 
@@ -303,8 +361,6 @@ class SimpleSAML_XHTML_Template
      */
     private function twigDefaultContext()
     {
-        $this->data['localeBackend'] = $this->configuration->getString('language.i18n.backend', 'SimpleSAMLphp');
-        $this->data['currentLanguage'] = $this->translator->getLanguage()->getLanguage();
         // show language bar by default
         if (!isset($this->data['hideLanguageBar'])) {
             $this->data['hideLanguageBar'] = false;
@@ -327,12 +383,6 @@ class SimpleSAML_XHTML_Template
         if (!isset($this->data['pagetitle'])) {
             $this->data['pagetitle'] = 'SimpleSAMLphp';
         }
-
-        // set RTL
-        $this->data['isRTL'] = false;
-        if ($this->translator->getLanguage()->isLanguageRTL()) {
-            $this->data['isRTL'] = true;
-        }
     }
 
 
@@ -343,6 +393,9 @@ class SimpleSAML_XHTML_Template
     {
         if ($this->twig !== false) {
             $this->twigDefaultContext();
+            if ($this->controller) {
+                $this->controller->display($this->data);
+            }
             echo $this->twig->render($this->twig_template, $this->data);
         } else {
             $filename = $this->findTemplatePath($this->template);
@@ -361,15 +414,7 @@ class SimpleSAML_XHTML_Template
     private function findModuleAndTemplateName($template)
     {
         $tmp = explode(':', $template, 2);
-        if (count($tmp) === 2) {
-            $templateModule = $tmp[0];
-            $templateName = $tmp[1];
-        } else {
-            $templateModule = null;
-            $templateName = $tmp[0];
-        }
-
-        return array($templateModule, $templateName);
+        return (count($tmp) === 2) ? array($tmp[0], $tmp[1]) : array(null, $tmp[0]);
     }
 
 
@@ -392,25 +437,15 @@ class SimpleSAML_XHTML_Template
     {
         assert('is_string($template)');
 
-        $result = $this->findModuleAndTemplateName($template);
-        $templateModule = $result[0] ? $result[0] : 'default';
-        $templateName = $result[1];
-
-        $tmp = explode(':', $this->configuration->getString('theme.use', 'default'), 2);
-        if (count($tmp) === 2) {
-            $themeModule = $tmp[0];
-            $themeName = $tmp[1];
-        } else {
-            $themeModule = null;
-            $themeName = $tmp[0];
-        }
+        list($templateModule, $templateName) = $this->findModuleAndTemplateName($template);
+        $templateModule = ($templateModule !== null) ? $templateModule : 'default';
 
         // first check the current theme
-        if ($themeModule !== null) {
+        if ($this->theme['module'] !== null) {
             // .../module/<themeModule>/themes/<themeName>/<templateModule>/<templateName>
 
-            $filename = \SimpleSAML\Module::getModuleDir($themeModule).
-                '/themes/'.$themeName.'/'.$templateModule.'/'.$templateName;
+            $filename = \SimpleSAML\Module::getModuleDir($this->theme['module']).
+                '/themes/'.$this->theme['name'].'/'.$templateModule.'/'.$templateName;
         } elseif ($templateModule !== 'default') {
             // .../module/<templateModule>/templates/<templateName>
             $filename = \SimpleSAML\Module::getModuleDir($templateModule).'/templates/'.$templateName;
@@ -467,6 +502,17 @@ class SimpleSAML_XHTML_Template
     }
 
 
+    /**
+     * Get the current instance of Twig in use.
+     *
+     * @return false|Twig_Environment The Twig instance in use, or false if Twig is not used.
+     */
+    public function getTwig()
+    {
+        return $this->twig;
+    }
+
+
     /*
      * Deprecated methods of this interface, all of them should go away.
      */
diff --git a/lib/SimpleSAML/XHTML/TemplateControllerInterface.php b/lib/SimpleSAML/XHTML/TemplateControllerInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..bd54907c48a9e5d4152062e36a19add335ad83ee
--- /dev/null
+++ b/lib/SimpleSAML/XHTML/TemplateControllerInterface.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace SimpleSAML\XHTML;
+
+/**
+ * Interface that allows modules to run several hooks for templates.
+ *
+ * @package SimpleSAMLphp
+ */
+interface TemplateControllerInterface {
+
+    /**
+     * Implement to modify the twig environment after its initialization (e.g. add filters or extensions).
+     *
+     * @param \Twig_Environment $twig The current twig environment.
+     *
+     * @return void
+     */
+    public function setUpTwig(\Twig_Environment &$twig);
+
+
+    /**
+     * Implement to add, delete or modify the data passed to the template.
+     *
+     * This method will be called right before displaying the template.
+     *
+     * @param array $data The current data used by the template.
+     *
+     * @return void
+     */
+    public function display(&$data);
+}
diff --git a/lib/SimpleSAML/XML/Errors.php b/lib/SimpleSAML/XML/Errors.php
index c56d95b74b8ec8679f1c2a78aac349d6f06ed609..201c56f26956f69821628b9e3cd1b68d8109e586 100644
--- a/lib/SimpleSAML/XML/Errors.php
+++ b/lib/SimpleSAML/XML/Errors.php
@@ -9,124 +9,130 @@
  * @author Olav Morken, UNINETT AS.
  * @package SimpleSAMLphp
  */
-class SimpleSAML_XML_Errors {
-
-	/**
-	 * This is an stack of error logs. The topmost element is the one we are
-	 * currently working on.
-	 */
-	private static $errorStack = array();
-
-	/**
-	 * This is the xml error state we had before we began logging.
-	 */
-	private static $xmlErrorState;
-
-
-	/**
-	 * Append current XML errors to the the current stack level.
-	 */
-	private static function addErrors() {
-
-		$currentErrors = libxml_get_errors();
-		libxml_clear_errors();
-
-		$level = count(self::$errorStack) - 1;
-		self::$errorStack[$level] = array_merge(self::$errorStack[$level], $currentErrors);
-	}
-
-
-	/**
-	 * Start error logging.
-	 *
-	 * A call to this function will begin a new error logging context. Every call must have
-	 * a corresponding call to end().
-	 */
-	public static function begin() {
-
-		// Check whether the error access functions are present
-		if(!function_exists('libxml_use_internal_errors')) {
-			return;
-		}
-
-		if(count(self::$errorStack) === 0) {
-			// No error logging is currently in progress. Initialize it.
-			self::$xmlErrorState = libxml_use_internal_errors(TRUE);
-			libxml_clear_errors();
-		} else {
-			/* We have already started error logging. Append the current errors to the
-			 * list of errors in this level.
-			 */
-			self::addErrors();
-		}
-
-		// Add a new level to the error stack
-		self::$errorStack[] = array();
-	}
-
-
-	/**
-	 * End error logging.
-	 *
-	 * @return  An array with the LibXMLErrors which has occurred since begin() was called.
-	 */
-	public static function end() {
-
-		// Check whether the error access functions are present
-		if(!function_exists('libxml_use_internal_errors')) {
-			// Pretend that no errors occurred
-			return array();
-		}
-
-		// Add any errors which may have occurred
-		self::addErrors();
-
-
-		$ret = array_pop(self::$errorStack);
-
-		if(count(self::$errorStack) === 0) {
-			// Disable our error logging and restore the previous state
-			libxml_use_internal_errors(self::$xmlErrorState);
-		}
-
-		return $ret;
-	}
-
-
-	/**
-	 * Format an error as a string.
-	 *
-	 * This function formats the given LibXMLError object as a string.
-	 *
-	 * @param $error  The LibXMLError which should be formatted.
-	 * @return  A string representing the given LibXMLError.
-	 */
-	public static function formatError($error) {
-		assert('$error instanceof LibXMLError');
-		return 'level=' . $error->level . ',code='  . $error->code . ',line=' . $error->line . ',col=' . $error->column .
-			',msg=' . trim($error->message);
-	}
-
-
-	/**
-	 * Format a list of errors as a string.
-	 *
-	 * This fucntion takes an array of LibXMLError objects and creates a string with all the errors.
-	 * Each error will be separated by a newline, and the string will end with a newline-character.
-	 *
-	 * @param $errors  An array of errors.
-	 * @return  A string representing the errors. An empty string will be returned if there were no
-	 *          errors in the array.
-	 */
-	public static function formatErrors($errors) {
-		assert('is_array($errors)');
-
-		$ret = '';
-		foreach($errors as $error) {
-			$ret .= self::formatError($error) . "\n";
-		}
-
-		return $ret;
-	}
 
+namespace SimpleSAML\XML;
+
+class Errors
+{
+
+    /**
+     * @var array This is an stack of error logs. The topmost element is the one we are currently working on.
+     */
+    private static $errorStack = array();
+
+    /**
+     * @var bool This is the xml error state we had before we began logging.
+     */
+    private static $xmlErrorState;
+
+
+    /**
+     * Append current XML errors to the current stack level.
+     */
+    private static function addErrors()
+    {
+        $currentErrors = libxml_get_errors();
+        libxml_clear_errors();
+
+        $level = count(self::$errorStack) - 1;
+        self::$errorStack[$level] = array_merge(self::$errorStack[$level], $currentErrors);
+    }
+
+
+    /**
+     * Start error logging.
+     *
+     * A call to this function will begin a new error logging context. Every call must have
+     * a corresponding call to end().
+     */
+    public static function begin()
+    {
+
+        // Check whether the error access functions are present
+        if (!function_exists('libxml_use_internal_errors')) {
+            return;
+        }
+
+        if (count(self::$errorStack) === 0) {
+            // No error logging is currently in progress. Initialize it.
+            self::$xmlErrorState = libxml_use_internal_errors(true);
+            libxml_clear_errors();
+        } else {
+            /* We have already started error logging. Append the current errors to the
+             * list of errors in this level.
+             */
+            self::addErrors();
+        }
+
+        // Add a new level to the error stack
+        self::$errorStack[] = array();
+    }
+
+
+    /**
+     * End error logging.
+     *
+     * @return array  An array with the LibXMLErrors which has occurred since begin() was called.
+     */
+    public static function end()
+    {
+
+        // Check whether the error access functions are present
+        if (!function_exists('libxml_use_internal_errors')) {
+            // Pretend that no errors occurred
+            return array();
+        }
+
+        // Add any errors which may have occurred
+        self::addErrors();
+
+
+        $ret = array_pop(self::$errorStack);
+
+        if (count(self::$errorStack) === 0) {
+            // Disable our error logging and restore the previous state
+            libxml_use_internal_errors(self::$xmlErrorState);
+        }
+
+        return $ret;
+    }
+
+
+    /**
+     * Format an error as a string.
+     *
+     * This function formats the given LibXMLError object as a string.
+     *
+     * @param \LibXMLError $error  The LibXMLError which should be formatted.
+     * @return string  A string representing the given LibXMLError.
+     */
+    public static function formatError($error)
+    {
+        assert('$error instanceof LibXMLError');
+        return 'level=' . $error->level . ',code='  . $error->code . ',line=' . $error->line . ',col=' . $error->column .
+            ',msg=' . trim($error->message);
+    }
+
+
+    /**
+     * Format a list of errors as a string.
+     *
+     * This fucntion takes an array of LibXMLError objects and creates a string with all the errors.
+     * Each error will be separated by a newline, and the string will end with a newline-character.
+     *
+     * @param array $errors  An array of errors.
+     * @return string  A string representing the errors. An empty string will be returned if there were no
+     *          errors in the array.
+     */
+    public static function formatErrors($errors)
+    {
+        assert('is_array($errors)');
+
+        $ret = '';
+        foreach ($errors as $error) {
+            $ret .= self::formatError($error) . "\n";
+        }
+
+        return $ret;
+    }
 }
diff --git a/lib/SimpleSAML/XML/Parser.php b/lib/SimpleSAML/XML/Parser.php
index 42b4731b9de159f75d8c4b3817e64a7183556f7d..b43fe49a76eccb329d6fa83bd321f3578287ca97 100644
--- a/lib/SimpleSAML/XML/Parser.php
+++ b/lib/SimpleSAML/XML/Parser.php
@@ -6,59 +6,72 @@
  * @author Andreas Ã…kre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
  * @package SimpleSAMLphp
  */
-class SimpleSAML_XML_Parser  {
 
-	var $simplexml = null;
+namespace SimpleSAML\XML;
 
-	function __construct($xml) {;
-		$this->simplexml = new SimpleXMLElement($xml);
-		$this->simplexml->registerXPathNamespace('saml2',     'urn:oasis:names:tc:SAML:2.0:assertion');
-		$this->simplexml->registerXPathNamespace('saml2meta', 'urn:oasis:names:tc:SAML:2.0:metadata');
-		$this->simplexml->registerXPathNamespace('ds',        'http://www.w3.org/2000/09/xmldsig#');
-		
-	}
-	
-	public static function fromSimpleXMLElement(SimpleXMLElement $element) {
-		
-		// Traverse all existing namespaces in element
-		$namespaces = $element->getNamespaces();
-		foreach ($namespaces AS $prefix => $ns) {
-			$element[(($prefix === '') ? 'xmlns' : 'xmlns:' . $prefix)] = $ns;
-		}
-		
-		/* Create a new parser with the xml document where the namespace definitions
-		 * are added.
-		 */
-		$parser = new SimpleSAML_XML_Parser($element->asXML());
-		return $parser;
-		
-	}
-	
-	public function getValueDefault($xpath, $defvalue) {
-		try {
-			return $this->getValue($xpath, true);
-		} catch (Exception $e) {
-			return $defvalue;
-		}
-	}
-	
-	public function getValue($xpath, $required = false) {
-		
-		$result = $this->simplexml->xpath($xpath);
-		if (! $result or !is_array($result)) {
-			if ($required) throw new Exception('Could not get value from XML document using the following XPath expression: ' . $xpath);
-				else return null;
-		}
-		return (string) $result[0];
-	}
-	
-	public function getValueAlternatives(array $xpath, $required = false) {
-		foreach ($xpath AS $x) {
-			$seek = $this->getValue($x);
-			if ($seek) return $seek;
-		}
-		if ($required) throw new Exception('Could not get value from XML document using multiple alternative XPath expressions.');
-			else return null;
-	}
-	
+class Parser
+{
+    public $simplexml = null;
+
+    public function __construct($xml)
+    {
+        ;
+        $this->simplexml = new \SimpleXMLElement($xml);
+        $this->simplexml->registerXPathNamespace('saml2', 'urn:oasis:names:tc:SAML:2.0:assertion');
+        $this->simplexml->registerXPathNamespace('saml2meta', 'urn:oasis:names:tc:SAML:2.0:metadata');
+        $this->simplexml->registerXPathNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#');
+    }
+    
+    public static function fromSimpleXMLElement(\SimpleXMLElement $element)
+    {
+        
+        // Traverse all existing namespaces in element
+        $namespaces = $element->getNamespaces();
+        foreach ($namespaces as $prefix => $ns) {
+            $element[(($prefix === '') ? 'xmlns' : 'xmlns:' . $prefix)] = $ns;
+        }
+        
+        /* Create a new parser with the xml document where the namespace definitions
+         * are added.
+         */
+        $parser = new Parser($element->asXML());
+        return $parser;
+    }
+    
+    public function getValueDefault($xpath, $defvalue)
+    {
+        try {
+            return $this->getValue($xpath, true);
+        } catch (\Exception $e) {
+            return $defvalue;
+        }
+    }
+    
+    public function getValue($xpath, $required = false)
+    {
+        $result = $this->simplexml->xpath($xpath);
+        if (! $result or !is_array($result)) {
+            if ($required) {
+                throw new \Exception('Could not get value from XML document using the following XPath expression: ' . $xpath);
+            } else {
+                return null;
+            }
+        }
+        return (string) $result[0];
+    }
+    
+    public function getValueAlternatives(array $xpath, $required = false)
+    {
+        foreach ($xpath as $x) {
+            $seek = $this->getValue($x);
+            if ($seek) {
+                return $seek;
+            }
+        }
+        if ($required) {
+            throw new \Exception('Could not get value from XML document using multiple alternative XPath expressions.');
+        } else {
+            return null;
+        }
+    }
 }
diff --git a/lib/SimpleSAML/XML/Shib13/AuthnRequest.php b/lib/SimpleSAML/XML/Shib13/AuthnRequest.php
index aaecb2e9f75b6ed54b625afcd0dfd3375f42d235..f52fea212cfa4f379ccb94e6676d1a7abd93cd70 100644
--- a/lib/SimpleSAML/XML/Shib13/AuthnRequest.php
+++ b/lib/SimpleSAML/XML/Shib13/AuthnRequest.php
@@ -1,46 +1,53 @@
 <?php
 
 /**
- * The Shibboleth 1.3 Authentication Request. Not part of SAML 1.1, 
+ * The Shibboleth 1.3 Authentication Request. Not part of SAML 1.1,
  * but an extension using query paramters no XML.
  *
  * @author Andreas Ã…kre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
  * @package SimpleSAMLphp
  */
-class SimpleSAML_XML_Shib13_AuthnRequest {
 
-	private $issuer = null;
-	private $relayState = null;
+namespace SimpleSAML\XML\Shib13;
 
-	public function setRelayState($relayState) {
-		$this->relayState = $relayState;
-	}
-	
-	public function getRelayState() {
-		return $this->relayState;
-	}
-	
-	public function setIssuer($issuer) {
-		$this->issuer = $issuer;
-	}
-	public function getIssuer() {
-		return $this->issuer;
-	}
+class AuthnRequest
+{
+    private $issuer = null;
+    private $relayState = null;
 
-	public function createRedirect($destination, $shire) {
-		$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
-		$idpmetadata = $metadata->getMetaDataConfig($destination, 'shib13-idp-remote');
+    public function setRelayState($relayState)
+    {
+        $this->relayState = $relayState;
+    }
+    
+    public function getRelayState()
+    {
+        return $this->relayState;
+    }
+    
+    public function setIssuer($issuer)
+    {
+        $this->issuer = $issuer;
+    }
+    public function getIssuer()
+    {
+        return $this->issuer;
+    }
 
-		$desturl = $idpmetadata->getDefaultEndpoint('SingleSignOnService', array('urn:mace:shibboleth:1.0:profiles:AuthnRequest'));
-		$desturl = $desturl['Location'];
+    public function createRedirect($destination, $shire)
+    {
+        $metadata = \SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+        $idpmetadata = $metadata->getMetaDataConfig($destination, 'shib13-idp-remote');
 
-		$target = $this->getRelayState();
-		
-		$url = $desturl . '?' .
-	    	'providerId=' . urlencode($this->getIssuer()) .
-		    '&shire=' . urlencode($shire) .
-		    (isset($target) ? '&target=' . urlencode($target) : '');
-		return $url;
-	}
+        $desturl = $idpmetadata->getDefaultEndpoint('SingleSignOnService', array('urn:mace:shibboleth:1.0:profiles:AuthnRequest'));
+        $desturl = $desturl['Location'];
 
+        $target = $this->getRelayState();
+        
+        $url = $desturl . '?' .
+            'providerId=' . urlencode($this->getIssuer()) .
+            '&shire=' . urlencode($shire) .
+            (isset($target) ? '&target=' . urlencode($target) : '');
+        return $url;
+    }
 }
diff --git a/lib/SimpleSAML/XML/Shib13/AuthnResponse.php b/lib/SimpleSAML/XML/Shib13/AuthnResponse.php
index ce0dbaa4e9161100f7426bfe5ce45e1959f1a623..b023fa4192d9bf88f0e9c2084c2f8325ebb6130b 100644
--- a/lib/SimpleSAML/XML/Shib13/AuthnResponse.php
+++ b/lib/SimpleSAML/XML/Shib13/AuthnResponse.php
@@ -1,360 +1,372 @@
 <?php
- 
+
 /**
  * A Shibboleth 1.3 authentication response.
  *
  * @author Andreas Ã…kre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
  * @package SimpleSAMLphp
  */
-class SimpleSAML_XML_Shib13_AuthnResponse {
-
-	/**
-	 * This variable contains an XML validator for this message.
-	 */
-	private $validator = null;
-
-
-	/**
-	 * Whether this response was validated by some external means (e.g. SSL).
-	 *
-	 * @var bool
-	 */
-	private $messageValidated = FALSE;
-
-
-	const SHIB_PROTOCOL_NS = 'urn:oasis:names:tc:SAML:1.0:protocol';
-	const SHIB_ASSERT_NS = 'urn:oasis:names:tc:SAML:1.0:assertion';
-
-
-	/**
-	 * The DOMDocument which represents this message.
-	 *
-	 * @var DOMDocument
-	 */
-	private $dom;
-
-	/**
-	 * The relaystate which is associated with this response.
-	 *
-	 * @var string|NULL
-	 */
-	private $relayState = null;
-
-
-	/**
-	 * Set whether this message was validated externally.
-	 *
-	 * @param bool $messageValidated  TRUE if the message is already validated, FALSE if not.
-	 */
-	public function setMessageValidated($messageValidated) {
-		assert('is_bool($messageValidated)');
-
-		$this->messageValidated = $messageValidated;
-	}
-
-
-	public function setXML($xml) {
-		assert('is_string($xml)');
-
-		try {
-			$this->dom = \SAML2\DOMDocumentFactory::fromString(str_replace ("\r", "", $xml));
-		} catch(\Exception $e) {
-			throw new Exception('Unable to parse AuthnResponse XML.');
-		}
-	}
-
-	public function setRelayState($relayState) {
-		$this->relayState = $relayState;
-	}
-
-	public function getRelayState() {
-		return $this->relayState;
-	}
-
-	public function validate() {
-		assert('$this->dom instanceof DOMDocument');
-
-		if ($this->messageValidated) {
-			// This message was validated externally
-			return TRUE;
-		}
-
-		// Validate the signature
-		$this->validator = new SimpleSAML_XML_Validator($this->dom, array('ResponseID', 'AssertionID'));
-
-		// Get the issuer of the response
-		$issuer = $this->getIssuer();
-
-		// Get the metadata of the issuer
-		$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
-		$md = $metadata->getMetaDataConfig($issuer, 'shib13-idp-remote');
-
-		$publicKeys = $md->getPublicKeys('signing');
-		if ($publicKeys !== NULL) {
-			$certFingerprints = array();
-			foreach ($publicKeys as $key) {
-				if ($key['type'] !== 'X509Certificate') {
-					continue;
-				}
-				$certFingerprints[] = sha1(base64_decode($key['X509Certificate']));
-			}
-			$this->validator->validateFingerprint($certFingerprints);
-		} elseif ($md->hasValue('certFingerprint')) {
-			$certFingerprints = $md->getArrayizeString('certFingerprint');
-
-			// Validate the fingerprint
-			$this->validator->validateFingerprint($certFingerprints);
-		} elseif ($md->hasValue('caFile')) {
-			// Validate against CA
-			$this->validator->validateCA(\SimpleSAML\Utils\Config::getCertPath($md->getString('caFile')));
-		} else {
-			throw new SimpleSAML_Error_Exception('Missing certificate in Shibboleth 1.3 IdP Remote metadata for identity provider [' . $issuer . '].');
-		}
-
-		return true;
-	}
-
-
-	/* Checks if the given node is validated by the signature on this response.
-	 *
-	 * Returns:
-	 *  TRUE if the node is validated or FALSE if not.
-	 */
-	private function isNodeValidated($node) {
-
-		if ($this->messageValidated) {
-			// This message was validated externally
-			return TRUE;
-		}
-
-		if($this->validator === NULL) {
-			return FALSE;
-		}
-
-		// Convert the node to a DOM node if it is an element from SimpleXML
-		if($node instanceof SimpleXMLElement) {
-			$node = dom_import_simplexml($node);
-		}
-
-		assert('$node instanceof DOMNode');
-
-		return $this->validator->isNodeValidated($node);
-	}
-
-
-	/**
-	 * This function runs an xPath query on this authentication response.
-	 *
-	 * @param $query  The query which should be run.
-	 * @param $node   The node which this query is relative to. If this node is NULL (the default)
-	 *                then the query will be relative to the root of the response.
-	 */
-	private function doXPathQuery($query, $node = NULL) {
-		assert('is_string($query)');
-		assert('$this->dom instanceof DOMDocument');
-
-		if($node === NULL) {
-			$node = $this->dom->documentElement;
-		}
-
-		assert('$node instanceof DOMNode');
-
-		$xPath = new DOMXpath($this->dom);
-		$xPath->registerNamespace('shibp', self::SHIB_PROTOCOL_NS);
-		$xPath->registerNamespace('shib', self::SHIB_ASSERT_NS);
-
-		return $xPath->query($query, $node);
-	}
-
-	/**
-	 * Retrieve the session index of this response.
-	 *
-	 * @return string|NULL  The session index of this response.
-	 */
-	function getSessionIndex() {
-		assert('$this->dom instanceof DOMDocument');
-
-		$query = '/shibp:Response/shib:Assertion/shib:AuthnStatement';
-		$nodelist = $this->doXPathQuery($query);
-		if ($node = $nodelist->item(0)) {
-			return $node->getAttribute('SessionIndex');
-		}
-
-		return NULL;
-	}
-
-	
-	public function getAttributes() {
-
-		$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
-		$md = $metadata->getMetadata($this->getIssuer(), 'shib13-idp-remote');
-		$base64 = isset($md['base64attributes']) ? $md['base64attributes'] : false;
-
-		if (! ($this->dom instanceof DOMDocument) ) {
-			return array();
-		}
-
-		$attributes = array();
-
-		$assertions = $this->doXPathQuery('/shibp:Response/shib:Assertion');
-
-		foreach ($assertions AS $assertion) {
-
-			if(!$this->isNodeValidated($assertion)) {
-				throw new Exception('Shib13 AuthnResponse contained an unsigned assertion.');
-			}
-
-			$conditions = $this->doXPathQuery('shib:Conditions', $assertion);
-			if ($conditions && $conditions->length > 0) {
-				$condition = $conditions->item(0);
-
-				$start = $condition->getAttribute('NotBefore');
-				$end = $condition->getAttribute('NotOnOrAfter');
-
-				if ($start && $end) {
-					if (!self::checkDateConditions($start, $end)) {
-						error_log('Date check failed ... (from ' . $start . ' to ' . $end . ')');
-						continue;
-					}
-				}
-			}
-
-			$attribute_nodes = $this->doXPathQuery('shib:AttributeStatement/shib:Attribute/shib:AttributeValue', $assertion);
-			foreach($attribute_nodes as $attribute) {
-
-				$value = $attribute->textContent;
-				$name = $attribute->parentNode->getAttribute('AttributeName');
-
-				if ($attribute->hasAttribute('Scope')) {
-					$scopePart = '@' . $attribute->getAttribute('Scope');
-				} else {
-					$scopePart = '';
-				}
-
-				if(!is_string($name)) {
-					throw new Exception('Shib13 Attribute node without an AttributeName.');
-				}
-
-				if(!array_key_exists($name, $attributes)) {
-					$attributes[$name] = array();
-				}
-
-				if ($base64) {
-					$encodedvalues = explode('_', $value);
-					foreach($encodedvalues AS $v) {
-						$attributes[$name][] = base64_decode($v) . $scopePart;
-					}
-				} else {
-					$attributes[$name][] = $value . $scopePart;
-				}
-			}
-		}
-
-		return $attributes;
-	}
-
-	
-	public function getIssuer() {
-
-		$query = '/shibp:Response/shib:Assertion/@Issuer';
-		$nodelist = $this->doXPathQuery($query);
-
-		if ($attr = $nodelist->item(0)) {
-			return $attr->value;
-		} else {
-			throw new Exception('Could not find Issuer field in Authentication response');
-		}
-
-	}
-
-	public function getNameID() {
-
-		$nameID = array();
-
-		$query = '/shibp:Response/shib:Assertion/shib:AuthenticationStatement/shib:Subject/shib:NameIdentifier';
-		$nodelist = $this->doXPathQuery($query);
-
-		if ($node = $nodelist->item(0)) {
-			$nameID["Value"] = $node->nodeValue;
-			$nameID["Format"] = $node->getAttribute('Format');
-		}
-
-		return $nameID;
-	}
-
-
-	/**
-	 * Build a authentication response.
-	 *
-	 * @param array $idp  Metadata for the IdP the response is sent from.
-	 * @param array $sp  Metadata for the SP the response is sent to.
-	 * @param string $shire  The endpoint on the SP the response is sent to.
-	 * @param array|NULL $attributes  The attributes which should be included in the response.
-	 * @return string  The response.
-	 */
-	public function generate(SimpleSAML_Configuration $idp, SimpleSAML_Configuration $sp, $shire, $attributes) {
-		assert('is_string($shire)');
-		assert('$attributes === NULL || is_array($attributes)');
-
-		if ($sp->hasValue('scopedattributes')) {
-			$scopedAttributes = $sp->getArray('scopedattributes');
-		} elseif ($idp->hasValue('scopedattributes')) {
-			$scopedAttributes = $idp->getArray('scopedattributes');
-		} else {
-			$scopedAttributes = array();
-		}
-
-		$id = SimpleSAML\Utils\Random::generateID();
-		
-		$issueInstant = SimpleSAML\Utils\Time::generateTimestamp();
-		
-		// 30 seconds timeskew back in time to allow differing clocks
-		$notBefore = SimpleSAML\Utils\Time::generateTimestamp(time() - 30);
-		
-		
-		$assertionExpire = SimpleSAML\Utils\Time::generateTimestamp(time() + 60 * 5);# 5 minutes
-		$assertionid = SimpleSAML\Utils\Random::generateID();
-
-		$spEntityId = $sp->getString('entityid');
-
-		$audience = $sp->getString('audience', $spEntityId);
-		$base64 = $sp->getBoolean('base64attributes', FALSE);
-
-		$namequalifier = $sp->getString('NameQualifier', $spEntityId);
-		$nameid = SimpleSAML\Utils\Random::generateID();
-		$subjectNode =
-			'<Subject>' .
-			'<NameIdentifier' .
-			' Format="urn:mace:shibboleth:1.0:nameIdentifier"' .
-			' NameQualifier="' . htmlspecialchars($namequalifier) . '"' .
-			'>' .
-			htmlspecialchars($nameid) .
-			'</NameIdentifier>' .
-			'<SubjectConfirmation>' .
-			'<ConfirmationMethod>' .
-			'urn:oasis:names:tc:SAML:1.0:cm:bearer' .
-			'</ConfirmationMethod>' .
-			'</SubjectConfirmation>' .
-			'</Subject>';
-
-		$encodedattributes = '';
-
-		if (is_array($attributes)) {
-
-			$encodedattributes .= '<AttributeStatement>';
-			$encodedattributes .= $subjectNode;
-
-			foreach ($attributes AS $name => $value) {
-				$encodedattributes .= $this->enc_attribute($name, $value, $base64, $scopedAttributes);
-			}
-
-			$encodedattributes .= '</AttributeStatement>';
-		}
-
-		/*
-		 * The SAML 1.1 response message
-		 */
-		$response = '<Response xmlns="urn:oasis:names:tc:SAML:1.0:protocol"
+
+namespace SimpleSAML\XML\Shib13;
+
+use SAML2\DOMDocumentFactory;
+use SAML2\Utils;
+use SimpleSAML\Utils\Config;
+use SimpleSAML\Utils\Random;
+use SimpleSAML\Utils\Time;
+use SimpleSAML\XML\Validator;
+
+class AuthnResponse
+{
+
+    /**
+     * @var \SimpleSAML\XML\Validator This variable contains an XML validator for this message.
+     */
+    private $validator = null;
+
+
+    /**
+     * @var bool Whether this response was validated by some external means (e.g. SSL).
+     */
+    private $messageValidated = false;
+
+
+    const SHIB_PROTOCOL_NS = 'urn:oasis:names:tc:SAML:1.0:protocol';
+    const SHIB_ASSERT_NS = 'urn:oasis:names:tc:SAML:1.0:assertion';
+
+
+    /**
+     * @var \DOMDocument The DOMDocument which represents this message.
+     */
+    private $dom;
+
+    /**
+     * @var string|null The relaystate which is associated with this response.
+     */
+    private $relayState = null;
+
+
+    /**
+     * Set whether this message was validated externally.
+     *
+     * @param bool $messageValidated  TRUE if the message is already validated, FALSE if not.
+     */
+    public function setMessageValidated($messageValidated)
+    {
+        assert('is_bool($messageValidated)');
+
+        $this->messageValidated = $messageValidated;
+    }
+
+
+    public function setXML($xml)
+    {
+        assert('is_string($xml)');
+
+        try {
+            $this->dom = DOMDocumentFactory::fromString(str_replace("\r", "", $xml));
+        } catch (\Exception $e) {
+            throw new \Exception('Unable to parse AuthnResponse XML.');
+        }
+    }
+
+    public function setRelayState($relayState)
+    {
+        $this->relayState = $relayState;
+    }
+
+    public function getRelayState()
+    {
+        return $this->relayState;
+    }
+
+    public function validate()
+    {
+        assert('$this->dom instanceof DOMDocument');
+
+        if ($this->messageValidated) {
+            // This message was validated externally
+            return true;
+        }
+
+        // Validate the signature
+        $this->validator = new Validator($this->dom, array('ResponseID', 'AssertionID'));
+
+        // Get the issuer of the response
+        $issuer = $this->getIssuer();
+
+        // Get the metadata of the issuer
+        $metadata = \SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+        $md = $metadata->getMetaDataConfig($issuer, 'shib13-idp-remote');
+
+        $publicKeys = $md->getPublicKeys('signing');
+        if ($publicKeys !== null) {
+            $certFingerprints = array();
+            foreach ($publicKeys as $key) {
+                if ($key['type'] !== 'X509Certificate') {
+                    continue;
+                }
+                $certFingerprints[] = sha1(base64_decode($key['X509Certificate']));
+            }
+            $this->validator->validateFingerprint($certFingerprints);
+        } elseif ($md->hasValue('certFingerprint')) {
+            $certFingerprints = $md->getArrayizeString('certFingerprint');
+
+            // Validate the fingerprint
+            $this->validator->validateFingerprint($certFingerprints);
+        } elseif ($md->hasValue('caFile')) {
+            // Validate against CA
+            $this->validator->validateCA(Config::getCertPath($md->getString('caFile')));
+        } else {
+            throw new \SimpleSAML_Error_Exception('Missing certificate in Shibboleth 1.3 IdP Remote metadata for identity provider [' . $issuer . '].');
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Checks if the given node is validated by the signature on this response.
+     *
+     * @param \DOMElement $node Node to be validated.
+     * @return bool TRUE if the node is validated or FALSE if not.
+     */
+    private function isNodeValidated($node)
+    {
+        if ($this->messageValidated) {
+            // This message was validated externally
+            return true;
+        }
+
+        if ($this->validator === null) {
+            return false;
+        }
+
+        // Convert the node to a DOM node if it is an element from SimpleXML
+        if ($node instanceof \SimpleXMLElement) {
+            $node = dom_import_simplexml($node);
+        }
+
+        assert('$node instanceof DOMNode');
+
+        return $this->validator->isNodeValidated($node);
+    }
+
+
+    /**
+     * This function runs an xPath query on this authentication response.
+     *
+     * @param string $query   The query which should be run.
+     * @param \DOMNode $node  The node which this query is relative to. If this node is NULL (the default)
+     *                        then the query will be relative to the root of the response.
+     * @return \DOMNodeList
+     */
+    private function doXPathQuery($query, $node = null)
+    {
+        assert('is_string($query)');
+        assert('$this->dom instanceof DOMDocument');
+
+        if ($node === null) {
+            $node = $this->dom->documentElement;
+        }
+
+        assert('$node instanceof DOMNode');
+
+        $xPath = new \DOMXpath($this->dom);
+        $xPath->registerNamespace('shibp', self::SHIB_PROTOCOL_NS);
+        $xPath->registerNamespace('shib', self::SHIB_ASSERT_NS);
+
+        return $xPath->query($query, $node);
+    }
+
+    /**
+     * Retrieve the session index of this response.
+     *
+     * @return string|null  The session index of this response.
+     */
+    public function getSessionIndex()
+    {
+        assert('$this->dom instanceof DOMDocument');
+
+        $query = '/shibp:Response/shib:Assertion/shib:AuthnStatement';
+        $nodelist = $this->doXPathQuery($query);
+        if ($node = $nodelist->item(0)) {
+            return $node->getAttribute('SessionIndex');
+        }
+
+        return null;
+    }
+
+    
+    public function getAttributes()
+    {
+        $metadata = \SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+        $md = $metadata->getMetadata($this->getIssuer(), 'shib13-idp-remote');
+        $base64 = isset($md['base64attributes']) ? $md['base64attributes'] : false;
+
+        if (! ($this->dom instanceof \DOMDocument)) {
+            return array();
+        }
+
+        $attributes = array();
+
+        $assertions = $this->doXPathQuery('/shibp:Response/shib:Assertion');
+
+        foreach ($assertions as $assertion) {
+            if (!$this->isNodeValidated($assertion)) {
+                throw new \Exception('Shib13 AuthnResponse contained an unsigned assertion.');
+            }
+
+            $conditions = $this->doXPathQuery('shib:Conditions', $assertion);
+            if ($conditions && $conditions->length > 0) {
+                $condition = $conditions->item(0);
+
+                $start = $condition->getAttribute('NotBefore');
+                $end = $condition->getAttribute('NotOnOrAfter');
+
+                if ($start && $end) {
+                    if (!self::checkDateConditions($start, $end)) {
+                        error_log('Date check failed ... (from ' . $start . ' to ' . $end . ')');
+                        continue;
+                    }
+                }
+            }
+
+            $attribute_nodes = $this->doXPathQuery('shib:AttributeStatement/shib:Attribute/shib:AttributeValue', $assertion);
+            /** @var \DOMElement $attribute */
+            foreach ($attribute_nodes as $attribute) {
+                $value = $attribute->textContent;
+                $name = $attribute->parentNode->getAttribute('AttributeName');
+
+                if ($attribute->hasAttribute('Scope')) {
+                    $scopePart = '@' . $attribute->getAttribute('Scope');
+                } else {
+                    $scopePart = '';
+                }
+
+                if (!is_string($name)) {
+                    throw new \Exception('Shib13 Attribute node without an AttributeName.');
+                }
+
+                if (!array_key_exists($name, $attributes)) {
+                    $attributes[$name] = array();
+                }
+
+                if ($base64) {
+                    $encodedvalues = explode('_', $value);
+                    foreach ($encodedvalues as $v) {
+                        $attributes[$name][] = base64_decode($v) . $scopePart;
+                    }
+                } else {
+                    $attributes[$name][] = $value . $scopePart;
+                }
+            }
+        }
+
+        return $attributes;
+    }
+
+    
+    public function getIssuer()
+    {
+        $query = '/shibp:Response/shib:Assertion/@Issuer';
+        $nodelist = $this->doXPathQuery($query);
+
+        if ($attr = $nodelist->item(0)) {
+            return $attr->value;
+        } else {
+            throw new \Exception('Could not find Issuer field in Authentication response');
+        }
+    }
+
+    public function getNameID()
+    {
+        $nameID = array();
+
+        $query = '/shibp:Response/shib:Assertion/shib:AuthenticationStatement/shib:Subject/shib:NameIdentifier';
+        $nodelist = $this->doXPathQuery($query);
+
+        if ($node = $nodelist->item(0)) {
+            $nameID["Value"] = $node->nodeValue;
+            $nameID["Format"] = $node->getAttribute('Format');
+        }
+
+        return $nameID;
+    }
+
+
+    /**
+     * Build a authentication response.
+     *
+     * @param \SimpleSAML_Configuration $idp Metadata for the IdP the response is sent from.
+     * @param \SimpleSAML_Configuration $sp Metadata for the SP the response is sent to.
+     * @param string $shire The endpoint on the SP the response is sent to.
+     * @param array|null $attributes The attributes which should be included in the response.
+     * @return string The response.
+     */
+    public function generate(\SimpleSAML_Configuration $idp, \SimpleSAML_Configuration $sp, $shire, $attributes)
+    {
+        assert('is_string($shire)');
+        assert('$attributes === NULL || is_array($attributes)');
+
+        if ($sp->hasValue('scopedattributes')) {
+            $scopedAttributes = $sp->getArray('scopedattributes');
+        } elseif ($idp->hasValue('scopedattributes')) {
+            $scopedAttributes = $idp->getArray('scopedattributes');
+        } else {
+            $scopedAttributes = array();
+        }
+
+        $id = Random::generateID();
+        
+        $issueInstant = Time::generateTimestamp();
+        
+        // 30 seconds timeskew back in time to allow differing clocks
+        $notBefore = Time::generateTimestamp(time() - 30);
+        
+        
+        $assertionExpire = Time::generateTimestamp(time() + 60 * 5);# 5 minutes
+        $assertionid = Random::generateID();
+
+        $spEntityId = $sp->getString('entityid');
+
+        $audience = $sp->getString('audience', $spEntityId);
+        $base64 = $sp->getBoolean('base64attributes', false);
+
+        $namequalifier = $sp->getString('NameQualifier', $spEntityId);
+        $nameid = Random::generateID();
+        $subjectNode =
+            '<Subject>' .
+            '<NameIdentifier' .
+            ' Format="urn:mace:shibboleth:1.0:nameIdentifier"' .
+            ' NameQualifier="' . htmlspecialchars($namequalifier) . '"' .
+            '>' .
+            htmlspecialchars($nameid) .
+            '</NameIdentifier>' .
+            '<SubjectConfirmation>' .
+            '<ConfirmationMethod>' .
+            'urn:oasis:names:tc:SAML:1.0:cm:bearer' .
+            '</ConfirmationMethod>' .
+            '</SubjectConfirmation>' .
+            '</Subject>';
+
+        $encodedattributes = '';
+
+        if (is_array($attributes)) {
+            $encodedattributes .= '<AttributeStatement>';
+            $encodedattributes .= $subjectNode;
+
+            foreach ($attributes as $name => $value) {
+                $encodedattributes .= $this->enc_attribute($name, $value, $base64, $scopedAttributes);
+            }
+
+            $encodedattributes .= '</AttributeStatement>';
+        }
+
+        /*
+         * The SAML 1.1 response message
+         */
+        $response = '<Response xmlns="urn:oasis:names:tc:SAML:1.0:protocol"
     xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"
     xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" IssueInstant="' . $issueInstant. '"
@@ -373,96 +385,94 @@ class SimpleSAML_XML_Shib13_AuthnResponse {
         </Conditions>
         <AuthenticationStatement AuthenticationInstant="' . $issueInstant. '"
             AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:unspecified">' .
-			$subjectNode . '
+            $subjectNode . '
         </AuthenticationStatement>
         ' . $encodedattributes . '
     </Assertion>
 </Response>';
 
-		return $response;
-	}
-
-
-	/**
-	 * Format a shib13 attribute.
-	 *
-	 * @param string $name  Name of the attribute.
-	 * @param array $values  Values of the attribute (as an array of strings).
-	 * @param bool $base64  Whether the attriubte values should be base64-encoded.
-	 * @param array $scopedAttributes  Array of attributes names which are scoped.
-	 * @return string  The attribute encoded as an XML-string.
-	 */
-	private function enc_attribute($name, $values, $base64, $scopedAttributes) {
-		assert('is_string($name)');
-		assert('is_array($values)');
-		assert('is_bool($base64)');
-		assert('is_array($scopedAttributes)');
-
-		if (in_array($name, $scopedAttributes, TRUE)) {
-			$scoped = TRUE;
-		} else {
-			$scoped = FALSE;
-		}
-
-		$attr = '<Attribute AttributeName="' . htmlspecialchars($name) . '" AttributeNamespace="urn:mace:shibboleth:1.0:attributeNamespace:uri">';
-		foreach ($values AS $value) {
-
-			$scopePart = '';
-			if ($scoped) {
-				$tmp = explode('@', $value, 2);
-				if (count($tmp) === 2) {
-					$value = $tmp[0];
-					$scopePart = ' Scope="' . htmlspecialchars($tmp[1]) . '"';
-				}
-			}
-
-			if ($base64) {
-				$value = base64_encode($value);
-			}
-
-			$attr .= '<AttributeValue' . $scopePart . '>' . htmlspecialchars($value) . '</AttributeValue>';
-		}
-		$attr .= '</Attribute>';
-
-		return $attr;
-	}
-
-	/**
-	 * Check if we are currently between the given date & time conditions.
-	 *
-	 * Note that this function allows a 10-minute leap from the initial time as marked by $start.
-	 *
-	 * @param string|null $start A SAML2 timestamp marking the start of the period to check. Defaults to null, in which
-	 *     case there's no limitations in the past.
-	 * @param string|null $end A SAML2 timestamp marking the end of the period to check. Defaults to null, in which
-	 *     case there's no limitations in the future.
-	 *
-	 * @return bool True if the current time belongs to the period specified by $start and $end. False otherwise.
-	 *
-	 * @see \SAML2\Utils::xsDateTimeToTimestamp.
-	 *
-	 * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
-	 * @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
-	 */
-	protected static function checkDateConditions($start = null, $end = null)
-	{
-		$currentTime = time();
-
-		if (!empty($start)) {
-			$startTime = \SAML2\Utils::xsDateTimeToTimestamp($start);
-			// allow for a 10 minute difference in time
-			if (($startTime < 0) || (($startTime - 600) > $currentTime)) {
-				return false;
-			}
-		}
-		if (!empty($end)) {
-			$endTime = \SAML2\Utils::xsDateTimeToTimestamp($end);
-			if (($endTime < 0) || ($endTime <= $currentTime)) {
-				return false;
-			}
-		}
-		return true;
-	}
-
+        return $response;
+    }
+
+
+    /**
+     * Format a shib13 attribute.
+     *
+     * @param string $name  Name of the attribute.
+     * @param array $values  Values of the attribute (as an array of strings).
+     * @param bool $base64  Whether the attriubte values should be base64-encoded.
+     * @param array $scopedAttributes  Array of attributes names which are scoped.
+     * @return string  The attribute encoded as an XML-string.
+     */
+    private function enc_attribute($name, $values, $base64, $scopedAttributes)
+    {
+        assert('is_string($name)');
+        assert('is_array($values)');
+        assert('is_bool($base64)');
+        assert('is_array($scopedAttributes)');
+
+        if (in_array($name, $scopedAttributes, true)) {
+            $scoped = true;
+        } else {
+            $scoped = false;
+        }
+
+        $attr = '<Attribute AttributeName="' . htmlspecialchars($name) . '" AttributeNamespace="urn:mace:shibboleth:1.0:attributeNamespace:uri">';
+        foreach ($values as $value) {
+            $scopePart = '';
+            if ($scoped) {
+                $tmp = explode('@', $value, 2);
+                if (count($tmp) === 2) {
+                    $value = $tmp[0];
+                    $scopePart = ' Scope="' . htmlspecialchars($tmp[1]) . '"';
+                }
+            }
+
+            if ($base64) {
+                $value = base64_encode($value);
+            }
+
+            $attr .= '<AttributeValue' . $scopePart . '>' . htmlspecialchars($value) . '</AttributeValue>';
+        }
+        $attr .= '</Attribute>';
+
+        return $attr;
+    }
+
+    /**
+     * Check if we are currently between the given date & time conditions.
+     *
+     * Note that this function allows a 10-minute leap from the initial time as marked by $start.
+     *
+     * @param string|null $start A SAML2 timestamp marking the start of the period to check. Defaults to null, in which
+     *     case there's no limitations in the past.
+     * @param string|null $end A SAML2 timestamp marking the end of the period to check. Defaults to null, in which
+     *     case there's no limitations in the future.
+     *
+     * @return bool True if the current time belongs to the period specified by $start and $end. False otherwise.
+     *
+     * @see \SAML2\Utils::xsDateTimeToTimestamp.
+     *
+     * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
+     * @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
+     */
+    protected static function checkDateConditions($start = null, $end = null)
+    {
+        $currentTime = time();
+
+        if (!empty($start)) {
+            $startTime = Utils::xsDateTimeToTimestamp($start);
+            // allow for a 10 minute difference in time
+            if (($startTime < 0) || (($startTime - 600) > $currentTime)) {
+                return false;
+            }
+        }
+        if (!empty($end)) {
+            $endTime = Utils::xsDateTimeToTimestamp($end);
+            if (($endTime < 0) || ($endTime <= $currentTime)) {
+                return false;
+            }
+        }
+        return true;
+    }
 }
-
diff --git a/lib/SimpleSAML/XML/Signer.php b/lib/SimpleSAML/XML/Signer.php
index 04f176c3d56d7532966f36e8470753f023cfbcde..42ddfa350bb4ee9ec5b672b3e3b2c9b14b381151 100644
--- a/lib/SimpleSAML/XML/Signer.php
+++ b/lib/SimpleSAML/XML/Signer.php
@@ -8,261 +8,310 @@
  * @author Olav Morken, UNINETT AS.
  * @package SimpleSAMLphp
  */
-class SimpleSAML_XML_Signer {
-
-
-	/**
-	 * The name of the ID attribute.
-	 */
-	private $idAttrName;
-
-	/**
-	 * The private key (as an XMLSecurityKey).
-	 */
-	private $privateKey;
-
-	/**
-	 * The certificate (as text).
-	 */
-	private $certificate;
-
-
-	/**
-	 * Extra certificates which should be included in the response.
-	 */
-	private $extraCertificates;
-
-
-	/**
-	 * Constructor for the metadata signer.
-	 *
-	 * You can pass an list of options as key-value pairs in the array. This allows you to initialize
-	 * a metadata signer in one call.
-	 *
-	 * The following keys are recognized:
-	 *  - privatekey       The file with the private key, relative to the cert-directory.
-	 *  - privatekey_pass  The passphrase for the private key.
-	 *  - certificate      The file with the certificate, relative to the cert-directory.
-	 *  - privatekey_array The private key, as an array returned from SimpleSAML_Utilities::loadPrivateKey.
-	 *  - publickey_array  The public key, as an array returned from SimpleSAML_Utilities::loadPublicKey.
-	 *  - id               The name of the ID attribute.
-	 *
-	 * @param $options  Associative array with options for the constructor. Defaults to an empty array.
-	 */
-	public function __construct($options = array()) {
-		assert('is_array($options)');
-
-		$this->idAttrName = FALSE;
-		$this->privateKey = FALSE;
-		$this->certificate = FALSE;
-		$this->extraCertificates = array();
-
-		if(array_key_exists('privatekey', $options)) {
-			$pass = NULL;
-			if(array_key_exists('privatekey_pass', $options)) {
-				$pass = $options['privatekey_pass'];
-			}
-
-			$this->loadPrivateKey($options['privatekey'], $pass);
-		}
-
-		if(array_key_exists('certificate', $options)) {
-			$this->loadCertificate($options['certificate']);
-		}
-
-		if (array_key_exists('privatekey_array', $options)) {
-			$this->loadPrivateKeyArray($options['privatekey_array']);
-		}
-
-		if (array_key_exists('publickey_array', $options)) {
-			$this->loadPublicKeyArray($options['publickey_array']);
-		}
-
-		if(array_key_exists('id', $options)) {
-			$this->setIdAttribute($options['id']);
-		}
-	}
-
-
-	/**
-	 * Set the private key from an array.
-	 *
-	 * This function loads the private key from an array matching what is returned
-	 * by SimpleSAML_Utilities::loadPrivateKey(...).
-	 *
-	 * @param array $privatekey  The private key.
-	 */
-	public function loadPrivateKeyArray($privatekey) {
-		assert('is_array($privatekey)');
-		assert('array_key_exists("PEM", $privatekey)');
-
-		$this->privateKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'private'));
-		if (array_key_exists('password', $privatekey)) {
-			$this->privateKey->passphrase = $privatekey['password'];
-		}
-		$this->privateKey->loadKey($privatekey['PEM'], FALSE);
-	}
-
-
-	/**
-	 * Set the private key.
-	 *
-	 * Will throw an exception if unable to load the private key.
-	 *
-	 * @param $file  The file which contains the private key. The path is assumed to be relative
-	 *               to the cert-directory.
-	 * @param $pass  The passphrase on the private key. Pass no value or NULL if the private key is unencrypted.
-	 */
-	public function loadPrivateKey($file, $pass = NULL) {
-		assert('is_string($file)');
-		assert('is_string($pass) || is_null($pass)');
-
-		$keyFile = \SimpleSAML\Utils\Config::getCertPath($file);
-		if (!file_exists($keyFile)) {
-			throw new Exception('Could not find private key file "' . $keyFile . '".');
-		}
-		$keyData = file_get_contents($keyFile);
-		if($keyData === FALSE) {
-			throw new Exception('Unable to read private key file "' . $keyFile . '".');
-		}
-
-		$privatekey = array('PEM' => $keyData);
-		if($pass !== NULL) {
-			$privatekey['password'] = $pass;
-		}
-		$this->loadPrivateKeyArray($privatekey);
-	}
-
-
-	/**
-	 * Set the public key / certificate we should include in the signature.
-	 *
-	 * This function loads the public key from an array matching what is returned
-	 * by SimpleSAML_Utilities::loadPublicKey(...).
-	 *
-	 * @param array $publickey  The public key.
-	 */
-	public function loadPublicKeyArray($publickey) {
-		assert('is_array($publickey)');
-
-		if (!array_key_exists('PEM', $publickey)) {
-			// We have a public key with only a fingerprint
-			throw new Exception('Tried to add a certificate fingerprint in a signature.');
-		}
-
-		// For now, we only assume that the public key is an X509 certificate
-		$this->certificate = $publickey['PEM'];
-	}
-
-
-	/**
-	 * Set the certificate we should include in the signature.
-	 *
-	 * If this function isn't called, no certificate will be included.
-	 * Will throw an exception if unable to load the certificate.
-	 *
-	 * @param $file  The file which contains the certificate. The path is assumed to be relative to
-	 *               the cert-directory.
-	 */
-	public function loadCertificate($file) {
-		assert('is_string($file)');
-
-		$certFile = \SimpleSAML\Utils\Config::getCertPath($file);
-		if (!file_exists($certFile)) {
-			throw new Exception('Could not find certificate file "' . $certFile . '".');
-		}
-
-		$this->certificate = file_get_contents($certFile);
-		if($this->certificate === FALSE) {
-			throw new Exception('Unable to read certificate file "' . $certFile . '".');
-		}
-	}
-
-
-	/**
-	 * Set the attribute name for the ID value.
-	 *
-	 * @param $idAttrName  The name of the attribute which contains the id.
-	 */
-	public function setIDAttribute($idAttrName) {
-		assert('is_string($idAttrName)');
-
-		$this->idAttrName = $idAttrName;
-	}
-
-
-	/**
-	 * Add an extra certificate to the certificate chain in the signature.
-	 *
-	 * Extra certificates will be added to the certificate chain in the order they
-	 * are added.
-	 *
-	 * @param $file  The file which contains the certificate, relative to the cert-directory.
-	 */
-	public function addCertificate($file) {
-		assert('is_string($file)');
-
-		$certFile = \SimpleSAML\Utils\Config::getCertPath($file);
-		if (!file_exists($certFile)) {
-			throw new Exception('Could not find extra certificate file "' . $certFile . '".');
-		}
-
-		$certificate = file_get_contents($certFile);
-		if($certificate === FALSE) {
-			throw new Exception('Unable to read extra certificate file "' . $certFile . '".');
-		}
-
-		$this->extraCertificates[] = $certificate;
-	}
-
-
-	/**
-	 * Signs the given DOMElement and inserts the signature at the given position.
-	 *
-	 * The private key must be set before calling this function.
-	 *
-	 * @param $node  The DOMElement we should generate a signature for.
-	 * @param $insertInto  The DOMElement we should insert the signature element into.
-	 * @param $insertBefore  The element we should insert the signature element before. Defaults to NULL,
-	 *                       in which case the signature will be appended to the element spesified in
-	 *                       $insertInto.
-	 */
-	public function sign($node, $insertInto, $insertBefore = NULL) {
-		assert('$node instanceof DOMElement');
-		assert('$insertInto instanceof DOMElement');
-		assert('is_null($insertBefore) || $insertBefore instanceof DOMElement ' .
-			'|| $insertBefore instanceof DOMComment || $insertBefore instanceof DOMText');
-
-		if($this->privateKey === FALSE) {
-			throw new Exception('Private key not set.');
-		}
-
-
-		$objXMLSecDSig = new XMLSecurityDSig();
-		$objXMLSecDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
-
-		$options = array();
-		if($this->idAttrName !== FALSE) {
-			$options['id_name'] = $this->idAttrName;
-		}
-
-		$objXMLSecDSig->addReferenceList(array($node), XMLSecurityDSig::SHA1,
-			array('http://www.w3.org/2000/09/xmldsig#enveloped-signature', XMLSecurityDSig::EXC_C14N),
-			$options);
-
-		$objXMLSecDSig->sign($this->privateKey);
-
-
-		if($this->certificate !== FALSE) {
-			// Add the certificate to the signature
-			$objXMLSecDSig->add509Cert($this->certificate, TRUE);
-		}
-
-		// Add extra certificates
-		foreach($this->extraCertificates as $certificate) {
-			$objXMLSecDSig->add509Cert($certificate, TRUE);
-		}
-
-		$objXMLSecDSig->insertSignature($insertInto, $insertBefore);
-	}
+
+namespace SimpleSAML\XML;
+
+use RobRichards\XMLSecLibs\XMLSecurityDSig;
+use RobRichards\XMLSecLibs\XMLSecurityKey;
+use SimpleSAML\Utils\Config;
+
+class Signer
+{
+
+
+    /**
+     * @var string The name of the ID attribute.
+     */
+    private $idAttrName;
+
+    /**
+     * @var XMLSecurityKey|bool  The private key (as an XMLSecurityKey).
+     */
+    private $privateKey;
+
+    /**
+     * @var string The certificate (as text).
+     */
+    private $certificate;
+
+
+    /**
+     * @var string Extra certificates which should be included in the response.
+     */
+    private $extraCertificates;
+
+
+    /**
+     * Constructor for the metadata signer.
+     *
+     * You can pass an list of options as key-value pairs in the array. This allows you to initialize
+     * a metadata signer in one call.
+     *
+     * The following keys are recognized:
+     *  - privatekey       The file with the private key, relative to the cert-directory.
+     *  - privatekey_pass  The passphrase for the private key.
+     *  - certificate      The file with the certificate, relative to the cert-directory.
+     *  - privatekey_array The private key, as an array returned from SimpleSAML_Utilities::loadPrivateKey.
+     *  - publickey_array  The public key, as an array returned from SimpleSAML_Utilities::loadPublicKey.
+     *  - id               The name of the ID attribute.
+     *
+     * @param array $options  Associative array with options for the constructor. Defaults to an empty array.
+     */
+    public function __construct($options = array())
+    {
+        assert('is_array($options)');
+
+        $this->idAttrName = false;
+        $this->privateKey = false;
+        $this->certificate = false;
+        $this->extraCertificates = array();
+
+        if (array_key_exists('privatekey', $options)) {
+            $pass = null;
+            if (array_key_exists('privatekey_pass', $options)) {
+                $pass = $options['privatekey_pass'];
+            }
+
+            $this->loadPrivateKey($options['privatekey'], $pass);
+        }
+
+        if (array_key_exists('certificate', $options)) {
+            $this->loadCertificate($options['certificate']);
+        }
+
+        if (array_key_exists('privatekey_array', $options)) {
+            $this->loadPrivateKeyArray($options['privatekey_array']);
+        }
+
+        if (array_key_exists('publickey_array', $options)) {
+            $this->loadPublicKeyArray($options['publickey_array']);
+        }
+
+        if (array_key_exists('id', $options)) {
+            $this->setIdAttribute($options['id']);
+        }
+    }
+
+
+    /**
+     * Set the private key from an array.
+     *
+     * This function loads the private key from an array matching what is returned
+     * by SimpleSAML_Utilities::loadPrivateKey(...).
+     *
+     * @param array $privatekey  The private key.
+     */
+    public function loadPrivateKeyArray($privatekey)
+    {
+        assert('is_array($privatekey)');
+        assert('array_key_exists("PEM", $privatekey)');
+
+        $this->privateKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'private'));
+        if (array_key_exists('password', $privatekey)) {
+            $this->privateKey->passphrase = $privatekey['password'];
+        }
+        $this->privateKey->loadKey($privatekey['PEM'], false);
+    }
+
+
+    /**
+     * Set the private key.
+     *
+     * Will throw an exception if unable to load the private key.
+     *
+     * @param string $file  The file which contains the private key. The path is assumed to be relative
+     *                      to the cert-directory.
+     * @param string|null $pass  The passphrase on the private key. Pass no value or NULL if the private
+     *                           key is unencrypted.
+     * @param bool $full_path  Whether the filename found in the configuration contains the
+     *                         full path to the private key or not. Default to false.
+     * @throws \Exception
+     */
+    public function loadPrivateKey($file, $pass = null, $full_path = false)
+    {
+        assert('is_string($file)');
+        assert('is_string($pass) || is_null($pass)');
+        assert('is_bool($full_path)');
+
+        if (!$full_path) {
+            $keyFile = Config::getCertPath($file);
+        } else {
+            $keyFile = $file;
+        }
+
+        if (!file_exists($keyFile)) {
+            throw new \Exception('Could not find private key file "' . $keyFile . '".');
+        }
+        $keyData = file_get_contents($keyFile);
+        if ($keyData === false) {
+            throw new \Exception('Unable to read private key file "' . $keyFile . '".');
+        }
+
+        $privatekey = array('PEM' => $keyData);
+        if ($pass !== null) {
+            $privatekey['password'] = $pass;
+        }
+        $this->loadPrivateKeyArray($privatekey);
+    }
+
+
+    /**
+     * Set the public key / certificate we should include in the signature.
+     *
+     * This function loads the public key from an array matching what is returned
+     * by SimpleSAML_Utilities::loadPublicKey(...).
+     *
+     * @param array $publickey The public key.
+     * @throws \Exception
+     */
+    public function loadPublicKeyArray($publickey)
+    {
+        assert('is_array($publickey)');
+
+        if (!array_key_exists('PEM', $publickey)) {
+            // We have a public key with only a fingerprint
+            throw new \Exception('Tried to add a certificate fingerprint in a signature.');
+        }
+
+        // For now, we only assume that the public key is an X509 certificate
+        $this->certificate = $publickey['PEM'];
+    }
+
+
+    /**
+     * Set the certificate we should include in the signature.
+     *
+     * If this function isn't called, no certificate will be included.
+     * Will throw an exception if unable to load the certificate.
+     *
+     * @param string $file  The file which contains the certificate. The path is assumed to be relative to
+     *                      the cert-directory.
+     * @param bool $full_path  Whether the filename found in the configuration contains the
+     *                         full path to the private key or not. Default to false.
+     * @throws \Exception
+     */
+    public function loadCertificate($file, $full_path = false)
+    {
+        assert('is_string($file)');
+        assert('is_bool($full_path)');
+
+        if (!$full_path) {
+            $certFile = Config::getCertPath($file);
+        } else {
+            $certFile = $file;
+        }
+
+        if (!file_exists($certFile)) {
+            throw new \Exception('Could not find certificate file "' . $certFile . '".');
+        }
+
+        $this->certificate = file_get_contents($certFile);
+        if ($this->certificate === false) {
+            throw new \Exception('Unable to read certificate file "' . $certFile . '".');
+        }
+    }
+
+
+    /**
+     * Set the attribute name for the ID value.
+     *
+     * @param string $idAttrName  The name of the attribute which contains the id.
+     */
+    public function setIDAttribute($idAttrName)
+    {
+        assert('is_string($idAttrName)');
+
+        $this->idAttrName = $idAttrName;
+    }
+
+
+    /**
+     * Add an extra certificate to the certificate chain in the signature.
+     *
+     * Extra certificates will be added to the certificate chain in the order they
+     * are added.
+     *
+     * @param string $file  The file which contains the certificate, relative to the cert-directory.
+     * @param bool $full_path  Whether the filename found in the configuration contains the
+     *                         full path to the private key or not. Default to false.
+     * @throws \Exception
+     */
+    public function addCertificate($file, $full_path = false)
+    {
+        assert('is_string($file)');
+        assert('is_bool($full_path)');
+
+        if (!$full_path) {
+            $certFile = Config::getCertPath($file);
+        } else {
+            $certFile = $file;
+        }
+
+        if (!file_exists($certFile)) {
+            throw new \Exception('Could not find extra certificate file "' . $certFile . '".');
+        }
+
+        $certificate = file_get_contents($certFile);
+        if ($certificate === false) {
+            throw new \Exception('Unable to read extra certificate file "' . $certFile . '".');
+        }
+
+        $this->extraCertificates[] = $certificate;
+    }
+
+
+    /**
+     * Signs the given DOMElement and inserts the signature at the given position.
+     *
+     * The private key must be set before calling this function.
+     *
+     * @param \DOMElement $node  The DOMElement we should generate a signature for.
+     * @param \DOMElement $insertInto  The DOMElement we should insert the signature element into.
+     * @param \DOMElement $insertBefore  The element we should insert the signature element before. Defaults to NULL,
+     *                                   in which case the signature will be appended to the element spesified in
+     *                                   $insertInto.
+     * @throws \Exception
+     */
+    public function sign($node, $insertInto, $insertBefore = null)
+    {
+        assert('$node instanceof DOMElement');
+        assert('$insertInto instanceof DOMElement');
+        assert('is_null($insertBefore) || $insertBefore instanceof DOMElement ' .
+            '|| $insertBefore instanceof DOMComment || $insertBefore instanceof DOMText');
+
+        if ($this->privateKey === false) {
+            throw new \Exception('Private key not set.');
+        }
+
+
+        $objXMLSecDSig = new XMLSecurityDSig();
+        $objXMLSecDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
+
+        $options = array();
+        if ($this->idAttrName !== false) {
+            $options['id_name'] = $this->idAttrName;
+        }
+
+        $objXMLSecDSig->addReferenceList(
+            array($node),
+            XMLSecurityDSig::SHA1,
+            array('http://www.w3.org/2000/09/xmldsig#enveloped-signature', XMLSecurityDSig::EXC_C14N),
+            $options
+        );
+
+        $objXMLSecDSig->sign($this->privateKey);
+
+
+        if ($this->certificate !== false) {
+            // Add the certificate to the signature
+            $objXMLSecDSig->add509Cert($this->certificate, true);
+        }
+
+        // Add extra certificates
+        foreach ($this->extraCertificates as $certificate) {
+            $objXMLSecDSig->add509Cert($certificate, true);
+        }
+
+        $objXMLSecDSig->insertSignature($insertInto, $insertBefore);
+    }
 }
diff --git a/lib/SimpleSAML/XML/Validator.php b/lib/SimpleSAML/XML/Validator.php
index be283c0775adf99e214971e93896283ae5e48f65..3cb6df9cf551138f52ff73d9e04d99334a03fb9b 100644
--- a/lib/SimpleSAML/XML/Validator.php
+++ b/lib/SimpleSAML/XML/Validator.php
@@ -3,420 +3,442 @@
 /**
  * This class implements helper functions for XML validation.
  *
- * @author Olav Morken, UNINETT AS. 
+ * @author Olav Morken, UNINETT AS.
  * @package SimpleSAMLphp
  */
-class SimpleSAML_XML_Validator {
-
-	/**
-	 * This variable contains the X509 certificate the XML document
-	 * was signed with, or NULL if it wasn't signed with an X509 certificate.
-	 */
-	private $x509Certificate;
-
-	/**
-	 * This variable contains the nodes which are signed.
-	 */
-	private $validNodes = null;
-
-
-	/**
-	 * This function initializes the validator.
-	 *
-	 * This function accepts an optional parameter $publickey, which is the public key
-	 * or certificate which should be used to validate the signature. This parameter can
-	 * take the following values:
-	 * - NULL/FALSE: No validation will be performed. This is the default.
-	 * - A string: Assumed to be a PEM-encoded certificate / public key.
-	 * - An array: Assumed to be an array returned by SimpleSAML_Utilities::loadPublicKey.
-	 *
-	 * @param DOMNode $xmlNode  The XML node which contains the Signature element.
-	 * @param string|array $idAttribute  The ID attribute which is used in node references. If
-	 *          this attribute is NULL (the default), then we will use whatever is the default
-	 *          ID. Can be eigther a string with one value, or an array with multiple ID
-	 *          attrbute names.
-	 * @param array $publickey  The public key / certificate which should be used to validate the XML node.
-	 */
-	public function __construct($xmlNode, $idAttribute = NULL, $publickey = FALSE) {
-		assert('$xmlNode instanceof DOMNode');
-
-		if ($publickey === NULL) {
-			$publickey = FALSE;
-		} elseif(is_string($publickey)) {
-			$publickey = array(
-				'PEM' => $publickey,
-			);
-		} else {
-			assert('$publickey === FALSE || is_array($publickey)');
-		}
-
-		// Create an XML security object
-		$objXMLSecDSig = new XMLSecurityDSig();
-
-		// Add the id attribute if the user passed in an id attribute
-		if($idAttribute !== NULL) {
-			if (is_string($idAttribute)) {
-				$objXMLSecDSig->idKeys[] = $idAttribute;
-			} elseif (is_array($idAttribute)) {
-				foreach ($idAttribute AS $ida) 
-					$objXMLSecDSig->idKeys[] = $ida;
-			}
-		}
-
-		// Locate the XMLDSig Signature element to be used
-		$signatureElement = $objXMLSecDSig->locateSignature($xmlNode);
-		if (!$signatureElement) {
-			throw new Exception('Could not locate XML Signature element.');
-		}
-
-		// Canonicalize the XMLDSig SignedInfo element in the message
-		$objXMLSecDSig->canonicalizeSignedInfo();
-
-		// Validate referenced xml nodes
-		if (!$objXMLSecDSig->validateReference()) {
-			throw new Exception('XMLsec: digest validation failed');
-		}
-
-
-		// Find the key used to sign the document
-		$objKey = $objXMLSecDSig->locateKey();
-		if (empty($objKey)) {
-			throw new Exception('Error loading key to handle XML signature');
-		}
-
-		// Load the key data
-		if ($publickey !== FALSE && array_key_exists('PEM', $publickey)) {
-			// We have PEM data for the public key / certificate
-			$objKey->loadKey($publickey['PEM']);
-		} else {
-			// No PEM data. Search for key in signature
-
-			if (!XMLSecEnc::staticLocateKeyInfo($objKey, $signatureElement)) {
-				throw new Exception('Error finding key data for XML signature validation.');
-			}
-
-			if ($publickey !== FALSE) {
-				/* $publickey is set, and should therefore contain one or more fingerprints.
-				 * Check that the response contains a certificate with a matching
-				 * fingerprint.
-				 */
-				assert('is_array($publickey["certFingerprint"])');
-
-				$certificate = $objKey->getX509Certificate();
-				if ($certificate === NULL) {
-					// Wasn't signed with an X509 certificate
-					throw new Exception('Message wasn\'t signed with an X509 certificate,' .
-						' and no public key was provided in the metadata.');
-				}
-
-				self::validateCertificateFingerprint($certificate, $publickey['certFingerprint']);
-				// Key OK
-			}
-		}
-
-		// Check the signature
-		if ($objXMLSecDSig->verify($objKey) !== 1) {
-			throw new Exception("Unable to validate Signature");
-		}
-
-		// Extract the certificate
-		$this->x509Certificate = $objKey->getX509Certificate();
-
-		// Find the list of validated nodes
-		$this->validNodes = $objXMLSecDSig->getValidatedNodes();
-	}
-
-
-	/**
-	 * Retrieve the X509 certificate which was used to sign the XML.
-	 *
-	 * This function will return the certificate as a PEM-encoded string. If the XML
-	 * wasn't signed by an X509 certificate, NULL will be returned.
-	 *
-	 * @return The certificate as a PEM-encoded string, or NULL if not signed with an X509 certificate.
-	 */
-	public function getX509Certificate() {
-		return $this->x509Certificate;
-	}
-
-
-	/**
-	 * Calculates the fingerprint of an X509 certificate.
-	 *
-	 * @param $x509cert  The certificate as a base64-encoded string. The string may optionally
-	 *                   be framed with '-----BEGIN CERTIFICATE-----' and '-----END CERTIFICATE-----'.
-	 * @return  The fingerprint as a 40-character lowercase hexadecimal number. NULL is returned if the
-	 *          argument isn't an X509 certificate.
-	 */
-	private static function calculateX509Fingerprint($x509cert) {
-		assert('is_string($x509cert)');
-
-		$lines = explode("\n", $x509cert);
-
-		$data = '';
-
-		foreach($lines as $line) {
-			// Remove '\r' from end of line if present
-			$line = rtrim($line);
-			if($line === '-----BEGIN CERTIFICATE-----') {
-				// Delete junk from before the certificate
-				$data = '';
-			} elseif($line === '-----END CERTIFICATE-----') {
-				// Ignore data after the certificate
-				break;
-			} elseif($line === '-----BEGIN PUBLIC KEY-----') {
-				// This isn't an X509 certificate
-				return NULL;
-			} else {
-				// Append the current line to the certificate data
-				$data .= $line;
-			}
-		}
-
-		/* $data now contains the certificate as a base64-encoded string. The fingerprint
-		 * of the certificate is the sha1-hash of the certificate.
-		 */
-		return strtolower(sha1(base64_decode($data)));
-	}
-
-
-	/**
-	 * Helper function for validating the fingerprint.
-	 *
-	 * Checks the fingerprint of a certificate against an array of valid fingerprints.
-	 * Will throw an exception if none of the fingerprints matches.
-	 *
-	 * @param string $certificate  The X509 certificate we should validate.
-	 * @param array $fingerprints  The valid fingerprints.
-	 */
-	private static function validateCertificateFingerprint($certificate, $fingerprints) {
-		assert('is_string($certificate)');
-		assert('is_array($fingerprints)');
-
-		$certFingerprint = self::calculateX509Fingerprint($certificate);
-		if ($certFingerprint === NULL) {
-			// Couldn't calculate fingerprint from X509 certificate. Should not happen.
-			throw new Exception('Unable to calculate fingerprint from X509' .
-				' certificate. Maybe it isn\'t an X509 certificate?');
-		}
-
-		foreach ($fingerprints as $fp) {
-			assert('is_string($fp)');
-
-			if ($fp === $certFingerprint) {
-				// The fingerprints matched
-				return;
-			}
-
-		}
-
-		// None of the fingerprints matched. Throw an exception describing the error.
-		throw new Exception('Invalid fingerprint of certificate. Expected one of [' .
-			implode('], [', $fingerprints) . '], but got [' . $certFingerprint . ']');
-	}
-
-
-	/**
-	 * Validate the fingerprint of the certificate which was used to sign this document.
-	 *
-	 * This function accepts either a string, or an array of strings as a parameter. If this
-	 * is an array, then any string (certificate) in the array can match. If this is a string,
-	 * then that string must match,
-	 *
-	 * @param $fingerprints  The fingerprints which should match. This can be a single string,
-	 *                       or an array of fingerprints.
-	 */
-	public function validateFingerprint($fingerprints) {
-		assert('is_string($fingerprints) || is_array($fingerprints)');
-
-		if($this->x509Certificate === NULL) {
-			throw new Exception('Key used to sign the message was not an X509 certificate.');
-		}
-
-		if(!is_array($fingerprints)) {
-			$fingerprints = array($fingerprints);
-		}
-
-		// Normalize the fingerprints
-		foreach($fingerprints as &$fp) {
-			assert('is_string($fp)');
-
-			// Make sure that the fingerprint is in the correct format
-			$fp = strtolower(str_replace(":", "", $fp));
-		}
-
-		self::validateCertificateFingerprint($this->x509Certificate, $fingerprints);
-	}
-
-
-	/**
-	 * This function checks if the given XML node was signed.
-	 *
-	 * @param $node   The XML node which we should verify that was signed.
-	 *
-	 * @return TRUE if this node (or a parent node) was signed. FALSE if not.
-	 */
-	public function isNodeValidated($node) {
-		assert('$node instanceof DOMNode');
-
-		while($node !== NULL) {
-			if(in_array($node, $this->validNodes)) {
-				return TRUE;
-			}
-
-			$node = $node->parentNode;
-		}
-
-		/* Neither this node nor any of the parent nodes could be found in the list of
-		 * signed nodes.
-		 */
-		return FALSE;
-	}
-
-
-	/**
-	 * Validate the certificate used to sign the XML against a CA file.
-	 *
-	 * This function throws an exception if unable to validate against the given CA file.
-	 *
-	 * @param $caFile  File with trusted certificates, in PEM-format.
-	 */
-	public function validateCA($caFile) {
-
-		assert('is_string($caFile)');
-
-		if($this->x509Certificate === NULL) {
-			throw new Exception('Key used to sign the message was not an X509 certificate.');
-		}
-
-		self::validateCertificate($this->x509Certificate, $caFile);
-	}
-
-	/**
-	 * Validate a certificate against a CA file, by using the builtin
-	 * openssl_x509_checkpurpose function
-	 *
-	 * @param string $certificate  The certificate, in PEM format.
-	 * @param string $caFile  File with trusted certificates, in PEM-format.
-	 * @return boolean|string TRUE on success, or a string with error messages if it failed.
-	 * @deprecated
-	 */
-	private static function validateCABuiltIn($certificate, $caFile) {
-		assert('is_string($certificate)');
-		assert('is_string($caFile)');
-
-		// Clear openssl errors
-		while(openssl_error_string() !== FALSE);
-
-		$res = openssl_x509_checkpurpose($certificate, X509_PURPOSE_ANY, array($caFile));
-
-		$errors = '';
-		// Log errors
-		while( ($error = openssl_error_string()) !== FALSE) {
-			$errors .= ' [' . $error . ']';
-		}
-
-		if($res !== TRUE) {
-			return $errors;
-		}
-
-		return TRUE;
-	}
-
-
-	/**
-	 * Validate the certificate used to sign the XML against a CA file, by using the "openssl verify" command.
-	 *
-	 * This function uses the openssl verify command to verify a certificate, to work around limitations
-	 * on the openssl_x509_checkpurpose function. That function will not work on certificates without a purpose
-	 * set.
-	 *
-	 * @param string $certificate  The certificate, in PEM format.
-	 * @param string $caFile  File with trusted certificates, in PEM-format.
-	 * @return boolean|string TRUE on success, a string with error messages on failure.
-	 * @deprecated
-	 */
-	private static function validateCAExec($certificate, $caFile) {
-		assert('is_string($certificate)');
-		assert('is_string($caFile)');
-
-		$command = array(
-			'openssl', 'verify',
-			'-CAfile', $caFile,
-			'-purpose', 'any',
-		);
-
-		$cmdline = '';
-		foreach($command as $c) {
-			$cmdline .= escapeshellarg($c) . ' ';
-		}
-
-		$cmdline .= '2>&1';
-		$descSpec = array(
-			0 => array('pipe', 'r'),
-			1 => array('pipe', 'w'),
-		);
-		$process = proc_open($cmdline, $descSpec, $pipes);
-		if (!is_resource($process)) {
-			throw new Exception('Failed to execute verification command: ' . $cmdline);
-		}
-
-		if (fwrite($pipes[0], $certificate) === FALSE) {
-			throw new Exception('Failed to write certificate for verification.');
-		}
-		fclose($pipes[0]);
-
-		$out = '';
-		while (!feof($pipes[1])) {
-			$line = trim(fgets($pipes[1]));
-			if(strlen($line) > 0) {
-				$out .= ' [' . $line . ']';
-			}
-		}
-		fclose($pipes[1]);
-
-		$status = proc_close($process);
-		if ($status !== 0 || $out !== ' [stdin: OK]') {
-			return $out;
-		}
-
-		return TRUE;
+
+namespace SimpleSAML\XML;
+
+use RobRichards\XMLSecLibs\XMLSecEnc;
+use RobRichards\XMLSecLibs\XMLSecurityDSig;
+use SimpleSAML\Logger;
+
+class Validator
+{
+
+    /**
+     * @var string This variable contains the X509 certificate the XML document
+     *             was signed with, or NULL if it wasn't signed with an X509 certificate.
+     */
+    private $x509Certificate;
+
+    /**
+     * @var array This variable contains the nodes which are signed.
+     */
+    private $validNodes = null;
+
+
+    /**
+     * This function initializes the validator.
+     *
+     * This function accepts an optional parameter $publickey, which is the public key
+     * or certificate which should be used to validate the signature. This parameter can
+     * take the following values:
+     * - NULL/FALSE: No validation will be performed. This is the default.
+     * - A string: Assumed to be a PEM-encoded certificate / public key.
+     * - An array: Assumed to be an array returned by SimpleSAML_Utilities::loadPublicKey.
+     *
+     * @param \DOMNode $xmlNode The XML node which contains the Signature element.
+     * @param string|array $idAttribute The ID attribute which is used in node references. If
+     *          this attribute is NULL (the default), then we will use whatever is the default
+     *          ID. Can be eigther a string with one value, or an array with multiple ID
+     *          attrbute names.
+     * @param array|bool $publickey The public key / certificate which should be used to validate the XML node.
+     * @throws \Exception
+     */
+    public function __construct($xmlNode, $idAttribute = null, $publickey = false)
+    {
+        assert('$xmlNode instanceof \DOMNode');
+
+        if ($publickey === null) {
+            $publickey = false;
+        } elseif (is_string($publickey)) {
+            $publickey = array(
+                'PEM' => $publickey,
+            );
+        } else {
+            assert('$publickey === FALSE || is_array($publickey)');
+        }
+
+        // Create an XML security object
+        $objXMLSecDSig = new XMLSecurityDSig();
+
+        // Add the id attribute if the user passed in an id attribute
+        if ($idAttribute !== null) {
+            if (is_string($idAttribute)) {
+                $objXMLSecDSig->idKeys[] = $idAttribute;
+            } elseif (is_array($idAttribute)) {
+                foreach ($idAttribute as $ida) {
+                    $objXMLSecDSig->idKeys[] = $ida;
+                }
+            }
+        }
+
+        // Locate the XMLDSig Signature element to be used
+        $signatureElement = $objXMLSecDSig->locateSignature($xmlNode);
+        if (!$signatureElement) {
+            throw new \Exception('Could not locate XML Signature element.');
+        }
+
+        // Canonicalize the XMLDSig SignedInfo element in the message
+        $objXMLSecDSig->canonicalizeSignedInfo();
+
+        // Validate referenced xml nodes
+        if (!$objXMLSecDSig->validateReference()) {
+            throw new \Exception('XMLsec: digest validation failed');
+        }
+
+
+        // Find the key used to sign the document
+        $objKey = $objXMLSecDSig->locateKey();
+        if (empty($objKey)) {
+            throw new \Exception('Error loading key to handle XML signature');
+        }
+
+        // Load the key data
+        if ($publickey !== false && array_key_exists('PEM', $publickey)) {
+            // We have PEM data for the public key / certificate
+            $objKey->loadKey($publickey['PEM']);
+        } else {
+            // No PEM data. Search for key in signature
+
+            if (!XMLSecEnc::staticLocateKeyInfo($objKey, $signatureElement)) {
+                throw new \Exception('Error finding key data for XML signature validation.');
+            }
+
+            if ($publickey !== false) {
+                /* $publickey is set, and should therefore contain one or more fingerprints.
+                 * Check that the response contains a certificate with a matching
+                 * fingerprint.
+                 */
+                assert('is_array($publickey["certFingerprint"])');
+
+                $certificate = $objKey->getX509Certificate();
+                if ($certificate === null) {
+                    // Wasn't signed with an X509 certificate
+                    throw new \Exception('Message wasn\'t signed with an X509 certificate,' .
+                        ' and no public key was provided in the metadata.');
+                }
+
+                self::validateCertificateFingerprint($certificate, $publickey['certFingerprint']);
+                // Key OK
+            }
+        }
+
+        // Check the signature
+        if ($objXMLSecDSig->verify($objKey) !== 1) {
+            throw new \Exception("Unable to validate Signature");
+        }
+
+        // Extract the certificate
+        $this->x509Certificate = $objKey->getX509Certificate();
+
+        // Find the list of validated nodes
+        $this->validNodes = $objXMLSecDSig->getValidatedNodes();
+    }
+
+
+    /**
+     * Retrieve the X509 certificate which was used to sign the XML.
+     *
+     * This function will return the certificate as a PEM-encoded string. If the XML
+     * wasn't signed by an X509 certificate, NULL will be returned.
+     *
+     * @return string  The certificate as a PEM-encoded string, or NULL if not signed with an X509 certificate.
+     */
+    public function getX509Certificate()
+    {
+        return $this->x509Certificate;
+    }
+
+
+    /**
+     * Calculates the fingerprint of an X509 certificate.
+     *
+     * @param string $x509cert  The certificate as a base64-encoded string. The string may optionally
+     *                          be framed with '-----BEGIN CERTIFICATE-----' and '-----END CERTIFICATE-----'.
+     * @return string  The fingerprint as a 40-character lowercase hexadecimal number. NULL is returned if the
+     *                 argument isn't an X509 certificate.
+     */
+    private static function calculateX509Fingerprint($x509cert)
+    {
+        assert('is_string($x509cert)');
+
+        $lines = explode("\n", $x509cert);
+
+        $data = '';
+
+        foreach ($lines as $line) {
+            // Remove '\r' from end of line if present
+            $line = rtrim($line);
+            if ($line === '-----BEGIN CERTIFICATE-----') {
+                // Delete junk from before the certificate
+                $data = '';
+            } elseif ($line === '-----END CERTIFICATE-----') {
+                // Ignore data after the certificate
+                break;
+            } elseif ($line === '-----BEGIN PUBLIC KEY-----') {
+                // This isn't an X509 certificate
+                return null;
+            } else {
+                // Append the current line to the certificate data
+                $data .= $line;
+            }
+        }
+
+        /* $data now contains the certificate as a base64-encoded string. The fingerprint
+         * of the certificate is the sha1-hash of the certificate.
+         */
+        return strtolower(sha1(base64_decode($data)));
+    }
+
+
+    /**
+     * Helper function for validating the fingerprint.
+     *
+     * Checks the fingerprint of a certificate against an array of valid fingerprints.
+     * Will throw an exception if none of the fingerprints matches.
+     *
+     * @param string $certificate The X509 certificate we should validate.
+     * @param array $fingerprints The valid fingerprints.
+     * @throws \Exception
+     */
+    private static function validateCertificateFingerprint($certificate, $fingerprints)
+    {
+        assert('is_string($certificate)');
+        assert('is_array($fingerprints)');
+
+        $certFingerprint = self::calculateX509Fingerprint($certificate);
+        if ($certFingerprint === null) {
+            // Couldn't calculate fingerprint from X509 certificate. Should not happen.
+            throw new \Exception('Unable to calculate fingerprint from X509' .
+                ' certificate. Maybe it isn\'t an X509 certificate?');
+        }
+
+        foreach ($fingerprints as $fp) {
+            assert('is_string($fp)');
+
+            if ($fp === $certFingerprint) {
+                // The fingerprints matched
+                return;
+            }
+        }
+
+        // None of the fingerprints matched. Throw an exception describing the error.
+        throw new \Exception('Invalid fingerprint of certificate. Expected one of [' .
+            implode('], [', $fingerprints) . '], but got [' . $certFingerprint . ']');
+    }
+
+
+    /**
+     * Validate the fingerprint of the certificate which was used to sign this document.
+     *
+     * This function accepts either a string, or an array of strings as a parameter. If this
+     * is an array, then any string (certificate) in the array can match. If this is a string,
+     * then that string must match,
+     *
+     * @param string|array $fingerprints  The fingerprints which should match. This can be a single string,
+     *                                    or an array of fingerprints.
+     * @throws \Exception
+     */
+    public function validateFingerprint($fingerprints)
+    {
+        assert('is_string($fingerprints) || is_array($fingerprints)');
+
+        if ($this->x509Certificate === null) {
+            throw new \Exception('Key used to sign the message was not an X509 certificate.');
+        }
+
+        if (!is_array($fingerprints)) {
+            $fingerprints = array($fingerprints);
+        }
+
+        // Normalize the fingerprints
+        foreach ($fingerprints as &$fp) {
+            assert('is_string($fp)');
+
+            // Make sure that the fingerprint is in the correct format
+            $fp = strtolower(str_replace(":", "", $fp));
+        }
+
+        self::validateCertificateFingerprint($this->x509Certificate, $fingerprints);
     }
 
 
-	/**
-	 * Validate the certificate used to sign the XML against a CA file.
-	 *
-	 * This function throws an exception if unable to validate against the given CA file.
-	 *
-	 * @param string $certificate  The certificate, in PEM format.
-	 * @param string $caFile  File with trusted certificates, in PEM-format.
-	 * @deprecated
-	 */
-	public static function validateCertificate($certificate, $caFile) {
-		assert('is_string($certificate)');
-		assert('is_string($caFile)');
-
-		if (!file_exists($caFile)) {
-			throw new Exception('Could not load CA file: ' . $caFile);
-		}
-
-		SimpleSAML\Logger::debug('Validating certificate against CA file: ' . var_export($caFile, TRUE));
-
-		$resBuiltin = self::validateCABuiltIn($certificate, $caFile);
-		if ($resBuiltin !== TRUE) {
-			SimpleSAML\Logger::debug('Failed to validate with internal function: ' . var_export($resBuiltin, TRUE));
-
-			$resExternal = self::validateCAExec($certificate, $caFile);
-			if ($resExternal !== TRUE) {
-				SimpleSAML\Logger::debug('Failed to validate with external function: ' . var_export($resExternal, TRUE));
-				throw new Exception('Could not verify certificate against CA file "'
-					. $caFile . '". Internal result:' . $resBuiltin .
-					' External result:' . $resExternal);
-			}
-		}
-
-		SimpleSAML\Logger::debug('Successfully validated certificate.');
-	}
+    /**
+     * This function checks if the given XML node was signed.
+     *
+     * @param \DOMNode $node  The XML node which we should verify that was signed.
+     *
+     * @return bool  TRUE if this node (or a parent node) was signed. FALSE if not.
+     */
+    public function isNodeValidated($node)
+    {
+        assert('$node instanceof \DOMNode');
+
+        while ($node !== null) {
+            if (in_array($node, $this->validNodes)) {
+                return true;
+            }
+
+            $node = $node->parentNode;
+        }
+
+        /* Neither this node nor any of the parent nodes could be found in the list of
+         * signed nodes.
+         */
+        return false;
+    }
+
+
+    /**
+     * Validate the certificate used to sign the XML against a CA file.
+     *
+     * This function throws an exception if unable to validate against the given CA file.
+     *
+     * @param string $caFile  File with trusted certificates, in PEM-format.
+     * @throws \Exception
+     */
+    public function validateCA($caFile)
+    {
+        assert('is_string($caFile)');
+
+        if ($this->x509Certificate === null) {
+            throw new \Exception('Key used to sign the message was not an X509 certificate.');
+        }
 
+        self::validateCertificate($this->x509Certificate, $caFile);
+    }
+
+    /**
+     * Validate a certificate against a CA file, by using the builtin
+     * openssl_x509_checkpurpose function
+     *
+     * @param string $certificate  The certificate, in PEM format.
+     * @param string $caFile  File with trusted certificates, in PEM-format.
+     * @return boolean|string TRUE on success, or a string with error messages if it failed.
+     * @deprecated
+     */
+    private static function validateCABuiltIn($certificate, $caFile)
+    {
+        assert('is_string($certificate)');
+        assert('is_string($caFile)');
+
+        // Clear openssl errors
+        while (openssl_error_string() !== false);
+
+        $res = openssl_x509_checkpurpose($certificate, X509_PURPOSE_ANY, array($caFile));
+
+        $errors = '';
+        // Log errors
+        while (($error = openssl_error_string()) !== false) {
+            $errors .= ' [' . $error . ']';
+        }
+
+        if ($res !== true) {
+            return $errors;
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Validate the certificate used to sign the XML against a CA file, by using the "openssl verify" command.
+     *
+     * This function uses the openssl verify command to verify a certificate, to work around limitations
+     * on the openssl_x509_checkpurpose function. That function will not work on certificates without a purpose
+     * set.
+     *
+     * @param string $certificate The certificate, in PEM format.
+     * @param string $caFile File with trusted certificates, in PEM-format.
+     * @return bool|string TRUE on success, a string with error messages on failure.
+     * @throws \Exception
+     * @deprecated
+     */
+    private static function validateCAExec($certificate, $caFile)
+    {
+        assert('is_string($certificate)');
+        assert('is_string($caFile)');
+
+        $command = array(
+            'openssl', 'verify',
+            '-CAfile', $caFile,
+            '-purpose', 'any',
+        );
+
+        $cmdline = '';
+        foreach ($command as $c) {
+            $cmdline .= escapeshellarg($c) . ' ';
+        }
+
+        $cmdline .= '2>&1';
+        $descSpec = array(
+            0 => array('pipe', 'r'),
+            1 => array('pipe', 'w'),
+        );
+        $process = proc_open($cmdline, $descSpec, $pipes);
+        if (!is_resource($process)) {
+            throw new \Exception('Failed to execute verification command: ' . $cmdline);
+        }
+
+        if (fwrite($pipes[0], $certificate) === false) {
+            throw new \Exception('Failed to write certificate for verification.');
+        }
+        fclose($pipes[0]);
+
+        $out = '';
+        while (!feof($pipes[1])) {
+            $line = trim(fgets($pipes[1]));
+            if (strlen($line) > 0) {
+                $out .= ' [' . $line . ']';
+            }
+        }
+        fclose($pipes[1]);
+
+        $status = proc_close($process);
+        if ($status !== 0 || $out !== ' [stdin: OK]') {
+            return $out;
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Validate the certificate used to sign the XML against a CA file.
+     *
+     * This function throws an exception if unable to validate against the given CA file.
+     *
+     * @param string $certificate The certificate, in PEM format.
+     * @param string $caFile File with trusted certificates, in PEM-format.
+     * @throws \Exception
+     * @deprecated
+     */
+    public static function validateCertificate($certificate, $caFile)
+    {
+        assert('is_string($certificate)');
+        assert('is_string($caFile)');
+
+        if (!file_exists($caFile)) {
+            throw new \Exception('Could not load CA file: ' . $caFile);
+        }
+
+        Logger::debug('Validating certificate against CA file: ' . var_export($caFile, true));
+
+        $resBuiltin = self::validateCABuiltIn($certificate, $caFile);
+        if ($resBuiltin !== true) {
+            Logger::debug('Failed to validate with internal function: ' . var_export($resBuiltin, true));
+
+            $resExternal = self::validateCAExec($certificate, $caFile);
+            if ($resExternal !== true) {
+                Logger::debug('Failed to validate with external function: ' . var_export($resExternal, true));
+                throw new \Exception('Could not verify certificate against CA file "'
+                    . $caFile . '". Internal result:' . $resBuiltin .
+                    ' External result:' . $resExternal);
+            }
+        }
+
+        Logger::debug('Successfully validated certificate.');
+    }
 }
diff --git a/modules/authcrypt/docs/authcrypt.md b/modules/authcrypt/docs/authcrypt.md
index 8319840f3e23281811d88eeaabca46d0c4ba8102..148b9ebed0088456755cb360bde29bc9a9a9b428 100644
--- a/modules/authcrypt/docs/authcrypt.md
+++ b/modules/authcrypt/docs/authcrypt.md
@@ -17,38 +17,38 @@ This is based on `exampleAuth:UserPass`, and adds support for hashed passwords.
 Hashes can be generated with the included command line tool `bin/pwgen.sh`.
 This tool will interactively ask for a password, a hashing algorithm , and whether or not you want to use a salt:
 
-	[user@server simplesamlphp]$ bin/pwgen.php
-	Enter password: hackme
-	The following hashing algorithms are available:
-	md2          md4          md5          sha1         sha224       sha256
-	sha384       sha512       ripemd128    ripemd160    ripemd256    ripemd320
-	whirlpool    tiger128,3   tiger160,3   tiger192,3   tiger128,4   tiger160,4
-	tiger192,4   snefru       snefru256    gost         adler32      crc32
-	crc32b       salsa10      salsa20      haval128,3   haval160,3   haval192,3
-	haval224,3   haval256,3   haval128,4   haval160,4   haval192,4   haval224,4
-	haval256,4   haval128,5   haval160,5   haval192,5   haval224,5   haval256,5
-
-	Which one do you want? [sha256]
-	Do you want to use a salt? (yes/no) [yes]
-
-	  {SSHA256}y1mj3xsZ4/+LoQyPNVJzXUFfBcLHfwcHx1xxltxeQ1C5MeyEX/RxWA==
+    [user@server simplesamlphp]$ bin/pwgen.php
+    Enter password: hackme
+    The following hashing algorithms are available:
+    md2          md4          md5          sha1         sha224       sha256
+    sha384       sha512       ripemd128    ripemd160    ripemd256    ripemd320
+    whirlpool    tiger128,3   tiger160,3   tiger192,3   tiger128,4   tiger160,4
+    tiger192,4   snefru       snefru256    gost         adler32      crc32
+    crc32b       salsa10      salsa20      haval128,3   haval160,3   haval192,3
+    haval224,3   haval256,3   haval128,4   haval160,4   haval192,4   haval224,4
+    haval256,4   haval128,5   haval160,5   haval192,5   haval224,5   haval256,5
+
+    Which one do you want? [sha256]
+    Do you want to use a salt? (yes/no) [yes]
+
+      {SSHA256}y1mj3xsZ4/+LoQyPNVJzXUFfBcLHfwcHx1xxltxeQ1C5MeyEX/RxWA==
 
 Now create an authentication source in `config/authsources.php` and use the resulting string as the password:
 
-	'example-hashed' => array(
-		'authCrypt:Hash',
-		'student:{SSHA256}y1mj3xsZ4/+LoQyPNVJzXUFfBcLHfwcHx1xxltxeQ1C5MeyEX/RxWA==' => array(
-			'uid' => array('student'),
-			'eduPersonAffiliation' => array('member', 'student'),
-			),
-	),
+    'example-hashed' => array(
+        'authCrypt:Hash',
+        'student:{SSHA256}y1mj3xsZ4/+LoQyPNVJzXUFfBcLHfwcHx1xxltxeQ1C5MeyEX/RxWA==' => array(
+            'uid' => array('student'),
+            'eduPersonAffiliation' => array('member', 'student'),
+            ),
+    ),
 
 This example creates a user `student` with password `hackme`, and some attributes.
 
 ### Compatibility ###
 The generated hashes can also be used in `config.php` for the administrative password:
 
-	'auth.adminpassword'        => '{SSHA256}y1mj3xsZ4/+LoQyPNVJzXUFfBcLHfwcHx1xxltxeQ1C5MeyEX/RxWA==',
+    'auth.adminpassword'        => '{SSHA256}y1mj3xsZ4/+LoQyPNVJzXUFfBcLHfwcHx1xxltxeQ1C5MeyEX/RxWA==',
 
 Instead of generating hashes, you can also use existing ones from OpenLDAP, provided that the `userPassword` attribute is stored as MD5, SMD5, SHA, or SSHA.
 
@@ -62,12 +62,12 @@ The simple structure of the `.htpasswd` file does not allow for per-user attribu
 
 An example authentication source in `config/authsources.php` could look like this:
 
-	'htpasswd' => array(
-		'authcrypt:Htpasswd',
-			'htpasswd_file' => '/var/www/foo.edu/legacy_app/.htpasswd',
-			'static_attributes' => array(
-				'eduPersonAffiliation' => array('member', 'employee'),
-				'Organization' => array('University of Foo'),
-		),
-	),
+    'htpasswd' => array(
+        'authcrypt:Htpasswd',
+            'htpasswd_file' => '/var/www/foo.edu/legacy_app/.htpasswd',
+            'static_attributes' => array(
+                'eduPersonAffiliation' => array('member', 'employee'),
+                'Organization' => array('University of Foo'),
+        ),
+    ),
 
diff --git a/modules/authcrypt/lib/Auth/Source/Hash.php b/modules/authcrypt/lib/Auth/Source/Hash.php
index 98675c2185dcf081f13ea392accc3dc8045929c2..ef5b35b335d490ca96829f9e1365a18bf0aabe1e 100644
--- a/modules/authcrypt/lib/Auth/Source/Hash.php
+++ b/modules/authcrypt/lib/Auth/Source/Hash.php
@@ -1,5 +1,6 @@
 <?php
 
+
 /**
  * Authentication source for username & hashed password.
  *
@@ -9,87 +10,94 @@
  * @author Dyonisius Visser, TERENA.
  * @package SimpleSAMLphp
  */
-class sspmod_authcrypt_Auth_Source_Hash extends sspmod_core_Auth_UserPassBase {
-
+class sspmod_authcrypt_Auth_Source_Hash extends sspmod_core_Auth_UserPassBase
+{
 
-	/**
-	 * Our users, stored in an associative array. The key of the array is "<username>:<passwordhash>",
-	 * while the value of each element is a new array with the attributes for each user.
-	 */
-	private $users;
 
+    /**
+     * Our users, stored in an associative array. The key of the array is "<username>:<passwordhash>",
+     * while the value of each element is a new array with the attributes for each user.
+     */
+    private $users;
 
-	/**
-	 * Constructor for this authentication source.
-	 *
-	 * @param array $info  Information about this authentication source.
-	 * @param array $config  Configuration.
-	 */
-	public function __construct($info, $config) {
-		assert('is_array($info)');
-		assert('is_array($config)');
 
-		// Call the parent constructor first, as required by the interface
-		parent::__construct($info, $config);
+    /**
+     * Constructor for this authentication source.
+     *
+     * @param array $info Information about this authentication source.
+     * @param array $config Configuration.
+     *
+     * @throws Exception in case of a configuration error.
+     */
+    public function __construct($info, $config)
+    {
+        assert('is_array($info)');
+        assert('is_array($config)');
 
-		$this->users = array();
+        // Call the parent constructor first, as required by the interface
+        parent::__construct($info, $config);
 
-		// Validate and parse our configuration
-		foreach ($config as $userpass => $attributes) {
-			if (!is_string($userpass)) {
-				throw new Exception('Invalid <username>:<passwordhash> for authentication source ' .
-					$this->authId . ': ' . $userpass);
-			}
+        $this->users = array();
 
-			$userpass = explode(':', $userpass, 2);
-			if (count($userpass) !== 2) {
-				throw new Exception('Invalid <username>:<passwordhash> for authentication source ' .
-					$this->authId . ': ' . $userpass[0]);
-			}
-			$username = $userpass[0];
-			$passwordhash = $userpass[1];
+        // Validate and parse our configuration
+        foreach ($config as $userpass => $attributes) {
+            if (!is_string($userpass)) {
+                throw new Exception('Invalid <username>:<passwordhash> for authentication source '.
+                    $this->authId.': '.$userpass);
+            }
 
-			try {
-				$attributes = SimpleSAML\Utils\Attributes::normalizeAttributesArray($attributes);
-			} catch(Exception $e) {
-				throw new Exception('Invalid attributes for user ' . $username .
-					' in authentication source ' . $this->authId . ': ' .
-					$e->getMessage());
-			}
+            $userpass = explode(':', $userpass, 2);
+            if (count($userpass) !== 2) {
+                throw new Exception('Invalid <username>:<passwordhash> for authentication source '.
+                    $this->authId.': '.$userpass[0]);
+            }
+            $username = $userpass[0];
+            $passwordhash = $userpass[1];
 
-			$this->users[$username . ':' . $passwordhash] = $attributes;
-		}
-	}
+            try {
+                $attributes = SimpleSAML\Utils\Attributes::normalizeAttributesArray($attributes);
+            } catch (Exception $e) {
+                throw new Exception('Invalid attributes for user '.$username.
+                    ' in authentication source '.$this->authId.': '.
+                    $e->getMessage());
+            }
 
+            $this->users[$username.':'.$passwordhash] = $attributes;
+        }
+    }
 
-	/**
-	 * Attempt to log in using the given username and password.
-	 *
-	 * On a successful login, this function should return the users attributes. On failure,
-	 * it should throw an exception. If the error was caused by the user entering the wrong
-	 * username OR password, a SimpleSAML_Error_Error('WRONGUSERPASS') should be thrown.
-	 *
-	 * The username is UTF-8 encoded, and the hash is base64 encoded.
-	 *
-	 * @param string $username  The username the user wrote.
-	 * @param string $password  The password the user wrote.
-	 * @return array  Associative array with the users attributes.
-	 */
-	protected function login($username, $password) {
-		assert('is_string($username)');
-		assert('is_string($password)');
 
-		foreach($this->users as $userpass=>$attrs) {
-			$matches = explode(':', $userpass, 2);
-			if ($matches[0] === $username) {
-				if(SimpleSAML\Utils\Crypto::pwValid($matches[1], $password)) {
-					return $this->users[$userpass];
-				} else {
-					SimpleSAML\Logger::debug('Incorrect password "' . $password . '" for user '. $username);
-				}
-			}
-		}
-		throw new SimpleSAML_Error_Error('WRONGUSERPASS');
-	}
+    /**
+     * Attempt to log in using the given username and password.
+     *
+     * On a successful login, this function should return the users attributes. On failure,
+     * it should throw an exception. If the error was caused by the user entering the wrong
+     * username OR password, a SimpleSAML_Error_Error('WRONGUSERPASS') should be thrown.
+     *
+     * The username is UTF-8 encoded, and the hash is base64 encoded.
+     *
+     * @param string $username The username the user wrote.
+     * @param string $password The password the user wrote.
+     *
+     * @return array  Associative array with the users attributes.
+     *
+     * @throws SimpleSAML_Error_Error if authentication fails.
+     */
+    protected function login($username, $password)
+    {
+        assert('is_string($username)');
+        assert('is_string($password)');
 
+        foreach ($this->users as $userpass => $attrs) {
+            $matches = explode(':', $userpass, 2);
+            if ($matches[0] === $username) {
+                if (SimpleSAML\Utils\Crypto::pwValid($matches[1], $password)) {
+                    return $attrs;
+                } else {
+                    SimpleSAML\Logger::debug('Incorrect password "'.$password.'" for user '.$username);
+                }
+            }
+        }
+        throw new SimpleSAML_Error_Error('WRONGUSERPASS');
+    }
 }
diff --git a/modules/authcrypt/lib/Auth/Source/Htpasswd.php b/modules/authcrypt/lib/Auth/Source/Htpasswd.php
index 99923e18f96179efc931f76023f256e3e0edbde1..5b9ffc3f38321548e1a2cd311ae170e73e0a3aa5 100644
--- a/modules/authcrypt/lib/Auth/Source/Htpasswd.php
+++ b/modules/authcrypt/lib/Auth/Source/Htpasswd.php
@@ -9,91 +9,113 @@
 
 use WhiteHat101\Crypt\APR1_MD5;
 
-class sspmod_authcrypt_Auth_Source_Htpasswd extends sspmod_core_Auth_UserPassBase {
-
-
-	/**
-	 * Our users, stored in an array, where each value is "<username>:<passwordhash>".
-	 */
-	private $users;
-
-	/**
-	 * Constructor for this authentication source.
-	 *
-	 * @param array $info  Information about this authentication source.
-	 * @param array $config  Configuration.
-	 */
-	public function __construct($info, $config) {
-		assert('is_array($info)');
-		assert('is_array($config)');
-
-		// Call the parent constructor first, as required by the interface
-		parent::__construct($info, $config);
-
-		$this->users = array();
-
-		if(!$htpasswd = file_get_contents($config['htpasswd_file'])) {
-			throw new Exception('Could not read ' . $config['htpasswd_file']);
-		}
-
-		$this->users = explode("\n", trim($htpasswd));
-
-		try {
-			$this->attributes = SimpleSAML\Utils\Attributes::normalizeAttributesArray($config['static_attributes']);
-		} catch(Exception $e) {
-			throw new Exception('Invalid static_attributes in authentication source ' .
-				$this->authId . ': ' .	$e->getMessage());
-		}
-	}
-
-
-	/**
-	 * Attempt to log in using the given username and password.
-	 *
-	 * On a successful login, this function should return the username as 'uid' attribute,
-	 * and merged attributes from the configuration file.
-	 * On failure, it should throw an exception. A SimpleSAML_Error_Error('WRONGUSERPASS')
-	 * should be thrown in case of a wrong username OR a wrong password, to prevent the
-	 * enumeration of usernames.
-	 *
-	 * @param string $username  The username the user wrote.
-	 * @param string $password  The password the user wrote.
-	 * @return array  Associative array with the users attributes.
-	 */
-	protected function login($username, $password) {
-		assert('is_string($username)');
-		assert('is_string($password)');
-
-		foreach($this->users as $userpass) {
-			$matches = explode(':', $userpass, 2);
-			if($matches[0] == $username) {
-
-				$crypted = $matches[1];
-
-				// This is about the only attribute we can add
-				$attributes = array_merge(array('uid' => array($username)), $this->attributes);
-
-				// Traditional crypt(3)
-				if(crypt($password, $crypted) == $crypted) {
-					SimpleSAML\Logger::debug('User '. $username . ' authenticated successfully');
-					return $attributes;
-				}
-
-				// Apache's custom MD5
-				if(APR1_MD5::check($password, $crypted)) {
-					SimpleSAML\Logger::debug('User '. $username . ' authenticated successfully');
-					return $attributes;
-				}
-
-				// SHA1 or plain-text
-				if(SimpleSAML\Utils\Crypto::pwValid($crypted, $password)) {
-					SimpleSAML\Logger::debug('User '. $username . ' authenticated successfully');
-					return $attributes;
-				}
-				throw new SimpleSAML_Error_Error('WRONGUSERPASS');
-			}
-		}
-		throw new SimpleSAML_Error_Error('WRONGUSERPASS');
-	}
-
+class sspmod_authcrypt_Auth_Source_Htpasswd extends sspmod_core_Auth_UserPassBase
+{
+
+
+    /**
+     * Our users, stored in an array, where each value is "<username>:<passwordhash>".
+     *
+     * @var array
+     */
+    private $users;
+
+    /**
+     * An array containing static attributes for our users.
+     *
+     * @var array
+     */
+    private $attributes = array();
+
+
+    /**
+     * Constructor for this authentication source.
+     *
+     * @param array $info Information about this authentication source.
+     * @param array $config Configuration.
+     *
+     * @throws Exception if the htpasswd file is not readable or the static_attributes array is invalid.
+     */
+    public function __construct($info, $config)
+    {
+        assert('is_array($info)');
+        assert('is_array($config)');
+
+        // Call the parent constructor first, as required by the interface
+        parent::__construct($info, $config);
+
+        $this->users = array();
+
+        if (!$htpasswd = file_get_contents($config['htpasswd_file'])) {
+            throw new Exception('Could not read '.$config['htpasswd_file']);
+        }
+
+        $this->users = explode("\n", trim($htpasswd));
+
+        try {
+            $this->attributes = SimpleSAML\Utils\Attributes::normalizeAttributesArray($config['static_attributes']);
+        } catch (Exception $e) {
+            throw new Exception('Invalid static_attributes in authentication source '.
+                $this->authId.': '.$e->getMessage());
+        }
+    }
+
+
+    /**
+     * Attempt to log in using the given username and password.
+     *
+     * On a successful login, this function should return the username as 'uid' attribute,
+     * and merged attributes from the configuration file.
+     * On failure, it should throw an exception. A SimpleSAML_Error_Error('WRONGUSERPASS')
+     * should be thrown in case of a wrong username OR a wrong password, to prevent the
+     * enumeration of usernames.
+     *
+     * @param string $username The username the user wrote.
+     * @param string $password The password the user wrote.
+     *
+     * @return array Associative array with the users attributes.
+     *
+     * @throws SimpleSAML_Error_Error if authentication fails.
+     */
+    protected function login($username, $password)
+    {
+        assert('is_string($username)');
+        assert('is_string($password)');
+
+        foreach ($this->users as $userpass) {
+            $matches = explode(':', $userpass, 2);
+            if ($matches[0] == $username) {
+                $crypted = $matches[1];
+
+                // This is about the only attribute we can add
+                $attributes = array_merge(array('uid' => array($username)), $this->attributes);
+
+                // Traditional crypt(3)
+                if (SimpleSAML\Utils\Crypto::secureCompare($crypted, crypt($password, $crypted))) {
+                    SimpleSAML\Logger::debug('User '.$username.' authenticated successfully');
+                    SimpleSAML\Logger::warning(
+                        'CRYPT authentication is insecure. Please consider using something else.'
+                    );
+                    return $attributes;
+                }
+
+                // Apache's custom MD5
+                if (APR1_MD5::check($password, $crypted)) {
+                    SimpleSAML\Logger::debug('User '.$username.' authenticated successfully');
+                    return $attributes;
+                }
+
+                // SHA1 or plain-text
+                if (SimpleSAML\Utils\Crypto::pwValid($crypted, $password)) {
+                    SimpleSAML\Logger::debug('User '.$username.' authenticated successfully');
+                    SimpleSAML\Logger::warning(
+                        'SHA1 and PLAIN TEXT authentication are insecure. Please consider using something else.'
+                    );
+                    return $attributes;
+                }
+                throw new SimpleSAML_Error_Error('WRONGUSERPASS');
+            }
+        }
+        throw new SimpleSAML_Error_Error('WRONGUSERPASS');
+    }
 }
diff --git a/modules/authfacebook/extlibinc/base_facebook.php b/modules/authfacebook/extlibinc/base_facebook.php
index 7fd46b545f408564fc968da8010d1a5eeb4e4207..a5fd3e9043db0edc0fc683e6d5f596e06f9da660 100644
--- a/modules/authfacebook/extlibinc/base_facebook.php
+++ b/modules/authfacebook/extlibinc/base_facebook.php
@@ -789,8 +789,7 @@ abstract class BaseFacebook
       return false;
     }
 
-    $response_params = array();
-    parse_str($access_token_response, $response_params);
+    $response_params = json_decode($access_token_response, true);
     if (!isset($response_params['access_token'])) {
       return false;
     }
diff --git a/modules/authlinkedin/lib/Auth/Source/LinkedIn.php b/modules/authlinkedin/lib/Auth/Source/LinkedIn.php
index fe22da165b74072477f3cfaf8c28fc73a8dcb4e1..aeb6db11fd6f6d664c91df4a4ca1e81274a2cff2 100644
--- a/modules/authlinkedin/lib/Auth/Source/LinkedIn.php
+++ b/modules/authlinkedin/lib/Auth/Source/LinkedIn.php
@@ -8,118 +8,137 @@ require_once(dirname(dirname(dirname(dirname(dirname(__FILE__))))) . '/oauth/lib
  * @author Brook Schofield, TERENA.
  * @package SimpleSAMLphp
  */
-class sspmod_authlinkedin_Auth_Source_LinkedIn extends SimpleSAML_Auth_Source {
+class sspmod_authlinkedin_Auth_Source_LinkedIn extends SimpleSAML_Auth_Source 
+{
 
-	/**
-	 * The string used to identify our states.
-	 */
-	const STAGE_INIT = 'authlinkedin:init';
+    /**
+     * The string used to identify our states.
+     */
+    const STAGE_INIT = 'authlinkedin:init';
 
-	/**
-	 * The key of the AuthId field in the state.
-	 */
-	const AUTHID = 'authlinkedin:AuthId';
+    /**
+     * The key of the AuthId field in the state.
+     */
+    const AUTHID = 'authlinkedin:AuthId';
 
-	private $key;
-	private $secret;
-	private $attributes;
+    private $key;
+    private $secret;
+    private $attributes;
 
 
-	/**
-	 * Constructor for this authentication source.
-	 *
-	 * @param array $info  Information about this authentication source.
-	 * @param array $config  Configuration.
-	 */
-	public function __construct($info, $config) {
-		assert('is_array($info)');
-		assert('is_array($config)');
+    /**
+     * Constructor for this authentication source.
+     *
+     * @param array $info  Information about this authentication source.
+     * @param array $config  Configuration.
+     */
+    public function __construct($info, $config)
+    {
+        assert('is_array($info)');
+        assert('is_array($config)');
 
-		// Call the parent constructor first, as required by the interface
-		parent::__construct($info, $config);
+        // Call the parent constructor first, as required by the interface
+        parent::__construct($info, $config);
 
-		if (!array_key_exists('key', $config))
-			throw new Exception('LinkedIn authentication source is not properly configured: missing [key]');
+        if (!array_key_exists('key', $config))
+            throw new Exception('LinkedIn authentication source is not properly configured: missing [key]');
 
-		$this->key = $config['key'];
+        $this->key = $config['key'];
 
-		if (!array_key_exists('secret', $config))
-			throw new Exception('LinkedIn authentication source is not properly configured: missing [secret]');
+        if (!array_key_exists('secret', $config))
+            throw new Exception('LinkedIn authentication source is not properly configured: missing [secret]');
 
-		$this->secret = $config['secret'];
+        $this->secret = $config['secret'];
 
-		if (array_key_exists('attributes', $config)) {
-			$this->attributes = $config['attributes'];
-		} else {
-			// Default values if the attributes are not set in config (ref https://developer.linkedin.com/docs/fields)
-			$this->attributes = 'id,first-name,last-name,headline,summary,specialties,picture-url,email-address';
-		}
-	}
+        if (array_key_exists('attributes', $config)) {
+            $this->attributes = $config['attributes'];
+        } else {
+            // Default values if the attributes are not set in config (ref https://developer.linkedin.com/docs/fields)
+            $this->attributes = 'id,first-name,last-name,headline,summary,specialties,picture-url,email-address';
+        }
+    }
 
 
-	/**
-	 * Log-in using LinkedIn platform
-	 * Documentation at: http://developer.linkedin.com/docs/DOC-1008
-	 *
-	 * @param array &$state  Information about the current authentication.
-	 */
-	public function authenticate(&$state) {
-		assert('is_array($state)');
+    /**
+     * Log-in using LinkedIn platform
+     * Documentation at: http://developer.linkedin.com/docs/DOC-1008
+     *
+     * @param array &$state  Information about the current authentication.
+     */
+    public function authenticate(&$state)
+    {
+        assert('is_array($state)');
 
-		// We are going to need the authId in order to retrieve this authentication source later
-		$state[self::AUTHID] = $this->authId;
+        // We are going to need the authId in order to retrieve this authentication source later
+        $state[self::AUTHID] = $this->authId;
 
-		$stateID = SimpleSAML_Auth_State::getStateId($state);
-		SimpleSAML\Logger::debug('authlinkedin auth state id = ' . $stateID);
+        $stateID = SimpleSAML_Auth_State::getStateId($state);
+        SimpleSAML\Logger::debug('authlinkedin auth state id = ' . $stateID);
 
-		$consumer = new sspmod_oauth_Consumer($this->key, $this->secret);
+        $consumer = new sspmod_oauth_Consumer($this->key, $this->secret);
 
-		// Get the request token
-		$requestToken = $consumer->getRequestToken('https://api.linkedin.com/uas/oauth/requestToken', array('oauth_callback' => SimpleSAML\Module::getModuleUrl('authlinkedin') . '/linkback.php?stateid=' . $stateID));
+        // Get the request token
+        $requestToken = $consumer->getRequestToken(
+            'https://api.linkedin.com/uas/oauth/requestToken',
+            array('oauth_callback' => SimpleSAML\Module::getModuleUrl('authlinkedin') . '/linkback.php?stateid=' . $stateID)
+        );
 
-		SimpleSAML\Logger::debug("Got a request token from the OAuth service provider [" .
-			$requestToken->key . "] with the secret [" . $requestToken->secret . "]");
+        SimpleSAML\Logger::debug(
+            "Got a request token from the OAuth service provider [" .
+            $requestToken->key . "] with the secret [" . $requestToken->secret . "]"
+        );
 
-		$state['authlinkedin:requestToken'] = $requestToken;
+        $state['authlinkedin:requestToken'] = $requestToken;
 
-		// Update the state
-		SimpleSAML_Auth_State::saveState($state, self::STAGE_INIT);
+        // Update the state
+        SimpleSAML_Auth_State::saveState($state, self::STAGE_INIT);
 
-		// Authorize the request token
-		$consumer->getAuthorizeRequest('https://www.linkedin.com/uas/oauth/authenticate', $requestToken);
-	}
+        // Authorize the request token
+        $consumer->getAuthorizeRequest('https://www.linkedin.com/uas/oauth/authenticate', $requestToken);
+    }
 
 
-	public function finalStep(&$state) {
-		$requestToken = $state['authlinkedin:requestToken'];
+    public function finalStep(&$state) 
+    {
+        $requestToken = $state['authlinkedin:requestToken'];
 
-		$consumer = new sspmod_oauth_Consumer($this->key, $this->secret);
+        $consumer = new sspmod_oauth_Consumer($this->key, $this->secret);
 
-		SimpleSAML\Logger::debug("oauth: Using this request token [" .
-			$requestToken->key . "] with the secret [" . $requestToken->secret . "]");
+        SimpleSAML\Logger::debug(
+            "oauth: Using this request token [" .
+            $requestToken->key . "] with the secret [" . $requestToken->secret . "]"
+        );
 
-		// Replace the request token with an access token (via GET method)
-		$accessToken = $consumer->getAccessToken('https://api.linkedin.com/uas/oauth/accessToken', $requestToken,
-			array('oauth_verifier' => $state['authlinkedin:oauth_verifier']));
+        // Replace the request token with an access token (via GET method)
+        $accessToken = $consumer->getAccessToken(
+            'https://api.linkedin.com/uas/oauth/accessToken', $requestToken,
+            array('oauth_verifier' => $state['authlinkedin:oauth_verifier'])
+        );
 
-		SimpleSAML\Logger::debug("Got an access token from the OAuth service provider [" .
-			$accessToken->key . "] with the secret [" . $accessToken->secret . "]");
+        SimpleSAML\Logger::debug(
+            "Got an access token from the OAuth service provider [" .
+            $accessToken->key . "] with the secret [" . $accessToken->secret . "]"
+        );
 
-		$userdata = $consumer->getUserInfo('https://api.linkedin.com/v1/people/~:(' . $this->attributes . ')', $accessToken, array('http' => array('header' => 'x-li-format: json')));
+        $userdata = $consumer->getUserInfo(
+            'https://api.linkedin.com/v1/people/~:(' . $this->attributes . ')',
+            $accessToken, 
+            array('http' => array('header' => 'x-li-format: json'))
+        );
 
         $attributes = $this->flatten($userdata, 'linkedin.');
 
-		// TODO: pass accessToken: key, secret + expiry as attributes?
+        // TODO: pass accessToken: key, secret + expiry as attributes?
 
-		if (array_key_exists('id', $userdata) ) {
-			$attributes['linkedin_targetedID'] = array('http://linkedin.com!' . $userdata['id']);
-			$attributes['linkedin_user'] = array($userdata['id'] . '@linkedin.com');
-		}
+        if (array_key_exists('id', $userdata)) {
+            $attributes['linkedin_targetedID'] = array('http://linkedin.com!' . $userdata['id']);
+            $attributes['linkedin_user'] = array($userdata['id'] . '@linkedin.com');
+        }
 
-		SimpleSAML\Logger::debug('LinkedIn Returned Attributes: '. implode(", ",array_keys($attributes)));
+        SimpleSAML\Logger::debug('LinkedIn Returned Attributes: '. implode(", ",array_keys($attributes)));
 
-		$state['Attributes'] = $attributes;
-	}
+        $state['Attributes'] = $attributes;
+    }
 
     /**
      * takes an associative array, traverses it and returns the keys concatenated with a dot
@@ -148,7 +167,8 @@ class sspmod_authlinkedin_Auth_Source_LinkedIn extends SimpleSAML_Auth_Source {
      *
      * @return array the array with the new concatenated keys
      */
-    protected function flatten($array, $prefix = '') {
+    protected function flatten($array, $prefix = '')
+    {
         $result = array();
         foreach ($array as $key => $value) {
             if (is_array($value)) {
diff --git a/modules/authtwitter/lib/Auth/Source/Twitter.php b/modules/authtwitter/lib/Auth/Source/Twitter.php
index 2123373b78b6e03512f132f6817e45836850a824..b20a82be6a0c81ce80a10363b8faffc279255b06 100644
--- a/modules/authtwitter/lib/Auth/Source/Twitter.php
+++ b/modules/authtwitter/lib/Auth/Source/Twitter.php
@@ -43,6 +43,7 @@ class sspmod_authtwitter_Auth_Source_Twitter extends SimpleSAML_Auth_Source {
 		$this->key = $configObject->getString('key');
 		$this->secret = $configObject->getString('secret');
 		$this->force_login = $configObject->getBoolean('force_login', FALSE);
+		$this->include_email = $configObject->getBoolean('include_email', FALSE);
 	}
 
 
@@ -103,8 +104,12 @@ class sspmod_authtwitter_Auth_Source_Twitter extends SimpleSAML_Auth_Source {
 		$accessToken = $consumer->getAccessToken('https://api.twitter.com/oauth/access_token', $requestToken, $parameters);
 		SimpleSAML\Logger::debug("Got an access token from the OAuth service provider [" .
 			$accessToken->key . "] with the secret [" . $accessToken->secret . "]");
-			
-		$userdata = $consumer->getUserInfo('https://api.twitter.com/1.1/account/verify_credentials.json', $accessToken);
+
+		$verify_credentials_url = 'https://api.twitter.com/1.1/account/verify_credentials.json';
+		if ($this->include_email) {
+			$verify_credentials_url = $verify_credentials_url . '?include_email=true';
+		}
+		$userdata = $consumer->getUserInfo($verify_credentials_url, $accessToken);
 		
 		if (!isset($userdata['id_str']) || !isset($userdata['screen_name'])) {
 			throw new SimpleSAML_Error_AuthSource($this->authId, 'Authentication error: id_str and screen_name not set.');
diff --git a/modules/core/dictionaries/frontpage.translation.json b/modules/core/dictionaries/frontpage.translation.json
index 54d8ac7ca9c3ed987d3059de0ed909753975c284..767bf766fc5b72fa190c5df32cdd845e2acf7f47 100644
--- a/modules/core/dictionaries/frontpage.translation.json
+++ b/modules/core/dictionaries/frontpage.translation.json
@@ -21,7 +21,7 @@
 		"it": "<strong>Congratulazioni<\/strong>, hai installato SimpleSAMLphp con successo. Questa &egrave; la pagina di riferimento della tua installazione, qui puoi trovare i link ad esempi di test, diagnostiche, metadati e alla documentazione relativa.",
 		"lt": "<strong>Sveikiname<\/strong>, J\u016bs s\u0117kmingai \u012fdieg\u0117te SimpleSAMLphp. Tai pradinis diegimo puslapis, kur J\u016bs rasite nuorodas \u012f testavimo pavyzd\u017eius, diagnostik\u0105, metaduomenis ir netgi nuorodas \u012f susijusi\u0105 dokumentacij\u0105.",
 		"ja": "<strong>\u304a\u3081\u3067\u3068\u3046<\/strong>, \u3042\u306a\u305f\u306f SimpleSAMLphp \u306e\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u306b\u6210\u529f\u3057\u307e\u3057\u305f\u3002\u3053\u306e\u30da\u30fc\u30b8\u306f\u8a2d\u5b9a\u3092\u884c\u3046\u305f\u3081\u306e\u30b9\u30bf\u30fc\u30c8\u30da\u30fc\u30b8\u3067\u3059\u3002\u30c6\u30b9\u30c8\u3001\u8a3a\u65ad\u3001\u30e1\u30bf\u30c7\u30fc\u30bf\u3084\u95a2\u9023\u3059\u308b\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u3078\u306e\u30ea\u30f3\u30af\u3092\u898b\u3064\u3051\u308b\u3067\u3057\u3087\u3046\u3002",
-		"zh-tw": "<strong>\u606d\u559c\u4f60<\/strong>\uff0c\u60a8\u5df2\u7d93\u6210\u529f\u7684\u5b89\u88dd SimpleSAMLphp\u3002\u9019\u662f\u5b89\u88dd\u7684\u958b\u59cb\u9801\u9762\uff0c\u5728\u9019\u88e1\u60a8\u53ef\u4ee5\u627e\u5230\u6e2c\u8a66\u7bc4\u672c\u3001\u8a3a\u65b7\u5de5\u5177\u3001\u8a6e\u91cb\u8cc7\u6599\u53ca\u5404\u7a2e\u76f8\u95dc\u6587\u4ef6\u7684\u9023\u7d50\u3002",
+		"zh-tw": "<strong>\u606d\u559c\u60a8<\/strong>\uff0c\u60a8\u5df2\u7d93\u6210\u529f\u7684\u5b89\u88dd SimpleSAMLphp \u4e86\u3002\u9019\u662f\u60a8\u5b89\u88dd\u7684\u555f\u59cb\u9801\u9762\uff0c\u5728\u9019\u88e1\u60a8\u53ef\u4ee5\u627e\u5230\u6e2c\u8a66\u7bc4\u672c\u3001\u8a3a\u65b7\u5de5\u5177\u3001Metadata \u53ca\u5404\u7a2e\u76f8\u95dc\u6587\u4ef6\u7684\u9023\u7d50\u3002",
 		"ru": "<strong>\u041f\u043e\u0437\u0434\u0440\u0430\u0432\u043b\u044f\u0435\u043c<\/strong>, \u0412\u044b \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0438 SimpleSAMLphp. \u042d\u0442\u043e \u0441\u0442\u0430\u0440\u0442\u043e\u0432\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0432\u0430\u0448\u0435\u0439 \u0438\u043d\u0441\u0442\u0430\u043b\u043b\u044f\u0446\u0438\u0438, \u0433\u0434\u0435 \u0432\u044b \u043d\u0430\u0439\u0434\u0435\u0442\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u044b \u0442\u0435\u0441\u0442\u043e\u0432, \u0434\u0438\u0430\u0433\u043d\u043e\u0441\u0442\u0438\u043a\u0443, \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0438 \u0434\u0430\u0436\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044e.",
 		"et": "<strong>Palju \u00f5nne!<\/strong> Oled edukalt paigaldanud SimpleSAMLphp. See on sinu paigalduse avaleht, millelt leiad viited nii testn\u00e4idetele, diagnostikalehe, metaandmete kui ka vajaliku dokumentatsiooni juurde.",
 		"he": "<strong>\u05de\u05d6\u05dc \u05d8\u05d5\u05d1<\/strong>,SimpleSAMLphp. \u05d4\u05ea\u05e7\u05e0\u05ea \u05d4\u05d1\u05d4\u05e6\u05dc\u05d7\u05d4 \u05d0\u05ea \u05d6\u05d4 \u05d3\u05e3 \u05d4\u05d4\u05ea\u05d7\u05dc\u05d4 \u05e9\u05dc \u05d4\u05d4\u05ea\u05e7\u05e0\u05d4 \u05e9\u05dc\u05da, \u05d4\u05d9\u05db\u05df \u05e9\u05ea\u05de\u05e6\u05d0 \u05e7\u05d9\u05e9\u05d5\u05e8\u05d9\u05dd \u05dc\u05d3\u05d5\u05d2\u05de\u05d0\u05d5\u05ea \u05dc\u05d1\u05d3\u05d9\u05e7\u05d4, \u05d0\u05d1\u05d7\u05d5\u05df, \u05de\u05d8\u05d0-\u05e0\u05ea\u05d5\u05e0\u05d9\u05dd \u05d5\u05d0\u05e4\u05d9\u05dc\u05d5 \u05e7\u05d9\u05e9\u05d5\u05e8\u05d9\u05dd \u05dc\u05ea\u05e2\u05d5\u05d3 \u05d4\u05de\u05ea\u05d0\u05d9\u05dd. ",
@@ -57,7 +57,7 @@
 		"it": "Link utili per la tua installazione",
 		"lt": "Naudingos nuorodos diegimui",
 		"ja": "\u8a2d\u5b9a\u306b\u4fbf\u5229\u306a\u30ea\u30f3\u30af\u96c6",
-		"zh-tw": "\u5df2\u5b89\u88dd\u7684\u5e38\u7528\u9023\u7d50",
+		"zh-tw": "\u5df2\u5b89\u88dd\u7684\u6709\u7528\u9023\u7d50",
 		"ru": "\u041f\u043e\u043b\u0435\u0437\u043d\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 \u0434\u043b\u044f \u0432\u0430\u0448\u0435\u0439 \u0438\u043d\u0441\u0442\u0430\u043b\u043b\u044f\u0446\u0438\u0438",
 		"et": "Kasulikud viited",
 		"he": "\u05e7\u05d9\u05e9\u05d5\u05e8\u05d9\u05dd \u05e9\u05d9\u05de\u05d5\u05e9\u05d9\u05d9\u05dd \u05dc\u05d4\u05ea\u05e7\u05e0\u05d4 \u05e9\u05dc\u05da",
@@ -92,7 +92,7 @@
 		"it": "Metadati",
 		"lt": "Metaduomenys",
 		"ja": "\u30e1\u30bf\u30c7\u30fc\u30bf",
-		"zh-tw": "\u8a6e\u91cb\u8cc7\u6599",
+		"zh-tw": "Metadata",
 		"ru": "\u041c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435",
 		"et": "Metaandmed",
 		"he": "\u05de\u05d8\u05d0-\u05e0\u05ea\u05d5\u05e0\u05d9\u05dd",
@@ -231,7 +231,7 @@
 		"it": "Questo SimpleSAMLphp &egrave; davvero un bel prodotto, dove trovo ulteriori informazioni a riguardo? Puoi trovare maggiori informazioni su <a href=\"http:\/\/rnd.feide.no\/simplesamlphp\">SimpleSAMLphp nel Blog di Feide RnD<\/a> oltre che <a href=\"http:\/\/uninett.no\">UNINETT<\/a>.",
 		"lt": "\u0160is SimpleSAMLphp dalykas yra gana puikus, kur gal\u0117\u010diau daugiau apie j\u012f paskaityti? Daugiau informacijos apie <a href=\"http:\/\/rnd.feide.no\/simplesamlphp\">SimpleSAMLphp galite rasti Feide RnD blog'e<\/a> bei <a href=\"http:\/\/uninett.no\">UNINETT<\/a> svetain\u0117je.",
 		"ja": "\u3053\u306e SimpleSAMLphp \u306f\u7d20\u6674\u3089\u3057\u3044\u3082\u306e\u3067\u3059\u3002\u3053\u308c\u4ee5\u4e0a\u306e\u8aac\u660e\u304c\u5728\u308a\u307e\u3059\u304b\uff1f\u3053\u3061\u3089\u306e\u30ea\u30f3\u30af\u3067\u66f4\u306a\u308b\u60c5\u5831\u3092\u898b\u3064\u3051\u308b\u3053\u3068\u304c\u51fa\u6765\u307e\u3059 <a href=\"http:\/\/rnd.feide.no\/simplesamlphp\">SimpleSAMLphp at the Feide RnD blog<\/a>",
-		"zh-tw": "\u89ba\u5f97 SimpleSAMLphp \u9084\u883b\u9177\u7684\u561b\uff0c\u5728\u54ea\u88e1\u53ef\u4ee5\u627e\u5230\u66f4\u591a\u76f8\u95dc\u8cc7\u8a0a\uff1f\u4f60\u53ef\u4ee5\u5728\u4e0b\u5217\u7db2\u5740\u627e\u5230\u66f4\u591a\u76f8\u95dc\u8cc7\u8a0a\uff0c\u65bc <a href=\"http:\/\/uninett.no\">UNINETT<\/a> \u7684 <a href=\"http:\/\/rnd.feide.no\/simplesamlphp\">SimpleSAMLphp at the Feide RnD \u958b\u767c\u8005\u90e8\u843d\u683c<\/a>",
+		"zh-tw": "\u89ba\u5f97 SimpleSAMLphp \u9084\u883b\u9177\u7684\u561b\uff0c\u5728\u54ea\u53ef\u4ee5\u627e\u5230\u66f4\u591a\u76f8\u95dc\u8cc7\u8a0a\u5462\uff1f\u4f60\u53ef\u4ee5\u5728\u4e0b\u5217\u7db2\u5740\u627e\u5230\u66f4\u591a\u76f8\u95dc\u8cc7\u8a0a\uff0c\u65bc <a href=\"http:\/\/uninett.no\">UNINETT<\/a> \u7684 <a href=\"https:\/\/simplesamlphp.org\/\">SimpleSAMLphp<\/a>",
 		"ru": "SimpleSAMLphp - \u0432\u0435\u0449\u044c \u043a\u043b\u0430\u0441\u0441\u043d\u0430\u044f, \u0433\u0434\u0435 \u044f \u043c\u043e\u0433\u0443 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 \u043e\u0431 \u044d\u0442\u043e\u043c? \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u0439\u0442\u0438 \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e <a href=\"http:\/\/rnd.feide.no\/simplesamlphp\">SimpleSAMLphp \u0432 \u0431\u043b\u043e\u0433\u0435 Feide RnD<\/a> \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0430 <a href=\"http:\/\/uninett.no\">UNINETT<\/a>.",
 		"et": "See SimpleSAMLphp on p\u00e4ris \u00e4ge! Kust ma saaks selle kohta t\u00e4psemalt lugeda? Rohkem infot leiad <a href=\"http:\/\/rnd.feide.no\/simplesamlphp\">SimpleSAMLphp blogist<\/a>, mis asub <a href=\"http:\/\/uninett.no\">UNINETT<\/a>-is.",
 		"he": "\u05d4-SimpleSAMLphp \u05d4\u05d6\u05d4 \u05d4\u05d5\u05d0 \u05de\u05d4 \u05d6\u05d4 \u05de\u05d2\u05e0\u05d9\u05d1, \u05d0\u05d9\u05dd\u05d4 \u05d0\u05e0\u05d9 \u05d9\u05db\u05d5\u05dc \u05dc\u05e7\u05e8\u05d5\u05d0 \u05d9\u05d5\u05ea\u05e8 \u05e2\u05dc\u05d9\u05d5?\u05d0\u05ea\u05d4 \u05d9\u05db\u05d5\u05dc \u05dc\u05de\u05e6\u05d5\u05d0 \u05de\u05d9\u05d3\u05e2 \u05e0\u05d5\u05e1\u05e3 \u05d1- <a href=\"http:\/\/rnd.feide.no\/simplesamlphp\"> SimpleSAMLphp \u05d1 Feide \u05d1\u05d1\u05dc\u05d5\u05d2 \u05d4\u05e4\u05d9\u05ea\u05d5\u05d7 \u05e9\u05dc \u05e9\u05e0\u05de\u05e6\u05d0 \u05d1- <a href=\"http:\/\/uninett.no\">UNINETT<\/a>. ",
@@ -267,7 +267,7 @@
 		"it": "Obbligatorio",
 		"lt": "B\u016btinas",
 		"ja": "\u5fc5\u9808",
-		"zh-tw": "\u8acb\u6c42",
+		"zh-tw": "\u9700\u6c42",
 		"ru": "\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439",
 		"et": "N\u00f5utav",
 		"he": "\u05d3\u05e8\u05d5\u05e9",
@@ -302,7 +302,7 @@
 		"it": "Obbligatorio per LDAP",
 		"lt": "B\u016btinas LDAP serveriui",
 		"ja": "LDAP\u306b\u5fc5\u8981",
-		"zh-tw": "\u9700\u8981 LDAP",
+		"zh-tw": "LDAP \u7684\u9700\u6c42",
 		"ru": "\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0434\u043b\u044f LDAP",
 		"et": "N\u00f5utav LDAP-ile",
 		"he": "\u05d3\u05e8\u05d5\u05e9 \u05e2\u05d1\u05d5\u05e8 LDAP",
@@ -337,7 +337,7 @@
 		"it": "Obbligatorio per Radius",
 		"lt": "B\u016btinas Radius serveriui",
 		"ja": "Radius\u306b\u5fc5\u8981",
-		"zh-tw": "\u9700\u8981 Radius",
+		"zh-tw": "Radius \u7684\u9700\u6c42",
 		"ru": "\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0434\u043b\u044f Radius",
 		"et": "N\u00f5utav Radiusele",
 		"he": "\u05d3\u05e8\u05d5\u05e9 \u05e2\u05d1\u05d5\u05e8 Radius",
@@ -406,7 +406,7 @@
 		"it": "Raccomandato",
 		"lt": "Rekomenduojamas",
 		"ja": "\u63a8\u5968",
-		"zh-tw": "\u5efa\u8b70",
+		"zh-tw": "\u63a8\u85a6",
 		"ru": "\u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u043c\u044b\u0439",
 		"et": "Soovitatav",
 		"he": "\u05e8\u05e6\u05d5\u05d9",
@@ -476,7 +476,7 @@
 		"it": "<strong>Non stai usando HTTPS<\/strong> - comunicazione cifrata con l'utente. HTTP pu&ograve; funzionare per i test, ma in un ambiente di produzione si dovrebbe usare HTTPS. [ <a href=\"http:\/\/rnd.feide.no\/content\/simplesamlphp-maintenance-and-configuration\">Maggiori informazioni sulla manutenzione di SimpleSAMLphp<\/a> ]",
 		"lt": "<strong>J\u016bs nenaudojate HTTPS<\/strong> - \u0161ifruotos komunikacijos su vartotoju. HTTP puikiai tinka testavimo reikm\u0117ms, ta\u010diau realioje aplinkoje tur\u0117tum\u0117te naudoti HTTPS. [ <a href=\"http:\/\/rnd.feide.no\/content\/simplesamlphp-maintenance-and-configuration\">Skaityti daugiau apie SimpleSAMLphp prie\u017ei\u016br\u0105<\/a> ]",
 		"ja": "<strong>\u3042\u306a\u305f\u306fHTTPS(\u6697\u53f7\u5316\u901a\u4fe1)\u3092\u884c\u3063\u3066\u3044\u307e\u305b\u3093\u3002<\/strong>HTTP\u306f\u30c6\u30b9\u30c8\u74b0\u5883\u3067\u3042\u308c\u3070\u6b63\u5e38\u306b\u52d5\u4f5c\u3057\u307e\u3059\u3001\u3057\u304b\u3057\u88fd\u54c1\u74b0\u5883\u3067\u306fHTTPS\u3092\u4f7f\u7528\u3059\u308b\u3079\u304d\u3067\u3059\u3002[ <a href=\"http:\/\/rnd.feide.no\/content\/simplesamlphp-maintenance-and-configuration\">\u8a73\u3057\u304f\u306f SimpleSAMLphp \u30e1\u30f3\u30c6\u30ca\u30f3\u30b9\u60c5\u5831\u3092\u8aad\u3093\u3067\u304f\u3060\u3055\u3044\u3002<\/a> ]",
-		"zh-tw": "<strong>\u60a8\u4e0d\u662f\u4f7f\u7528 HTTPS <\/strong>-\u65bc\u4f7f\u7528\u7684\u50b3\u8f38\u904e\u7a0b\u4e2d\u52a0\u5bc6\u3002HTTP \u53ef\u4ee5\u6b63\u5e38\u7684\u5229\u7528\u65bc\u6e2c\u8a66\uff0c\u4f46\u662f\u5728\u4e0a\u7dda\u74b0\u5883\u88e1\uff0c\u60a8\u9084\u662f\u9700\u8981\u4f7f\u7528 HTTPS\u3002[ <a href=\"http:\/\/rnd.feide.no\/content\/simplesamlphp-maintenance-and-configuration\">\u95b1\u8b80\u66f4\u591a\u6709\u95dc\u65bc SimpleSAMLphp \u7684\u7dad\u8b77\u65b9\u5f0f<\/a> ]",
+		"zh-tw": "<strong>\u60a8\u672a\u4f7f\u7528 HTTPS <\/strong> \u5c0d\u50b3\u8f38\u904e\u7a0b\u9032\u884c\u52a0\u5bc6\u901a\u8a0a\u3002HTTP \u53ef\u4ee5\u6b63\u5e38\u7684\u904b\u4f5c\u65bc\u6e2c\u8a66\u74b0\u5883\u3002\u4f46\u5728\u6b63\u5f0f\u74b0\u5883\u88e1\uff0c\u60a8\u61c9\u8a72\u8981\u4f7f\u7528 HTTPS\u3002[ <a href=\"https:\/\/simplesamlphp.org\/docs\/stable\/simplesamlphp-maintenance\">\u95b1\u8b80\u66f4\u591a\u6709\u95dc\u65bc SimpleSAMLphp \u7684\u7dad\u8b77\u65b9\u5f0f<\/a> ]",
 		"ru": "<strong>\u0412\u044b \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 HTTPS<\/strong> - \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c. HTTP \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0445\u043e\u0440\u043e\u0448\u043e \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0445 \u0446\u0435\u043b\u0435\u0439, \u043d\u043e \u0432 \u044d\u043a\u043f\u043b\u0443\u0430\u0442\u0430\u0446\u0438\u0438 \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c HTTPS. [ <a href=\"http:\/\/rnd.feide.no\/content\/simplesamlphp-maintenance-and-configuration\">\u0423\u0437\u043d\u0430\u0439\u0442\u0435 \u0431\u043e\u043b\u044c\u0448\u0435 \u043e\u0431 \u043e\u0431\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u043d\u0438\u0438 SimpleSAMLphp<\/a> ]",
 		"et": "<strong>Sa ei kasuta andmete vahetamiseks HTTPS kr\u00fcpteeritud sidet<\/strong>. HTTP sobib testimiseks h\u00e4sti, kuid toodangus peaksid kindlasti HTTPS-i kasutama. [ <a href=\"http:\/\/rnd.feide.no\/content\/simplesamlphp-maintenance-and-configuration\">Loe t\u00e4psemalt SimpleSAMLphp hooldamisest<\/a> ]",
 		"he": "<strong>\u05d0\u05ea\u05d4 \u05dc\u05d0 \u05de\u05e9\u05ea\u05de\u05e9 \u05d1- HTTPS <\/strong> - \u05d4\u05ea\u05e7\u05e9\u05e8\u05d5\u05ea \u05de\u05d5\u05e6\u05e4\u05e0\u05ea \u05e2\u05dd \u05d4\u05de\u05e9\u05ea\u05de\u05e9. HTTP \u05e2\u05d5\u05d1\u05d3 \u05d1\u05e1\u05d3\u05e8 \u05dc\u05de\u05d8\u05e8\u05d5\u05ea \u05d1\u05d3\u05d9\u05e7\u05d4, \u05d0\u05d5\u05dc\u05dd \u05dc\u05de\u05e2\u05e8\u05db\u05d5\u05ea \u05d0\u05de\u05d9\u05ea\u05d9\u05d5\u05ea, \u05db\u05d3\u05d9 \u05dc\u05d4\u05e9\u05ea\u05de\u05e9 \u05d4 HTTPS. [ <a href=\"http:\/\/rnd.feide.no\/content\/simplesamlphp-maintenance-and-configuration\"> \u05e7\u05e8\u05d0 \u05e2\u05d5\u05d3 \u05e2\u05dc \u05ea\u05d7\u05d6\u05d5\u05e7 SimpleSAMLphp <\/a> ]",
@@ -581,7 +581,7 @@
 		"it": "OpenID Provider site - versione Alpha (codice di test)",
 		"lt": "OpenID tiek\u0117jo puslapis - alfa versija (bandomasis kodas)",
 		"ja": "OpenID \u30d7\u30ed\u30d0\u30a4\u30c0\u30b5\u30a4\u30c8 - \u30a2\u30eb\u30d5\u30a1\u30d0\u30fc\u30b8\u30e7\u30f3 (\u30c6\u30b9\u30c8\u30b3\u30fc\u30c9)",
-		"zh-tw": "OpenID \u63d0\u4f9b\u7db2\u7ad9 - \u958b\u767c\u7248\u672c(\u6e2c\u8a66\u78bc)",
+		"zh-tw": "OpenID \u63d0\u4f9b\u7ad9\u53f0 - \u958b\u767c (Alpha) \u7248\u672c (\u6e2c\u8a66\u78bc)",
 		"ru": "\u0421\u0430\u0439\u0442 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 OpenID - \u0410\u043b\u044c\u0444\u0430 \u0432\u0435\u0440\u0441\u0438\u044f (\u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u043a\u043e\u0434)",
 		"et": "OpenID pakkuja lehek\u00fclg - alfaversioon (testkood)",
 		"he": "\u05d0\u05ea\u05e8 \u05e1\u05e4\u05e7 OpenID - \u05d2\u05d9\u05e8\u05e1\u05ea \u05d0\u05dc\u05e4\u05d0 (\u05e7\u05d5\u05d3 \u05d1\u05d3\u05d9\u05e7\u05d4(",
@@ -616,7 +616,7 @@
 		"it": "Diagnostica su nome dell'host, porta e protocollo",
 		"lt": "Serverio vardo, porto ir protokolo diagnostika",
 		"ja": "\u30db\u30b9\u30c8\u30cd\u30fc\u30e0\u3084\u30dd\u30fc\u30c8\u3001\u30d7\u30ed\u30c8\u30b3\u30eb\u3092\u8a3a\u65ad",
-		"zh-tw": "\u8a3a\u65b7\u4e3b\u6a5f\u540d\u7a31\uff0c\u9023\u63a5\u57e0\u53ca\u5354\u5b9a",
+		"zh-tw": "\u8a3a\u65b7\u4e3b\u6a5f\u540d\u7a31\u3001\u9023\u63a5\u57e0\u53ca\u901a\u8a0a\u5354\u5b9a",
 		"ru": "\u0414\u0438\u0430\u0433\u043d\u043e\u0441\u0442\u0438\u043a\u0430 \u0438\u043c\u0435\u043d\u0438 \u0445\u043e\u0441\u0442\u0430, \u043f\u043e\u0440\u0442\u0430 \u0438 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430",
 		"et": "Serverinime, pordi ja protokolli diagnostika",
 		"he": "\u05d0\u05d9\u05d1\u05d7\u05d5\u05df \u05e2\u05dc \u05e9\u05dd \u05de\u05d7\u05e9\u05d1, \u05e4\u05d5\u05e8\u05d8 \u05d5\u05e4\u05e8\u05d5\u05d8\u05d5\u05e7\u05d5\u05dc",
@@ -686,7 +686,7 @@
 		"it": "Panoramica dei metadati della tua installazione. Diagnostica dei file dei metadati",
 		"lt": "Diegimo metaduomen\u0173 per\u017ei\u016bra. Galite analizuoti savo metaduomenis",
 		"ja": "\u30e1\u30bf\u30c7\u30fc\u30bf\u306e\u6982\u8981\u3002\u30e1\u30bf\u30c7\u30fc\u30bf\u30d5\u30a1\u30a4\u30eb\u3092\u8a3a\u65ad\u3057\u307e\u3059\u3002",
-		"zh-tw": "\u60a8\u5b89\u88dd\u7684\u8a6e\u91cb\u8cc7\u6599\u6982\u89c0\u3002\u8a3a\u65b7\u60a8\u7684\u8a6e\u91cb\u8cc7\u6599\u6a94\u6848",
+		"zh-tw": "\u60a8\u5b89\u88dd\u7684 Metadata \u6982\u89c0\u3002\u8a3a\u65b7\u60a8\u7684 Metadata \u6a94\u6848",
 		"ru": "\u041e\u0431\u0437\u043e\u0440 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u0438\u043d\u0441\u0442\u0430\u043b\u043b\u044f\u0446\u0438\u0438. \u0414\u0438\u0430\u0433\u043d\u043e\u0441\u0442\u0438\u043a\u0430 \u0432\u0430\u0448\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445.",
 		"et": "\u00dclevaade sinu paigalduse metaandmetest. Diagnoosi oma metaandmete faile",
 		"he": "\u05e1\u05e7\u05d9\u05e8\u05ea \u05de\u05d8\u05d0-\u05e0\u05ea\u05d5\u05e0\u05d9\u05dd \u05dc\u05d4\u05ea\u05e7\u05e0\u05d4 \u05e9\u05dc\u05da. \u05d0\u05d1\u05d7\u05df \u05d0\u05ea \u05e7\u05d1\u05e6\u05d9 \u05de\u05d8\u05d0-\u05d4\u05e0\u05ea\u05d5\u05e0\u05d9\u05dd \u05e9\u05dc\u05da",
@@ -721,7 +721,7 @@
 		"it": "Metadati del Service Provider SAML 2.0 Locale (generati automaticamente)",
 		"lt": "Vietinio SAML 2.0 SP metaduomenys (sugeneruoti automati\u0161kai)",
 		"ja": "\u30db\u30b9\u30c8 SAML 2.0 \u30b5\u30fc\u30d3\u30b9\u30d7\u30ed\u30d0\u30a4\u30c0\u30e1\u30bf\u30c7\u30fc\u30bf(\u81ea\u52d5\u751f\u6210)",
-		"zh-tw": "\u8a17\u7ba1 SAML 2.0 \u670d\u52d9\u63d0\u4f9b\u8005\u8a6e\u91cb\u8cc7\u6599(\u81ea\u52d5\u7522\u751f)",
+		"zh-tw": "\u672c\u5730 SAML 2.0 \u670d\u52d9\u63d0\u4f9b\u8005 Matadata (\u81ea\u52d5\u7522\u751f)",
 		"et": "Hostitud SAML 2.0. teenusepakkuja metaandmed (automaatselt genereeritud)",
 		"he": "\u05de\u05d8\u05d0-\u05d4\u05e0\u05ea\u05d5\u05e0\u05d9\u05dd \u05e9\u05dc \u05e1\u05e4\u05e7 \u05d4\u05e9\u05d9\u05e8\u05d5\u05ea\u05d9\u05dd \u05de\u05e1\u05d5\u05d2 SAML 2.0 \u05d4\u05de\u05d0\u05d5\u05e8\u05d7 (\u05e0\u05d5\u05e6\u05e8 \u05d0\u05d5\u05d8\u05d5\u05de\u05d8\u05d9\u05ea)",
 		"ru": "\u041c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 SAML 2.0 \u041f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 \u0423\u0441\u043b\u0443\u0433 (SP) (\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438)",
@@ -756,7 +756,7 @@
 		"it": "Metadati dell'Identity Provider SAML 2.0 Locale (generati automaticamente)",
 		"lt": "Vietinio SAML 2.0 IdP metaduomenys (sugeneruoti automati\u0161kai)",
 		"ja": "\u30db\u30b9\u30c8 SAML 2.0 \u30a2\u30a4\u30c7\u30f3\u30c6\u30a3\u30c6\u30a3\u30d7\u30ed\u30d0\u30a4\u30c0\u30e1\u30bf\u30c7\u30fc\u30bf(\u81ea\u52d5\u751f\u6210)",
-		"zh-tw": "\u8a17\u7ba1 SAML 2.0 \u9a57\u8b49\u63d0\u4f9b\u8005\u8a6e\u91cb\u8cc7\u6599(\u81ea\u52d5\u7522\u751f)",
+		"zh-tw": "\u672c\u5730 SAML 2.0 \u9a57\u8b49\u63d0\u4f9b\u8005 Matadata (\u81ea\u52d5\u7522\u751f)",
 		"et": "Hostitud SAML 2.0 identiteedipakkuja metaandmed (automaatselt genereeritud) ",
 		"he": "\u05de\u05d8\u05d0-\u05d4\u05e0\u05ea\u05d5\u05e0\u05d9\u05dd \u05e9\u05dc \u05e1\u05e4\u05e7 \u05d4\u05d6\u05d4\u05d5\u05d9\u05d5\u05ea \u05de\u05e1\u05d5\u05d2 SAML 2.0 \u05d4\u05de\u05d0\u05d5\u05e8\u05d7 (\u05e0\u05d5\u05e6\u05e8 \u05d0\u05d5\u05d8\u05d5\u05de\u05d8\u05d9\u05ea)",
 		"ru": "\u041c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 SAML 2.0 \u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430 \u043f\u043e\u0434\u043b\u0438\u043d\u043d\u043e\u0441\u0442\u0438 (IdP) (\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438)",
@@ -791,7 +791,7 @@
 		"it": "Metadati del Service Provider Shibboleth 1.3 Locale (generati automaticamente)",
 		"ja": "\u30db\u30b9\u30c8 Shibboleth 1.3 \u30b5\u30fc\u30d3\u30b9\u30d7\u30ed\u30d0\u30a4\u30c0\u30e1\u30bf\u30c7\u30fc\u30bf(\u81ea\u52d5\u751f\u6210)",
 		"lt": "Vietinio Shibboleth 1.3 paslaugos teik\u0117jo (SP) metaduomenys (sugeneruoti automati\u0161kai)",
-		"zh-tw": "\u8a17\u7ba1 Shibboleth 1.3 \u670d\u52d9\u63d0\u4f9b\u8005\u8a6e\u91cb\u8cc7\u6599(\u81ea\u52d5\u7522\u751f)",
+		"zh-tw": "\u672c\u5730 Shibboleth 1.3 \u670d\u52d9\u63d0\u4f9b\u8005 Matadata (\u81ea\u52d5\u7522\u751f)",
 		"et": "Hostitud Shibboleth 1.3 identiteedipakkuja metaandmed (automaatselt genereeritud) ",
 		"he": "\u05de\u05d8\u05d0-\u05d4\u05e0\u05ea\u05d5\u05e0\u05d9\u05dd \u05e9\u05dc \u05e1\u05e4\u05e7 \u05d4\u05e9\u05d9\u05e8\u05d5\u05ea\u05d9\u05dd \u05de\u05e1\u05d5\u05d2 Shibboleth 1.3 \u05d4\u05de\u05d0\u05d5\u05e8\u05d7 (\u05e0\u05d5\u05e6\u05e8 \u05d0\u05d5\u05d8\u05d5\u05de\u05d8\u05d9\u05ea)",
 		"ru": "\u041c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 Shibboleth 1.3 \u041f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 \u0423\u0441\u043b\u0443\u0433 (SP) (\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438)",
@@ -826,7 +826,7 @@
 		"it": "Metadati dell'Identity Provider Shibboleth 1.3 Locale (generati automaticamente)",
 		"ja": "\u30db\u30b9\u30c8 Shibboleth 1.3 \u30a2\u30a4\u30c7\u30f3\u30c6\u30a3\u30c6\u30a3\u30d7\u30ed\u30d0\u30a4\u30c0\u30e1\u30bf\u30c7\u30fc\u30bf (\u81ea\u52d5\u751f\u6210)",
 		"lt": "Vietinio Shibboleth 1.3 tapatyb\u0117s teik\u0117jo (IdP) metaduomenys (sugeneruoti automati\u0161kai)",
-		"zh-tw": "\u8a17\u7ba1Shibboleth 1.3 \u9a57\u8b49\u63d0\u4f9b\u8005\u8a6e\u91cb\u8cc7\u6599(\u81ea\u52d5\u7522\u751f)",
+		"zh-tw": "\u672c\u5730 Shibboleth 1.3 \u9a57\u8b49\u63d0\u4f9b\u8005 Matadata (\u81ea\u52d5\u7522\u751f)",
 		"et": "Hostitud Shibboleth 2.0 identiteedipakkuja metaandmed (automaatselt genereeritud) ",
 		"he": "\u05de\u05d8\u05d0-\u05d4\u05e0\u05ea\u05d5\u05e0\u05d9\u05dd \u05e9\u05dc \u05e1\u05e4\u05e7 \u05d4\u05e9\u05d6\u05d4\u05d5\u05d9\u05d5\u05ea \u05de\u05e1\u05d5\u05d2 Shibboleth 1.3 \u05d4\u05de\u05d0\u05d5\u05e8\u05d7 (\u05e0\u05d5\u05e6\u05e8 \u05d0\u05d5\u05d8\u05d5\u05de\u05d8\u05d9\u05ea)",
 		"ru": "\u041c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 Shibboleth 1.3 \u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430 \u043f\u043e\u0434\u043b\u0438\u043d\u043d\u043e\u0441\u0442\u0438 (IdP) (\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438)",
@@ -861,7 +861,7 @@
 		"it": "Convertitore di metadati dal formato XML al formato SimpleSAMLphp ",
 		"ja": "XML \u3092 SimpleSAMLphp\u30e1\u30bf\u30c7\u30fc\u30bf\u306b\u5909\u63db",
 		"lt": "XML \u012f SimpleSAMLphp metaduomen\u0173 vertimas",
-		"zh-tw": "XML \u81f3 SimpleSAMLphp \u8a6e\u91cb\u8cc7\u6599\u7ffb\u8b6f\u5668",
+		"zh-tw": "XML to SimpleSAMLphp Matadata \u8f49\u63db\u7a0b\u5f0f",
 		"ru": "\u041a\u043e\u043d\u0432\u0435\u0440\u0442\u043e\u0440 XML \u0432 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 SimpleSAMLphp",
 		"et": "XML-ist SimpleSAMLphp metaandmeteks teisendaja",
 		"he": "\u05de\u05de\u05d9\u05e8 XML \u05dc\u05de\u05d8\u05d0-\u05de\u05d9\u05d3\u05e2 \u05e9\u05dc SimpleSAMLphp",
@@ -1276,7 +1276,7 @@
 		"it": "Metadati",
 		"lt": "Metaduomenys",
 		"ja": "\u30e1\u30bf\u30c7\u30fc\u30bf",
-		"zh-tw": "\u8a6e\u91cb\u8cc7\u6599",
+		"zh-tw": "Metadata",
 		"ru": "\u041c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435",
 		"et": "Metaandmed",
 		"he": "\u05de\u05d8\u05d0-\u05e0\u05ea\u05d5\u05e0\u05d9\u05dd",
@@ -1344,7 +1344,7 @@
 		"it": "Mostra metadati",
 		"lt": "Parodyti metaduomenis",
 		"ja": "\u30e1\u30bf\u30c7\u30fc\u30bf\u3092\u8868\u793a",
-		"zh-tw": "\u986f\u793a\u8a6e\u91cb\u8cc7\u6599",
+		"zh-tw": "\u986f\u793a Metadata",
 		"ru": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435",
 		"et": "N\u00e4ita metaandmeid",
 		"he": "\u05d4\u05e8\u05d0\u05d4 \u05de\u05d8\u05d0-\u05e0\u05ea\u05d5\u05e0\u05d9\u05dd",
@@ -1621,13 +1621,14 @@
 	"warnings_secretsalt": {
 		"es": "<strong>Su configuraci&oacute;n est&aacute; usando el <em>salt<\/em> por defecto<\/strong>. Aseg&uacute;rese de modificar la opci&oacute;n de configuraci&oacute;n 'secretsalt' en entornos de producci&oacute;n. [<a href=\"https:\/\/simplesamlphp.org\/docs\/devel\/simplesamlphp-install\">Leer m&aacute;s sobre la configuraci&oacute;n de SimpleSAMLphp<\/a>]",
 		"ru": "<strong>\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u0435\u043a\u0440\u0435\u0442\u043d\u0443\u044e \u0441\u043e\u043b\u044c \u043f\u043e-\u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e<\/strong> - \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0432\u044b  \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 'secretsalt' \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 simpleSAML \u0432 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 \u0441\u0440\u0435\u0434\u0435. [<a href=\"https:\/\/simplesamlphp.org\/docs\/stable\/simplesamlphp-install\">\u041f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 \u043e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 SimpleSAMLphp<\/a> ]",
-		"zh-tw": "<strong>\u76ee\u524d\u8a2d\u5b9a\u6a94\u4f7f\u7528\u9810\u8a2d\u7684\u96dc\u6e4a\u53c3\u6578(salt)<\/strong> - \u5728\u60a8\u4e0a\u7dda\u904b\u4f5c\u524d\u8acb\u78ba\u8a8d\u60a8\u5df2\u65bc simpleSAML \u8a2d\u5b9a\u9801\u4e2d\u4fee\u6539\u9810\u8a2d\u7684 'secretsalt'\u3002[<a href=\"https:\/\/simplesamlphp.org\/docs\/stable\/simplesamlphp-install\">\u95b1\u8b80\u66f4\u591a\u95dc\u65bc SimpleSAMLphp \u8a2d\u5b9a\u503c<\/a> ]",
+		"zh-tw": "<strong>\u76ee\u524d\u8a2d\u5b9a\u6a94\u4f7f\u7528\u9810\u8a2d\u7684\u96dc\u6e4a\u53c3\u6578 (salt)<\/strong> - \u5728\u60a8\u4e0a\u7dda\u904b\u4f5c\u524d\u8acb\u78ba\u8a8d\u60a8\u5df2\u65bc simpleSAML \u8a2d\u5b9a\u9801\u4e2d\u4fee\u6539\u9810\u8a2d\u7684 'secretsalt'\u3002[<a href=\"https:\/\/simplesamlphp.org\/docs\/stable\/simplesamlphp-install\">\u95b1\u8b80\u66f4\u591a\u95dc\u65bc SimpleSAMLphp \u7684\u8a2d\u5b9a\u65b9\u5f0f<\/a> ]",
 		"nl": "<strong>De configuratie bevat de standaard secret salt<\/strong> - verander altijd de 'secretsalt'-optie in de simpleSAML-configuratie voor productieomgevingen. [<a href=\"https:\/\/simplesamlphp.org\/docs\/stable\/simplesamlphp-install\">Lees meer over het configureren van SimpleSAMLphp<\/a> ]",
 		"da": "<strong>Ops\u00e6tningen benytter standard 'secret salt'<\/strong> - s\u00f8rg for at \u00e6ndre standard indstillingen for 'secretsalt' i simpleSAML ops\u00e6tningen i produktionssystemer. [<a href=\"https:\/\/simplesamlphp.org\/docs\/stable\/simplesamlphp-install\">L\u00e6s mere om SimpleSAMLphp ops\u00e6tning.<\/a> ]",
 		"el": "<strong>\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03c4\u03b9\u03bc\u03ae \u03c4\u03bf\u03c5 \u03bc\u03c5\u03c3\u03c4\u03b9\u03ba\u03bf\u03cd \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd (salt)<\/strong> - \u03c6\u03c1\u03bf\u03bd\u03c4\u03af\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c4\u03c1\u03bf\u03c0\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03b1\u03c1\u03ac\u03bc\u03b5\u03c4\u03c1\u03bf 'secretsalt' \u03c3\u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c4\u03bf\u03c5 SimpleSAMLphp \u03c3\u03b5 \u03c0\u03b5\u03c1\u03b9\u03b2\u03ac\u03bb\u03bb\u03bf\u03bd \u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2. [<a href=\"https:\/\/simplesamlphp.org\/docs\/stable\/simplesamlphp-install\">\u0394\u03b9\u03b1\u03b2\u03ac\u03c3\u03c4\u03b5 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b1...<\/a> ]"
 	},
 	"warnings_outdated": {
 		"es": "Su instalaci&oacute;n de SimpleSAMLphp est&aacute; desactualizada. Por favor, actualice a la <a href=\"%LATEST_URL%\">&uacute;ltima versi&oacute;n</a> lo antes posible.",
+		"zh-tw": "\u60a8\u6b63\u5728\u4f7f\u7528\u5df2\u904e\u6642\u7684 SimpleSAMLphp \u7248\u672c\uff0c\u8acb\u76e1\u5feb\u66f4\u65b0\u81f3<a href=\"%LATEST_URL%\">\u6700\u65b0\u7248\u672c</a>\u3002",
 		"nl": "Deze installatie van SimpleSAMLphp is verouderd. Het is aan te raden zo snel mogelijk te upgraden naar <a href=\"%LATEST_URL%\">de meest recente versie</a>."
 	}
 }
diff --git a/modules/core/dictionaries/no_metadata.translation.json b/modules/core/dictionaries/no_metadata.translation.json
index c097099c1cfa32a932639ad743d958bd872b0095..e580e40c62cf2d831c08a0276f63ec6eee9a2ccf 100644
--- a/modules/core/dictionaries/no_metadata.translation.json
+++ b/modules/core/dictionaries/no_metadata.translation.json
@@ -14,7 +14,7 @@
 		"ja": "\u30e1\u30bf\u30c7\u30fc\u30bf\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093",
 		"da": "Metadata er ikke fundt",
 		"hr": "Metapodaci nisu prona\u0111eni",
-		"zh-tw": "\u627e\u4e0d\u5230\u8a6e\u91cb\u8cc7\u6599",
+		"zh-tw": "\u627e\u4e0d\u5230 Metadata",
 		"et": "Metaandmeid ei leitud",
 		"he": "\u05de\u05d8\u05d0-\u05de\u05d9\u05d3\u05e2 \u05dc\u05d0 \u05e0\u05de\u05e6\u05d0",
 		"ru": "\u041c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b",
@@ -45,7 +45,7 @@
 		"da": "Vi kan ikke finde metadata for denne forbindelse:",
 		"de": "F\u00fcr folgende Entit\u00e4t konnten keine Metadaten gefunden werden:",
 		"hr": "Ne mogu prona\u0107i metapodatke za entitet:",
-		"zh-tw": "\u6211\u5011\u7121\u6cd5\u5b9a\u4f4d\u6b64\u5be6\u9ad4\u4e4b\u8a6e\u91cb\u8cc7\u6599\uff1a",
+		"zh-tw": "\u6211\u5011\u7121\u6cd5\u5b9a\u4f4d\u6b64\u5be6\u9ad4\u4e4b Metadata\uff1a",
 		"et": "Ei suudetud leida olemi metaandmeid:",
 		"he": "\u05dc\u05d0 \u05d4\u05e6\u05dc\u05d7\u05e0\u05d5 \u05dc\u05d0\u05ea\u05e8 \u05de\u05d8\u05d0-\u05de\u05d9\u05d3\u05e2 \u05e2\u05d1\u05d5\u05e8 \u05d4\u05d9\u05e9\u05d5\u05ea:",
 		"ru": "\u041c\u044b \u043d\u0435 \u043d\u0430\u0448\u043b\u0438 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u0430:",
@@ -107,7 +107,7 @@
 		"da": "Hvis du har modtaget denne fejlbesked efter at have klikket p\u00e5 et lilnk, skal du rappoterer fejlen til ejeren af siden. ",
 		"de": "Sind Sie lediglich einem Verweis einer anderen Website hierher gefolgt, sollten Sie diesen Fehler den Betreibern der Website melden.",
 		"hr": "Ako se ova gre\u0161ka pojavila nakon \u0161to ste slijedili poveznicu na nekoj web stranici, onda biste gre\u0161ku trebali prijaviti vlasniku navedene stranice.",
-		"zh-tw": "\u82e5\u60a8\u662f\u500b\u4f7f\u7528\u8005\uff0c\u800c\u60a8\u65bc\u6b64\u7db2\u7ad9\u6536\u5230\u4e0b\u5217\u9023\u7d50\uff0c\u8acb\u53cd\u6620\u6b64\u932f\u8aa4\u7d66\u6b64\u7ad9\u7ba1\u7406\u54e1\u3002",
+		"zh-tw": "\u82e5\u60a8\u662f\u500b\u4f7f\u7528\u8005\uff0c\u800c\u60a8\u65bc\u6b64\u7ad9\u53f0\u6536\u5230\u4e0b\u5217\u9023\u7d50\uff0c\u8acb\u53cd\u6620\u6b64\u932f\u8aa4\u7d66\u6b64\u7ad9\u53f0\u7ba1\u7406\u54e1\u3002",
 		"et": "Kui sa oled kasutaja, kes sai selle veateate veebilehel linki kl\u00f5psates, siis peaksid sellest t\u00f5rkest veebilehe omanikku teavitama.",
 		"he": "\u05d0\u05dd \u05d0\u05ea\u05d4 \u05de\u05e9\u05ea\u05de\u05e9 \u05e9\u05e7\u05d9\u05d1\u05dc \u05e9\u05d2\u05d9\u05d0\u05d4 \u05d6\u05d5 \u05dc\u05d0\u05d7\u05e8 \u05dc\u05d7\u05d9\u05e6\u05d4 \u05e2\u05dc \u05e7\u05d9\u05e9\u05d5\u05e8 \u05d1\u05d0\u05ea\u05e8, \u05db\u05d3\u05d9 \u05e9\u05ea\u05d3\u05d5\u05d5\u05d7 \u05e2\u05dc \u05d4\u05e9\u05d2\u05d9\u05d0\u05d4 \u05dc\u05d1\u05e2\u05dc\u05d9 \u05d4\u05d0\u05ea\u05e8.",
 		"ru": "\u0415\u0441\u043b\u0438, \u043f\u0435\u0440\u0435\u0439\u0434\u044f \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 \u043d\u0430 \u0441\u0430\u0439\u0442, \u0432\u044b \u0443\u0432\u0438\u0434\u0435\u043b\u0438 \u044d\u0442\u0443 \u043e\u0448\u0438\u0431\u043a\u0443, \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0441\u043e\u043e\u0431\u0449\u0438\u0442\u044c \u043e\u0431 \u044d\u0442\u043e\u043c \u0432\u043b\u0430\u0434\u0435\u043b\u0435\u0446\u0443 \u044d\u0442\u043e\u0433\u043e \u0441\u0430\u0439\u0442\u0430.",
@@ -138,7 +138,7 @@
 		"da": "Hvis du er udvikler, s\u00e5 har du et metadata-konfigurationsproblem. Tjek at metadata er konfigurerede korrekt b\u00e5de p\u00e5 service-siden og identitetsudbyder-siden.",
 		"de": "Arbeiten Sie selbst an einem Web Single Sign-On System, stimmt mit den benutzten Metadaten etwas nicht. \u00dcberpr\u00fcfen Sie die Metadaten des Identity Providers und des Service Providers.",
 		"hr": "Ako ste programer koji postavlja sustav jedinstvene autentifikacije (Single Sign-On sustav), tada imate problema s konfiguracijom metapodataka. Provjerite jesu li metapodaci ispravno uneseni i na strani davatelja usluge i u konfiguraciji autentifikacijskog servisa.",
-		"zh-tw": "\u82e5\u60a8\u662f\u55ae\u4e00\u7c3d\u5165\u7a0b\u5f0f\u958b\u767c\u4eba\u54e1\uff0c\u60a8\u7684\u8a6e\u91cb\u8cc7\u6599\u8a2d\u5b9a\u53ef\u80fd\u6709\u554f\u984c\u3002\u8acb\u78ba\u8a8d\u670d\u52d9\u63d0\u4f9b\u8005\u6216\u9a57\u8b49\u63d0\u4f9b\u8005\u4e4b\u8a6e\u91cb\u8cc7\u6599\u8a2d\u5b9a\u6a94\u662f\u5426\u6b63\u78ba\u3002",
+		"zh-tw": "\u82e5\u60a8\u662f\u55ae\u4e00\u7c3d\u5165\u7a0b\u5f0f\u958b\u767c\u4eba\u54e1\uff0c\u60a8\u7684 Metadata \u8a2d\u5b9a\u53ef\u80fd\u6709\u554f\u984c\u3002\u8acb\u78ba\u8a8d\u670d\u52d9\u63d0\u4f9b\u8005\u6216\u9a57\u8b49\u63d0\u4f9b\u8005\u4e4b Metadata \u8a2d\u5b9a\u6a94\u662f\u5426\u6b63\u78ba\u3002",
 		"et": "Kui sa oled arendaja, kes juurutab \u00fchekordse sisselogimise lahendust, siis on probleemi p\u00f5hjuseks metaandmete seadistused. Kontrolli, et metaandmed oleks seadistatud korrektselt nii identiteedipakkuja kui teenusepakkuja poolel.",
 		"he": "\u05d0\u05dd \u05d0\u05ea\u05d4 \u05de\u05e4\u05ea\u05d7 \u05e9\u05e4\u05d5\u05e8\u05e9 \u05e4\u05d9\u05ea\u05e8\u05d5\u05df \u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05d9\u05d7\u05d9\u05d3\u05d4, \u05d9\u05e9 \u05dc\u05da \u05d1\u05e2\u05d9\u05d9\u05d4 \u05e2\u05dd \u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05d4\u05de\u05d8\u05d0-\u05de\u05d9\u05d3\u05e2. \u05d1\u05d3\u05d5\u05e7 \u05e9\u05d4\u05de\u05d8\u05d0-\u05de\u05d9\u05d3\u05e2 \u05de\u05d5\u05d2\u05d3\u05e8 \u05e0\u05db\u05d5\u05df \u05d1\u05e1\u05e4\u05e7\u05d9 \u05d4\u05d6\u05d4\u05d5\u05ea \u05d5\u05d4\u05e9\u05e8\u05d5\u05ea.",
 		"ru": "\u0415\u0441\u043b\u0438 \u0432\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0432\u043d\u0435\u0434\u0440\u044f\u044e\u0449\u0438\u0439 \u0422\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044e \u0435\u0434\u0438\u043d\u043e\u0433\u043e \u0432\u0445\u043e\u0434 (SSO), \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u044b \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u043d\u0430 \u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0435 \u043f\u043e\u0434\u043b\u0438\u043d\u043d\u043e\u0441\u0442\u0438 \u0438 \u041f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0435 \u0443\u0441\u043b\u0443\u0433.",
diff --git a/modules/core/dictionaries/no_state.translation.json b/modules/core/dictionaries/no_state.translation.json
index dd987f21e0015df5eba0931961b3da82ca91d000..b0ad8c5f644286dd7d8dfe0c95df981c63ad680e 100644
--- a/modules/core/dictionaries/no_state.translation.json
+++ b/modules/core/dictionaries/no_state.translation.json
@@ -12,7 +12,7 @@
 		"ja": "\u72b6\u614b\u60c5\u5831\u304c\u7121\u304f\u306a\u308a\u307e\u3057\u305f\u3002",
 		"da": "Tilstandsinformation forsvundet",
 		"hr": "Podatak o stanju je izgubljen",
-		"zh-tw": "\u907a\u5931\u72c0\u614b\u8cc7\u8a0a",
+		"zh-tw": "\u72c0\u614b\u8cc7\u8a0a\u907a\u5931",
 		"nl": "Toestand informatie verloren",
 		"et": "Olekuinfo on kadunud",
 		"he": "\u05de\u05d9\u05d3\u05e2 \u05d4\u05de\u05e6\u05d1 \u05d0\u05d1\u05d3",
@@ -70,7 +70,7 @@
 		"ja": "\u3053\u306e\u554f\u984c\u3092\u89e3\u6c7a\u3059\u308b\u70ba\u306e\u63d0\u6848:",
 		"da": "L\u00f8sningsforslag til problemet:",
 		"hr": "Preporuke za rje\u0161avanje ovog problema:",
-		"zh-tw": "\u5efa\u8b70\u89e3\u6c7a\u9019\u500b\u554f\u984c\uff1a",
+		"zh-tw": "\u95dc\u65bc\u89e3\u6c7a\u9019\u500b\u554f\u984c\u7684\u5efa\u8b70\uff1a",
 		"nl": "Suggesties om dit probleem op te lossen:",
 		"et": "N\u00f5uanded selle probleemi lahendamiseks:",
 		"he": "\u05d4\u05e6\u05e2\u05d5\u05ea \u05dc\u05e4\u05ea\u05e8\u05d5\u05df \u05d4\u05d1\u05e2\u05d9\u05d9\u05d4 \u05d4\u05e0\u05d5\u05db\u05d7\u05d9\u05ea:",
@@ -186,7 +186,7 @@
 		"ja": "WEB\u30d6\u30e9\u30a6\u30b6\u306e\u623b\u308b\u3084\u6b21\u3078\u306e\u30dc\u30bf\u30f3\u3092\u4f7f\u7528\u3057\u307e\u3059\u3002",
 		"da": "Brug frem- og tilbage-knappen i browseren.",
 		"hr": "Kori\u0161tenjem gumba za prethodnu (back) i sljede\u0107u (forward) stranicu u web pregledniku.",
-		"zh-tw": "\u65bc\u7db2\u9801\u700f\u89bd\u5668\u4f7f\u7528\u4e0a\u4e00\u9801\u53ca\u4e0b\u4e00\u9801\u3002",
+		"zh-tw": "\u8acb\u4f7f\u7528\u7db2\u9801\u700f\u89bd\u5668\u4e2d\u7684\u4e0a\u4e00\u9801\u53ca\u4e0b\u4e00\u9801\u3002",
 		"nl": "Gebruik de Volgende en Terug knoppen in de web browser.",
 		"et": "brauseri edasi-tagasi nuppude kasutamisest",
 		"he": "\u05e9\u05d9\u05de\u05d5\u05e9 \u05d1\u05db\u05e4\u05ea\u05d5\u05e8\u05d9 \u05d4\u05d1\u05d0 \u05d5\u05d4\u05e7\u05d5\u05d3\u05dd \u05d1\u05d3\u05e4\u05d3\u05e4\u05df.",
@@ -244,7 +244,7 @@
 		"ja": "\u3053\u306eWEB\u30d6\u30e9\u30a6\u30b6\u3067\u306f\u30af\u30c3\u30ad\u30fc\u304c\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059\u3002",
 		"da": "Cookies kan v\u00e6re deaktiveret i browseren.",
 		"hr": "Mogu\u0107e da je podr\u0161ka za kola\u010di\u0107e (\"cookies\") isklju\u010dena u web pregledniku.",
-		"zh-tw": "\u7db2\u9801\u700f\u89bd\u5668\u7684 Cookies \u53ef\u80fd\u88ab\u95dc\u9589\u3002",
+		"zh-tw": "\u7db2\u9801\u700f\u89bd\u5668\u7684 Cookies \u529f\u80fd\u53ef\u80fd\u88ab\u505c\u7528\u3002",
 		"nl": "Cookies kunnen uitgeschakeld zijn in de web browser.",
 		"et": "k\u00fcpsiste keelamisest brauseris",
 		"he": "\u05ea\u05de\u05d9\u05db\u05d4 \u05d1\u05e2\u05d5\u05d2\u05d9\u05d5\u05ea \u05de\u05d1\u05d5\u05d8\u05dc\u05ea \u05d1\u05d3\u05e4\u05d3\u05e4\u05df",
@@ -302,7 +302,7 @@
 		"ja": "\u3053\u306e\u554f\u984c\u304c\u7d99\u7d9a\u3057\u3066\u8d77\u3053\u308b\u5834\u5408\u3001\u30b7\u30b9\u30c6\u30e0\u7ba1\u7406\u8005\u306b\u5831\u544a\u3057\u3066\u304f\u3060\u3055\u3044\u3002",
 		"da": "Hvis dette problem forts\u00e6tter, kan du rapportere det til systemadministratoren.",
 		"hr": "Ako se ova gre\u0161ka bude i dalje pojavljivala, mo\u017eete ju prijaviti administratorima.",
-		"zh-tw": "\u5982\u679c\u9019\u500b\u932f\u8aa4\u6301\u7e8c\u5b58\u5728\uff0c\u60a8\u53ef\u4ee5\u5c07\u5b83\u56de\u5831\u7cfb\u7d71\u7ba1\u7406\u8005\u3002",
+		"zh-tw": "\u5982\u679c\u9019\u500b\u932f\u8aa4\u6301\u7e8c\u5b58\u5728\uff0c\u8acb\u56de\u5831\u7d66\u7cfb\u7d71\u7ba1\u7406\u8005\u3002",
 		"nl": "If dit probleem behoud, dan kun je het melden aan de systeem beheerders.",
 		"et": "Kui probleem ei kao, siis teavita sellest s\u00fcsteemi administraatoreid.",
 		"he": "\u05d0\u05dd \u05d4\u05d1\u05e2\u05d9\u05d9\u05d4 \u05de\u05de\u05e9\u05d9\u05db\u05d4, \u05d0\u05ea\u05d4 \u05d9\u05db\u05d5\u05dc \u05dc\u05d3\u05d5\u05d5\u05d7 \u05e2\u05dc\u05d9\u05d4 \u05dc\u05de\u05e0\u05d4\u05dc \u05d4\u05de\u05e2\u05e8\u05db\u05ea.",
diff --git a/modules/core/dictionaries/short_sso_interval.translation.json b/modules/core/dictionaries/short_sso_interval.translation.json
index 0db4f17a8ffad41cc884e4a06d37ca4bda7b3ba0..aee5b58fb67d9afd38fe4892f5cefaa7b7efe141 100644
--- a/modules/core/dictionaries/short_sso_interval.translation.json
+++ b/modules/core/dictionaries/short_sso_interval.translation.json
@@ -46,7 +46,7 @@
 		"hu": "T\u00fal kev\u00e9s id\u0151 telt el a bel\u00e9p\u00e9si k\u00eds\u00e9rletek k\u00f6z\u00f6tt.",
 		"nl": "Te kort interval tussen single sign on pogingen",
 		"ja": "\u30b7\u30f3\u30b0\u30eb\u30b5\u30a4\u30f3\u30aa\u30f3\u30a4\u30d9\u30f3\u30c8\u306e\u9593\u9694\u304c\u77ed\u3059\u304e\u307e\u3059\u3002",
-		"zh-tw": "\u55ae\u4e00\u7c3d\u5165\u4e8b\u4ef6\u9593\u9694\u904e\u77ed\u3002",
+		"zh-tw": "\u55ae\u4e00\u7c3d\u5165\u4e8b\u4ef6\u6642\u9593\u9593\u9694\u904e\u77ed\u3002",
 		"et": "Liiga l\u00fchike intervall \u00fchekordse sisselogimise s\u00fcndmuste vahel.",
 		"he": "\u05e4\u05e8\u05e7 \u05d6\u05de\u05df \u05e7\u05e6\u05e8 \u05de\u05d9\u05d3\u05d9 \u05d1\u05d9\u05df \u05d0\u05e8\u05d5\u05e2\u05d9 \u05db\u05e0\u05d9\u05e1\u05d4 \u05d9\u05d7\u05d9\u05d3\u05d4.",
 		"ru": "\u041e\u0447\u0435\u043d\u044c \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0439 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u043a \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043c\u0435\u0436\u0434\u0443 \u0435\u0434\u0438\u043d\u044b\u043c \u0432\u0445\u043e\u0434\u043e\u043c \u0432 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u0445.",
@@ -77,7 +77,7 @@
 		"hu": "\u00dajb\u00f3li bel\u00e9p\u00e9s",
 		"nl": "Inloggen opnieuw proberen",
 		"ja": "\u30ed\u30b0\u30a4\u30f3\u3092\u518d\u8a66\u884c",
-		"zh-tw": "\u91cd\u8a66\u767b\u5165",
+		"zh-tw": "\u91cd\u65b0\u767b\u5165",
 		"et": "Proovi uuesti logida",
 		"he": "\u05e0\u05e1\u05d4 \u05e9\u05d5\u05d1 \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8",
 		"ru": "\u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u0432\u0445\u043e\u0434\u0430",
diff --git a/modules/core/docs/authproc_attributecopy.md b/modules/core/docs/authproc_attributecopy.md
index 37c99eaec11990186613e799bad6af4c8587c777..6f663d395006efc064c450ff6a748dd2337f7216 100644
--- a/modules/core/docs/authproc_attributecopy.md
+++ b/modules/core/docs/authproc_attributecopy.md
@@ -1,13 +1,13 @@
 `core:AttributeCopy`
 ===================
 
-Filter that renames attributes.
+Filter that copies attributes.
 
 
 Examples
 --------
 
-Copy a single attribute (user's uid will be copied to the user's username):
+Copy a single attribute (user's `uid` will be copied to the user's `username`):
 
     'authproc' => array(
         50 => array(
@@ -16,3 +16,11 @@ Copy a single attribute (user's uid will be copied to the user's username):
         ),
     ),
 
+Copy a single attribute to more then one attribute (user's `uid` will be copied to the user's `username` and to `urn:mace:dir:attribute-def:uid`)
+
+    'authproc' => array(
+        50 => array(
+            'class' => 'core:AttributeCopy',
+            'uid' => array('username', 'urn:mace:dir:attribute-def:uid'),
+        ),
+    ),
diff --git a/modules/core/lib/Auth/Process/AttributeCopy.php b/modules/core/lib/Auth/Process/AttributeCopy.php
index bd9b9fd83b9bcc0cdf2ad17515165fbe34b13b35..4b83e05573035f26959610ebff176f023e483098 100644
--- a/modules/core/lib/Auth/Process/AttributeCopy.php
+++ b/modules/core/lib/Auth/Process/AttributeCopy.php
@@ -40,7 +40,7 @@ class sspmod_core_Auth_Process_AttributeCopy extends SimpleSAML_Auth_ProcessingF
 				throw new Exception('Invalid source attribute name: ' . var_export($source, TRUE));
 			}
 
-			if(!is_string($destination)) {
+			if(!is_string($destination) && !is_array($destination)) {
 				throw new Exception('Invalid destination attribute name: ' . var_export($destination, TRUE));
 			}
 
@@ -62,7 +62,13 @@ class sspmod_core_Auth_Process_AttributeCopy extends SimpleSAML_Auth_ProcessingF
 
 		foreach($attributes as $name => $values) {
 			if (array_key_exists($name,$this->map)){
-				$attributes[$this->map[$name]] = $values;
+				if (!is_array($this->map[$name])) {
+					$attributes[$this->map[$name]] = $values;
+				} else {
+					foreach ($this->map[$name] as $to_map) {
+						$attributes[$to_map] = $values;
+					}
+				}
 			}
 		}
 
diff --git a/modules/core/lib/Auth/Process/ExtendIdPSession.php b/modules/core/lib/Auth/Process/ExtendIdPSession.php
index 3945741189f1c4157403857b7a432cf28e4edb14..b8382d30c3b79e5a0b24a84b39613905d589fc86 100644
--- a/modules/core/lib/Auth/Process/ExtendIdPSession.php
+++ b/modules/core/lib/Auth/Process/ExtendIdPSession.php
@@ -37,7 +37,7 @@ class sspmod_core_Auth_Process_ExtendIdPSession extends SimpleSAML_Auth_Processi
 		}
 
 		/* Or if session lifetime is more than zero */
-		$sessionHandler = SimpleSAML_SessionHandler::getSessionHandler();
+		$sessionHandler = \SimpleSAML\SessionHandler::getSessionHandler();
 		$cookieParams = $sessionHandler->getCookieParams();
 		if ($cookieParams['lifetime'] > 0) {
 			$session->updateSessionCookies();
diff --git a/modules/core/lib/Auth/UserPassBase.php b/modules/core/lib/Auth/UserPassBase.php
index e688a70ddab6835404c14f663cca92db5e5c25e4..32e4868ac640d998ede7dd103d1a71f3b8e39526 100644
--- a/modules/core/lib/Auth/UserPassBase.php
+++ b/modules/core/lib/Auth/UserPassBase.php
@@ -256,7 +256,7 @@ abstract class sspmod_core_Auth_UserPassBase extends SimpleSAML_Auth_Source {
 			throw $e;
 		}
 
-		SimpleSAML\Logger::stats('User \''.$username.'\' has been successfully authenticated.');
+		SimpleSAML\Logger::stats('User \''.$username.'\' successfully authenticated from '.$_SERVER['REMOTE_ADDR']);
 
 		/* Save the attributes we received from the login-function in the $state-array. */
 		assert('is_array($attributes)');
diff --git a/modules/core/templates/loginuserpass.php b/modules/core/templates/loginuserpass.php
index 5baaec7aef0018fc65ff4d63cd0d17a2c1861cd7..d957fc43a2b0745ad86aaf14a8a2d3f57b329129 100644
--- a/modules/core/templates/loginuserpass.php
+++ b/modules/core/templates/loginuserpass.php
@@ -36,7 +36,7 @@ if ($this->data['errorcode'] !== null) {
     <form action="?" method="post" name="f">
         <table>
             <tr>
-                <td rowspan="2" id="loginicon">
+                <td rowspan="2" class="loginicon">
                     <img alt=""
                         src="/<?php echo $this->data['baseurlpath']; ?>resources/icons/experience/gtk-dialog-authentication.48x48.png" />
                 </td>
@@ -146,8 +146,8 @@ if ($this->data['errorcode'] !== null) {
                 <?php
             }
             ?>
-            <tr id="regularsubmit">
-                <td></td><td></td>
+            <tr id="submit">
+                <td class="loginicon"></td><td></td>
                 <td>
                     <button class="btn"
                             onclick="this.value='<?php echo $this->t('{login:processing}'); ?>';
@@ -156,14 +156,6 @@ if ($this->data['errorcode'] !== null) {
                     </button>
                 </td>
             </tr>
-            <tr id="mobilesubmit">
-                <td></td><td></td>
-                <td>
-                    <button class="btn" tabindex="6">
-                        <?php echo $this->t('{login:login_button}'); ?>
-                    </button>
-                </td>
-            </tr>
         </table>
         <?php
         foreach ($this->data['stateparams'] as $name => $value) {
diff --git a/modules/core/templates/logout-iframe-wrapper.php b/modules/core/templates/logout-iframe-wrapper.php
index 1890ee583c01f91e630a7aa1325f1984f78ece8f..4649c289bec589a29413162b8d8491ba194076a5 100644
--- a/modules/core/templates/logout-iframe-wrapper.php
+++ b/modules/core/templates/logout-iframe-wrapper.php
@@ -1,28 +1,29 @@
 <?php
 
-$id = $this->data['id'];
+$id = $this->data['auth_state'];
 $SPs = $this->data['SPs'];
 
-$iframeURL = 'logout-iframe.php?type=embed&id=' . urlencode($id);
+$iframeURL = 'logout-iframe.php?type=embed&id='.urlencode($id);
 
-// Pretty arbitrary height, but should have enough safety margins for most cases
+// pretty arbitrary height, but should have enough safety margins for most cases
 $iframeHeight = 25 + count($SPs) * 4;
 
 $this->data['header'] = $this->t('{logout:progress}');
 $this->includeAtTemplateBase('includes/header.php');
-echo '<iframe style="width:100%; height:' . $iframeHeight . 'em; border:0;" src="' . htmlspecialchars($iframeURL) . '"></iframe>';
+echo '<iframe style="width:100%; height:'.$iframeHeight.'em; border:0;" src="'.htmlspecialchars($iframeURL).
+     '"></iframe>';
 
-foreach ($SPs AS $assocId => $sp) {
-	$spId = sha1($assocId);
+foreach ($SPs as $assocId => $sp) {
+    $spId = sha1($assocId);
 
-	if ($sp['core:Logout-IFrame:State'] !== 'inprogress') {
-		continue;
-	}
-	assert('isset($sp["core:Logout-IFrame:URL"])');
+    if ($sp['core:Logout-IFrame:State'] !== 'inprogress') {
+        continue;
+    }
+    assert('isset($sp["core:Logout-IFrame:URL"])');
 
-	$url = $sp["core:Logout-IFrame:URL"];
+    $url = $sp["core:Logout-IFrame:URL"];
 
-	echo('<iframe style="width:0; height:0; border:0;" src="' . htmlspecialchars($url) . '"></iframe>');
+    echo('<iframe style="width:0; height:0; border:0;" src="'.htmlspecialchars($url).'"></iframe>');
 }
 
 $this->includeAtTemplateBase('includes/footer.php');
diff --git a/modules/core/templates/logout-iframe.php b/modules/core/templates/logout-iframe.php
index e71c2f266a5aecebe0406398f3a74e496046e7bf..de177d08177fd322df0b85ef3c3c35985cadc960 100644
--- a/modules/core/templates/logout-iframe.php
+++ b/modules/core/templates/logout-iframe.php
@@ -1,24 +1,24 @@
 <?php
 
-$id = $this->data['id'];
+$id = $this->data['auth_state'];
 $type = $this->data['type'];
 $from = $this->data['from'];
 $SPs = $this->data['SPs'];
 
 $stateImage = array(
-	'unsupported' => '/' . $this->data['baseurlpath'] . 'resources/icons/silk/delete.png',
-	'completed' => '/' . $this->data['baseurlpath'] . 'resources/icons/silk/accept.png',
-	'onhold' => '/' . $this->data['baseurlpath'] . 'resources/icons/bullet16_grey.png',
-	'inprogress' => '/' . $this->data['baseurlpath'] . 'resources/progress.gif',
-	'failed' => '/' . $this->data['baseurlpath'] . 'resources/icons/silk/exclamation.png',
+    'unsupported' => '/'.$this->data['baseurlpath'].'resources/icons/silk/delete.png',
+    'completed'   => '/'.$this->data['baseurlpath'].'resources/icons/silk/accept.png',
+    'onhold'      => '/'.$this->data['baseurlpath'].'resources/icons/bullet16_grey.png',
+    'inprogress'  => '/'.$this->data['baseurlpath'].'resources/progress.gif',
+    'failed'      => '/'.$this->data['baseurlpath'].'resources/icons/silk/exclamation.png',
 );
 
 $stateText = array(
-	'unsupported' => '',
-	'completed' => $this->t('{logout:completed}'),
-	'onhold' => '',
-	'inprogress' => $this->t('{logout:progress}'),
-	'failed' => $this->t('{logout:failed}'),
+    'unsupported' => '',
+    'completed'   => $this->t('{logout:completed}'),
+    'onhold'      => '',
+    'inprogress'  => $this->t('{logout:progress}'),
+    'failed'      => $this->t('{logout:failed}'),
 );
 
 $spStatus = array();
@@ -26,190 +26,170 @@ $spTimeout = array();
 $nFailed = 0;
 $nProgress = 0;
 foreach ($SPs as $assocId => $sp) {
-	assert('isset($sp["core:Logout-IFrame:State"])');
-	$state = $sp['core:Logout-IFrame:State'];
-	$spStatus[sha1($assocId)] = $state;
-	if (isset($sp['core:Logout-IFrame:Timeout'])) {
-		$spTimeout[sha1($assocId)] = $sp['core:Logout-IFrame:Timeout'] - time();
-	} else {
-		$spTimeout[sha1($assocId)] = 5;
-	}
-	if ($state === 'failed') {
-		$nFailed += 1;
-	} elseif ($state === 'inprogress') {
-		$nProgress += 1;
-	}
+    assert('isset($sp["core:Logout-IFrame:State"])');
+    $state = $sp['core:Logout-IFrame:State'];
+    $spStatus[sha1($assocId)] = $state;
+    if (isset($sp['core:Logout-IFrame:Timeout'])) {
+        $spTimeout[sha1($assocId)] = $sp['core:Logout-IFrame:Timeout'] - time();
+    } else {
+        $spTimeout[sha1($assocId)] = 5;
+    }
+    if ($state === 'failed') {
+        $nFailed += 1;
+    } elseif ($state === 'inprogress') {
+        $nProgress += 1;
+    }
 }
 
-if ($from !== NULL) {
-	$from = $this->getTranslator()->getPreferredTranslation($from);
+if ($from !== null) {
+    $from = $this->getTranslator()->getPreferredTranslation($from);
 }
 
-
 if (!isset($this->data['head'])) {
-	$this->data['head'] = '';
+    $this->data['head'] = '';
 }
 
 $this->data['head'] .= '
 <script type="text/javascript" language="JavaScript">
-window.stateImage = ' . json_encode($stateImage) . ';
-window.stateText = ' . json_encode($stateText) . ';
-window.spStatus = ' . json_encode($spStatus) . ';
-window.spTimeout = ' . json_encode($spTimeout) . ';
-window.type = "' . $type . '";
-window.asyncURL = "logout-iframe.php?id=' . $id . '&type=async";
+window.stateImage = '.json_encode($stateImage).';
+window.stateText = '.json_encode($stateText).';
+window.spStatus = '.json_encode($spStatus).';
+window.spTimeout = '.json_encode($spTimeout).';
+window.type = "'.$type.'";
 </script>';
 
 $this->data['head'] .= '<script type="text/javascript" src="logout-iframe.js"></script>';
 
 if ($type === 'embed') {
-	$this->data['head'] .= '<meta http-equiv="refresh" content="1" />';
+    $this->data['head'] .= '<meta http-equiv="refresh" content="1" />';
 }
 
 $this->data['header'] = $this->t('{logout:progress}');
 if ($type === 'embed') {
-	$this->includeAtTemplateBase('includes/header-embed.php');
+    $this->includeAtTemplateBase('includes/header-embed.php');
 } else {
-	$this->includeAtTemplateBase('includes/header.php');
+    $this->includeAtTemplateBase('includes/header.php');
 }
 ?>
-<div id="wrap">
+ <div id="wrap">
   <div id="content">
 <?php
-if ($from !== NULL) {
-
-	echo('<div><img style="float: left; margin-right: 12px" src="/' . $this->data['baseurlpath'] . 'resources/icons/checkmark.48x48.png" alt="Successful logout" />');
-	echo('<p style="padding-top: 16px; ">' . $this->t('{logout:loggedoutfrom}', array('%SP%' => '<strong>' .htmlspecialchars($from).'</strong>')) . '</p>');
-	echo('<p style="height: 0px; clear: left;"></p>');
-	echo('</div>');
+if ($from !== null) {
+    echo '<div><img style="float: left; margin-right: 12px" src="/'.$this->data['baseurlpath'].
+         'resources/icons/checkmark.48x48.png" alt="Successful logout" />';
+    echo '<p style="padding-top: 16px; ">'.
+        $this->t('{logout:loggedoutfrom}', array('%SP%' => '<strong>'.htmlspecialchars($from).'</strong>')).'</p>';
+    echo '<p style="height: 0px; clear: left;"></p></div>';
 }
 
-echo('<div style="margin-top: 3em; clear: both">');
-
+echo '<div style="margin-top: 3em; clear: both">';
 
-echo('<p style="margin-bottom: .5em">');
+echo '<p style="margin-bottom: .5em">';
 if ($type === 'init') {
-	echo($this->t('{logout:also_from}'));
+    echo $this->t('{logout:also_from}');
 } else {
-	echo($this->t('{logout:logging_out_from}'));
+    echo $this->t('{logout:logging_out_from}');
 }
 echo('</p>');
-
-echo '<table id="slostatustable">';
-
-foreach ($SPs AS $assocId => $sp) {
-	if (isset($sp['core:Logout-IFrame:Name'])) {
-		$spName = $this->getTranslator()->getPreferredTranslation($sp['core:Logout-IFrame:Name']);
-	} else {
-		$spName = $assocId;
-	}
-
-	assert('isset($sp["core:Logout-IFrame:State"])');
-	$spState = $sp['core:Logout-IFrame:State'];
-
-	$spId = sha1($assocId);
-
-	echo '<tr>';
-
-	echo '<td style="width: 3em;"></td>';
-
-	echo '<td>';
-	echo '<img class="logoutstatusimage" id="statusimage-' . $spId . '"  src="' . htmlspecialchars($stateImage[$spState]) . '" alt="' . htmlspecialchars($stateText[$spState]) . '"/>';
-	echo '</td>';
-
-	echo '<td>' . htmlspecialchars($spName) . '</td>';
-
-	echo '</tr>';
+?>
+  <table id="slostatustable">
+<?php
+foreach ($SPs as $assocId => $sp) {
+    if (isset($sp['core:Logout-IFrame:Name'])) {
+        $spName = $this->getTranslator()->getPreferredTranslation($sp['core:Logout-IFrame:Name']);
+    } else {
+        $spName = $assocId;
+    }
+
+    assert('isset($sp["core:Logout-IFrame:State"])');
+    $spState = $sp['core:Logout-IFrame:State'];
+
+    $spId = sha1($assocId);
+
+    echo '<tr>';
+    echo '<td style="width: 3em;"></td>';
+    echo '<td>';
+    echo '<img class="logoutstatusimage" id="statusimage-'.$spId.'"  src="'.htmlspecialchars($stateImage[$spState]).
+         '" alt="'.htmlspecialchars($stateText[$spState]).'"/>';
+    echo '</td>';
+    echo '<td>'.htmlspecialchars($spName).'</td>';
+    echo '</tr>';
 }
 
 if (isset($from)) {
-	$logoutCancelText = $this->t('{logout:logout_only}', array('%SP%' => htmlspecialchars($from)));
+    $logoutCancelText = $this->t('{logout:logout_only}', array('%SP%' => htmlspecialchars($from)));
 } else {
-	$logoutCancelText = $this->t('{logout:no}');
+    $logoutCancelText = $this->t('{logout:no}');
 }
 
 ?>
-</table>
-</div>
+  </table>
+ </div>
 
 <?php
 if ($type === 'init') {
 ?>
-<div id="confirmation" style="margin-top: 1em" >
-<p><?php echo $this->t('{logout:logout_all_question}'); ?> <br /></p>
-
-<form id="startform" method="get" style="display:inline;" action="logout-iframe.php">
-<input type="hidden" name="id" value="<?php echo $id; ?>" />
-<input type="hidden" id="logout-type-selector" name="type" value="nojs" />
-<button type="submit" id="logout-all" name="ok" class="btn"><?php echo $this->t('{logout:logout_all}'); ?></button>
-</form>
-
-<form method="get" style="display:inline;" action="logout-iframe-done.php">
-<input type="hidden" name="id" value="<?php echo $id; ?>" />
-<button type="submit" name="cancel" class="btn"><?php echo $logoutCancelText; ?></button>
-</form>
-
-</div>
-
-<?php
-} else {
-?>
-
+ <div id="confirmation" style="margin-top: 1em">
+  <p><?php echo $this->t('{logout:logout_all_question}'); ?> <br/></p>
+  <form id="startform" method="get" style="display:inline;" action="logout-iframe.php">
+   <input type="hidden" name="id" value="<?php echo $id; ?>"/>
+   <input type="hidden" id="logout-type-selector" name="type" value="nojs"/>
+   <button type="submit" id="logout-all" name="ok" class="btn"><?php echo $this->t('{logout:logout_all}'); ?></button>
+  </form>
+  <form method="get" style="display:inline;" action="logout-iframe-done.php">
+   <input type="hidden" name="id" value="<?php echo $id; ?>"/>
+   <button type="submit" name="cancel" class="btn"><?php echo $logoutCancelText; ?></button>
+  </form>
+ </div>
 <?php
-if ($nFailed > 0) {
-	$displayStyle = '';
-} else {
-	$displayStyle = 'display: none;';
-}
-echo('<div id="logout-failed-message" style="margin-top: 1em; border: 1px solid #ccc; padding: 1em; background: #eaeaea;' . $displayStyle . '">');
-echo('<img src="/' . $this->data['baseurlpath'] . 'resources/icons/experience/gtk-dialog-warning.48x48.png" alt="" style="float: left; margin-right: 5px;" />');
-echo('<p>' . $this->t('{logout:failedsps}') . '</p>');
-echo('<form method="post" action="logout-iframe-done.php" id="failed-form" target="_top">');
-echo('<input type="hidden" name="id" value="' . $id . '" />');
-echo('<button type="submit" name="continue" class="btn">' . $this->t('{logout:return}'). '</button>');
-echo('</form>');
-echo('</div>');
-
-if ($nProgress == 0 && $nFailed == 0) {
-	echo('<div id="logout-completed">');
 } else {
-	echo('<div id="logout-completed" style="display:none;">');
-}
-echo('<p>' . $this->t('{logout:success}') . '</p>');
+    if ($nFailed > 0) {
+        $displayStyle = '';
+    } else {
+        $displayStyle = 'display: none;';
+    }
+    echo '<div id="logout-failed-message" style="margin-top: 1em; border: 1px solid #ccc; padding: 1em; '.
+         'background: #eaeaea;'.$displayStyle.'">';
+    echo '<img src="/'.$this->data['baseurlpath'].'resources/icons/experience/gtk-dialog-warning.48x48.png" alt="" '.
+         'style="float: left; margin-right: 5px;" />';
+    echo '<p>'.$this->t('{logout:failedsps}').'</p>';
+    echo '<form method="post" action="logout-iframe-done.php" id="failed-form" target="_top">';
+    echo '<input type="hidden" name="id" value="'.$id.'" />';
+    echo '<button type="submit" name="continue" class="btn">'.$this->t('{logout:return}').'</button>';
+    echo '</form>';
+    echo '</div>';
+
+    if ($nProgress == 0 && $nFailed == 0) {
+        echo '<div id="logout-completed">';
+    } else {
+        echo '<div id="logout-completed" style="display:none;">';
+    }
+    echo '<p>'.$this->t('{logout:success}').'</p>';
 ?>
-<form method="post" action="logout-iframe-done.php" id="done-form" target="_top">
-	<input type="hidden" name="id" value="<?php echo $id; ?>" />
-	<button type="submit" name="continue" class="btn"><?php echo $this->t('{logout:return}'); ?></button>
-</form>
-</div>
-
-<?php
-if ($type === 'js') {
-	foreach ($SPs AS $assocId => $sp) {
-		$spId = sha1($assocId);
-
-		if ($sp['core:Logout-IFrame:State'] !== 'inprogress') {
-			continue;
-		}
-		assert('isset($sp["core:Logout-IFrame:URL"])');
-
-		$url = $sp["core:Logout-IFrame:URL"];
-
-		echo('<iframe style="width:0; height:0; border:0;" src="' . htmlspecialchars($url) . '"></iframe>');
-	}
-}
-?>
-
+ <form method="post" action="logout-iframe-done.php" id="done-form" target="_top">
+  <input type="hidden" name="id" value="<?php echo $id; ?>"/>
+  <button type="submit" name="continue" class="btn"><?php echo $this->t('{logout:return}'); ?></button>
+ </form>
+ </div>
 <?php
+    if ($type === 'js') {
+        foreach ($SPs as $assocId => $sp) {
+            $spId = sha1($assocId);
+            if ($sp['core:Logout-IFrame:State'] !== 'inprogress') {
+                continue;
+            }
+            assert('isset($sp["core:Logout-IFrame:URL"])');
+            echo '<iframe style="width:0; height:0; border:0;" src="'.
+                 htmlspecialchars($sp['core:Logout-IFrame:URL']).'"></iframe>';
+        }
+    }
 }
 ?>
-  </div>
-  <!-- #content -->
-</div>
-<!-- #wrap -->
+  </div><!-- #content -->
+ </div><!-- #wrap -->
 <?php
 if ($type === 'embed') {
-	$this->includeAtTemplateBase('includes/footer-embed.php');
+    $this->includeAtTemplateBase('includes/footer-embed.php');
 } else {
-	$this->includeAtTemplateBase('includes/footer.php');
+    $this->includeAtTemplateBase('includes/footer.php');
 }
diff --git a/modules/core/www/frontpage_config.php b/modules/core/www/frontpage_config.php
index af145e25535b801298fe68ad6ce2fad639936a68..f501d077b7b8edbc8a299add44cdb89105ba1982 100644
--- a/modules/core/www/frontpage_config.php
+++ b/modules/core/www/frontpage_config.php
@@ -107,6 +107,7 @@ $functionchecks = array(
 	'preg_match'       => array('required',  'RegEx support'),
 	'json_decode'      => array('required', 'JSON support'),
 	'class_implements' => array('required', 'Standard PHP Library (SPL)'),
+	'mb_strlen'        => array('required', 'Multibyte String Extension'),
 	'curl_init'        => array('optional', 'cURL (required if automatic version checks are used, also by some modules.'),
 	'mcrypt_module_open'=> array('optional',  'MCrypt (required if digital signatures or encryption are used)'),
 	'session_start'  => array('optional', 'Session Extension (required if PHP sessions are used)'),
@@ -130,6 +131,11 @@ foreach ($functionchecks AS $func => $descr) {
 
 $funcmatrix[] = array(
     'required' => 'optional',
+    'descr' => 'predis/predis (required if the redis data store is used)',
+    'enabled' => class_exists('\Predis\Client'),
+);
+
+$funcmatrix[] = array(
     'descr' => 'Memcache or Memcached Extension (required if a Memcached backend is used)',
     'enabled' => class_exists('Memcache') || class_exists('Memcached'),
 );
diff --git a/modules/core/www/idp/logout-iframe-done.php b/modules/core/www/idp/logout-iframe-done.php
index d8eeb9facf3c6c989d3b56c58aa83e51140e86ef..6b00621b842b3f334939797f93ea2c78172cb88c 100644
--- a/modules/core/www/idp/logout-iframe-done.php
+++ b/modules/core/www/idp/logout-iframe-done.php
@@ -1,7 +1,7 @@
 <?php
 
 if (!isset($_REQUEST['id'])) {
-	throw new SimpleSAML_Error_BadRequest('Missing required parameter: id');
+    throw new SimpleSAML_Error_BadRequest('Missing required parameter: id');
 }
 $state = SimpleSAML_Auth_State::loadState($_REQUEST['id'], 'core:Logout-IFrame');
 $idp = SimpleSAML_IdP::getByState($state);
@@ -9,55 +9,50 @@ $idp = SimpleSAML_IdP::getByState($state);
 $associations = $idp->getAssociations();
 
 if (!isset($_REQUEST['cancel'])) {
-	SimpleSAML\Logger::stats('slo-iframe done');
-	SimpleSAML_Stats::log('core:idp:logout-iframe:page', array('type' => 'done'));
-	$SPs = $state['core:Logout-IFrame:Associations'];
+    SimpleSAML\Logger::stats('slo-iframe done');
+    SimpleSAML_Stats::log('core:idp:logout-iframe:page', array('type' => 'done'));
+    $SPs = $state['core:Logout-IFrame:Associations'];
 } else {
-	// User skipped global logout
-	SimpleSAML\Logger::stats('slo-iframe skip');
-	SimpleSAML_Stats::log('core:idp:logout-iframe:page', array('type' => 'skip'));
-	$SPs = array(); // No SPs should have been logged out
-	$state['core:Failed'] = TRUE; /* Mark as partial logout. */
+    // user skipped global logout
+    SimpleSAML\Logger::stats('slo-iframe skip');
+    SimpleSAML_Stats::log('core:idp:logout-iframe:page', array('type' => 'skip'));
+    $SPs = array(); // no SPs should have been logged out
+    $state['core:Failed'] = true; // mark as partial logout
 }
 
-/* Find the status of all SPs. */
+// find the status of all SPs
 foreach ($SPs as $assocId => &$sp) {
-
-	$spId = 'logout-iframe-' . sha1($assocId);
-
-	if (isset($_REQUEST[$spId])) {
-		$spStatus = $_REQUEST[$spId];
-		if ($spStatus === 'completed' || $spStatus === 'failed') {
-			$sp['core:Logout-IFrame:State'] = $spStatus;
-		}
-	}
-
-	if (!isset($associations[$assocId])) {
-		$sp['core:Logout-IFrame:State'] = 'completed';
-	}
-
+    $spId = 'logout-iframe-'.sha1($assocId);
+
+    if (isset($_REQUEST[$spId])) {
+        $spStatus = $_REQUEST[$spId];
+        if ($spStatus === 'completed' || $spStatus === 'failed') {
+            $sp['core:Logout-IFrame:State'] = $spStatus;
+        }
+    }
+
+    if (!isset($associations[$assocId])) {
+        $sp['core:Logout-IFrame:State'] = 'completed';
+    }
 }
 
 
-/* Terminate the associations. */
+// terminate the associations
 foreach ($SPs as $assocId => $sp) {
-
-	if ($sp['core:Logout-IFrame:State'] === 'completed') {
-		$idp->terminateAssociation($assocId);
-	} else {
-		SimpleSAML\Logger::warning('Unable to terminate association with ' . var_export($assocId, TRUE) . '.');
-		if (isset($sp['saml:entityID'])) {
-			$spId = $sp['saml:entityID'];
-		} else {
-			$spId = $assocId;
-		}
-		SimpleSAML\Logger::stats('slo-iframe-fail ' . $spId);
-		SimpleSAML_Stats::log('core:idp:logout-iframe:spfail', array('sp' => $spId));
-		$state['core:Failed'] = TRUE;
-	}
-
+    if ($sp['core:Logout-IFrame:State'] === 'completed') {
+        $idp->terminateAssociation($assocId);
+    } else {
+        SimpleSAML\Logger::warning('Unable to terminate association with '.var_export($assocId, true).'.');
+        if (isset($sp['saml:entityID'])) {
+            $spId = $sp['saml:entityID'];
+        } else {
+            $spId = $assocId;
+        }
+        SimpleSAML\Logger::stats('slo-iframe-fail '.$spId);
+        SimpleSAML_Stats::log('core:idp:logout-iframe:spfail', array('sp' => $spId));
+        $state['core:Failed'] = true;
+    }
 }
 
-
-/* We are done. */
+// we are done
 $idp->finishLogout($state);
diff --git a/modules/core/www/idp/logout-iframe-post.php b/modules/core/www/idp/logout-iframe-post.php
index 5a9c4d879da765db6b13b05c902ac97430c96805..7079e19aa6b63be297bf5031e43141b3b30c92e6 100644
--- a/modules/core/www/idp/logout-iframe-post.php
+++ b/modules/core/www/idp/logout-iframe-post.php
@@ -1,24 +1,24 @@
 <?php
 
 if (!isset($_REQUEST['idp'])) {
-	throw new SimpleSAML_Error_BadRequest('Missing "idp" parameter.');
+    throw new SimpleSAML_Error_BadRequest('Missing "idp" parameter.');
 }
-$idp = (string)$_REQUEST['idp'];
+$idp = (string) $_REQUEST['idp'];
 $idp = SimpleSAML_IdP::getById($idp);
 
 if (!isset($_REQUEST['association'])) {
-	throw new SimpleSAML_Error_BadRequest('Missing "association" parameter.');
+    throw new SimpleSAML_Error_BadRequest('Missing "association" parameter.');
 }
 $assocId = urldecode($_REQUEST['association']);
 
-$relayState = NULL;
+$relayState = null;
 if (isset($_REQUEST['RelayState'])) {
-	$relayState = (string)$_REQUEST['RelayState'];
+    $relayState = (string) $_REQUEST['RelayState'];
 }
 
 $associations = $idp->getAssociations();
 if (!isset($associations[$assocId])) {
-	throw new SimpleSAML_Error_BadRequest('Invalid association id.');
+    throw new SimpleSAML_Error_BadRequest('Invalid association id.');
 }
 $association = $associations[$assocId];
 
@@ -30,23 +30,23 @@ $lr = sspmod_saml_Message::buildLogoutRequest($idpMetadata, $spMetadata);
 $lr->setSessionIndex($association['saml:SessionIndex']);
 $lr->setNameId($association['saml:NameID']);
 
-$assertionLifetime = $spMetadata->getInteger('assertion.lifetime', NULL);
-if ($assertionLifetime === NULL) {
-	$assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300);
+$assertionLifetime = $spMetadata->getInteger('assertion.lifetime', null);
+if ($assertionLifetime === null) {
+    $assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300);
 }
 $lr->setNotOnOrAfter(time() + $assertionLifetime);
 
-$encryptNameId = $spMetadata->getBoolean('nameid.encryption', NULL);
-if ($encryptNameId === NULL) {
-	$encryptNameId = $idpMetadata->getBoolean('nameid.encryption', FALSE);
+$encryptNameId = $spMetadata->getBoolean('nameid.encryption', null);
+if ($encryptNameId === null) {
+    $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', false);
 }
 if ($encryptNameId) {
-	$lr->encryptNameId(sspmod_saml_Message::getEncryptionKey($spMetadata));
+    $lr->encryptNameId(sspmod_saml_Message::getEncryptionKey($spMetadata));
 }
 
 SimpleSAML_Stats::log('saml:idp:LogoutRequest:sent', array(
-	'spEntityID' => $association['saml:entityID'],
-	'idpEntityID' => $idpMetadata->getString('entityid'),
+    'spEntityID'  => $association['saml:entityID'],
+    'idpEntityID' => $idpMetadata->getString('entityid'),
 ));
 
 $bindings = array(\SAML2\Constants::BINDING_HTTP_POST);
diff --git a/modules/core/www/idp/logout-iframe.js b/modules/core/www/idp/logout-iframe.js
index 282425ba6cd20fbbddac5484723f976a7cde1d30..6fc401070571c41ec845234551588333276da539 100644
--- a/modules/core/www/idp/logout-iframe.js
+++ b/modules/core/www/idp/logout-iframe.js
@@ -1,74 +1,106 @@
+/**
+ * This function updates the global logout status.
+ */
 function updateStatus() {
 
-	var nFailed = 0;
-	var nProgress = 0;
-	for (sp in window.spStatus) {
-		switch (window.spStatus[sp]) {
-		case 'failed':
-			nFailed += 1;
-			break;
-		case 'inprogress':
-			nProgress += 1;
-			break;
-		}
-	}
+    var nFailed = 0;
+    var nProgress = 0;
+    for (sp in window.spStatus) {
+        switch (window.spStatus[sp]) {
+            case 'failed':
+                nFailed += 1;
+                break;
+            case 'inprogress':
+                nProgress += 1;
+                break;
+        }
+    }
 
-	if (nFailed > 0) {
-		$('#logout-failed-message').show();
-	}
+    if (nFailed > 0) {
+        $('#logout-failed-message').show();
+    }
 
-	if (nProgress == 0 && nFailed == 0) {
-		$('#logout-completed').show();
-		$('#done-form').submit();
-	}
+    if (nProgress === 0 && nFailed === 0) {
+        $('#logout-completed').show();
+        $('#done-form').submit();
+    }
 }
 
+/**
+ * This function updates the logout status for a given SP.
+ *
+ * @param spId The ID of the SP.
+ * @param status The new status.
+ * @param reason The reason for the status change.
+ */
 function updateSPStatus(spId, status, reason) {
-	if (window.spStatus[spId] == status) {
-		/* Unchanged. */
-		return;
-	}
+    if (window.spStatus[spId] === status) {
+        // unchanged
+        return;
+    }
 
-	$('#statusimage-' + spId).attr('src', window.stateImage[status]).attr('alt', window.stateText[status]).attr('title', reason);
-	window.spStatus[spId] = status;
+    $('#statusimage-' + spId).attr('src', window.stateImage[status]).attr('alt', window.stateText[status]).attr('title', reason);
+    window.spStatus[spId] = status;
 
-	var formId = 'logout-iframe-' + spId;
-	var existing = $('input[name="' + formId + '"]');
-	if (existing.length == 0) {
-		/* Don't have an existing form element - add one. */
-		var elementHTML = '<input type="hidden" name="' + formId + '" value="' + status + '" />';
-		$('#failed-form , #done-form').append(elementHTML);
-	} else {
-		/* Update existing element. */
-		existing.attr('value', status);
-	}
+    var formId = 'logout-iframe-' + spId;
+    var existing = $('input[name="' + formId + '"]');
+    if (existing.length === 0) {
+        // don't have an existing form element, add one
+        var elementHTML = '<input type="hidden" name="' + formId + '" value="' + status + '" />';
+        $('#failed-form , #done-form').append(elementHTML);
+    } else {
+        // update existing element
+        existing.attr('value', status);
+    }
 
-	updateStatus();
+    updateStatus();
 }
+
+/**
+ * Mark logout as completed for an SP.
+ *
+ * This method will be called by the SimpleSAML\IdP\IFrameLogoutHandler class upon successful logout from the SP.
+ *
+ * @param spId The SP that completed logout successfully.
+ */
 function logoutCompleted(spId) {
-	updateSPStatus(spId, 'completed', '');
+    updateSPStatus(spId, 'completed', '');
 }
+
+/**
+ * Mark logout as failed for an SP.
+ *
+ * This method will be called by the SimpleSAML\IdP\IFrameLogoutHandler class upon logout failure from the SP.
+ *
+ * @param spId The SP that failed to complete logout.
+ * @param reason The reason why logout failed.
+ */
 function logoutFailed(spId, reason) {
-	updateSPStatus(spId, 'failed', reason);
+    updateSPStatus(spId, 'failed', reason);
 }
 
+/**
+ * Set timeouts for all logout operations.
+ *
+ * If an SP didn't reply by the timeout, we'll mark it as failed.
+ */
 function timeoutSPs() {
-	var cTime = ( (new Date()).getTime() - window.startTime ) / 1000;
-	for (sp in window.spStatus) {
-		if (window.spTimeout[sp] <= cTime && window.spStatus[sp] == 'inprogress') {
-			logoutFailed(sp, 'Timeout');
-		}
-	}
-	window.timeoutID = window.setTimeout(timeoutSPs, 1000);
+    var cTime = ((new Date()).getTime() - window.startTime) / 1000;
+    for (var sp in window.spStatus) {
+        if (window.spTimeout[sp] <= cTime && window.spStatus[sp] === 'inprogress') {
+            logoutFailed(sp, 'Timeout');
+        }
+    }
+    window.timeoutID = window.setTimeout(timeoutSPs, 1000);
 }
 
-$('document').ready(function(){
-	window.startTime = (new Date()).getTime();
-	if (window.type == 'js') {
-		window.timeoutID = window.setTimeout(timeoutSPs, 1000);
-		updateStatus();
-	} else if (window.type == 'init') {
-		$('#logout-type-selector').attr('value', 'js');
-		$('#logout-all').focus();
-	}
+$('document').ready(function () {
+    window.startTime = (new Date()).getTime();
+    if (window.type === 'js') {
+        window.timeoutID = window.setTimeout(timeoutSPs, 1000);
+        updateStatus();
+    } else if (window.type === 'init') {
+        $('#logout-type-selector').attr('value', 'js');
+        $('#logout-all').focus();
+    }
 });
diff --git a/modules/core/www/idp/logout-iframe.php b/modules/core/www/idp/logout-iframe.php
index 137c916a0f7301937842b167e96b7df430338d7a..caf00f11db1a154534a9cf8eebf6326da7b78cbe 100644
--- a/modules/core/www/idp/logout-iframe.php
+++ b/modules/core/www/idp/logout-iframe.php
@@ -1,112 +1,144 @@
 <?php
 
 if (!isset($_REQUEST['id'])) {
-	throw new SimpleSAML_Error_BadRequest('Missing required parameter: id');
+    throw new SimpleSAML_Error_BadRequest('Missing required parameter: id');
 }
 
 if (isset($_REQUEST['type'])) {
-	$type = (string)$_REQUEST['type'];
-	if (!in_array($type, array('init', 'js', 'nojs', 'embed'), TRUE)) {
-		throw new SimpleSAML_Error_BadRequest('Invalid value for type.');
-	}
+    $type = (string) $_REQUEST['type'];
+    if (!in_array($type, array('init', 'js', 'nojs', 'embed'), true)) {
+        throw new SimpleSAML_Error_BadRequest('Invalid value for type.');
+    }
 } else {
-	$type = 'init';
+    $type = 'init';
 }
 
-if ($type !== 'embed' && $type !== 'async') {
-	SimpleSAML\Logger::stats('slo-iframe ' . $type);
-	SimpleSAML_Stats::log('core:idp:logout-iframe:page', array('type' => $type));
+if ($type !== 'embed') {
+    SimpleSAML\Logger::stats('slo-iframe '.$type);
+    SimpleSAML_Stats::log('core:idp:logout-iframe:page', array('type' => $type));
 }
 
 $state = SimpleSAML_Auth_State::loadState($_REQUEST['id'], 'core:Logout-IFrame');
 $idp = SimpleSAML_IdP::getByState($state);
+$mdh = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+
+if ($type !== 'init') { // update association state
+    foreach ($state['core:Logout-IFrame:Associations'] as $assocId => &$sp) {
+        $spId = sha1($assocId);
+
+        // move SPs from 'onhold' to 'inprogress'
+        if ($sp['core:Logout-IFrame:State'] === 'onhold') {
+            $sp['core:Logout-IFrame:State'] = 'inprogress';
+        }
+
+        // check for update through request
+        if (isset($_REQUEST[$spId])) {
+            $s = $_REQUEST[$spId];
+            if ($s == 'completed' || $s == 'failed') {
+                $sp['core:Logout-IFrame:State'] = $s;
+            }
+        }
+
+        // check for timeout
+        if (isset($sp['core:Logout-IFrame:Timeout']) && $sp['core:Logout-IFrame:Timeout'] < time()) {
+            if ($sp['core:Logout-IFrame:State'] === 'inprogress') {
+                $sp['core:Logout-IFrame:State'] = 'failed';
+            }
+        }
+
+        // update the IdP
+        if ($sp['core:Logout-IFrame:State'] === 'completed') {
+            $idp->terminateAssociation($assocId);
+        }
+
+        if (!isset($sp['core:Logout-IFrame:Timeout'])) {
+            if (method_exists($sp['Handler'], 'getAssociationConfig')) {
+                $assocIdP = SimpleSAML_IdP::getByState($sp);
+                $assocConfig = call_user_func(array($sp['Handler'], 'getAssociationConfig'), $assocIdP, $sp);
+                $sp['core:Logout-IFrame:Timeout'] = $assocConfig->getInteger('core:logout-timeout', 5) + time();
+            } else {
+                $sp['core:Logout-IFrame:Timeout'] = time() + 5;
+            }
+        }
+    }
+}
 
-if ($type !== 'init') {
-	// Update association state
-
-	$associations = $idp->getAssociations();
-
-	foreach ($state['core:Logout-IFrame:Associations'] as $assocId => &$sp) {
-
-		$spId = sha1($assocId);
-
-		// Move SPs from 'onhold' to 'inprogress'
-		if ($sp['core:Logout-IFrame:State'] === 'onhold') {
-			$sp['core:Logout-IFrame:State'] = 'inprogress';
-		}
-
-		/* Check for update through request. */
-		if (isset($_REQUEST[$spId])) {
-			$s = $_REQUEST[$spId];
-			if ($s == 'completed' || $s == 'failed') {
-				$sp['core:Logout-IFrame:State'] = $s;
-			}
-		}
-
-		/* Check for timeout. */
-		if (isset($sp['core:Logout-IFrame:Timeout']) && $sp['core:Logout-IFrame:Timeout'] < time()) {
-			if ($sp['core:Logout-IFrame:State'] === 'inprogress') {
-				$sp['core:Logout-IFrame:State'] = 'failed';
-			}
-		}
-
-		/* In case we are refreshing a page. */
-		if (!isset($associations[$assocId])) {
-			$sp['core:Logout-IFrame:State'] = 'completed';
-		}
-
-		/* Update the IdP. */
-		if ($sp['core:Logout-IFrame:State'] === 'completed') {
-			$idp->terminateAssociation($assocId);
-		}
-
-		if (!isset($sp['core:Logout-IFrame:Timeout'])) {
-			if (method_exists($sp['Handler'], 'getAssociationConfig')) {
-				$assocIdP = SimpleSAML_IdP::getByState($sp);
-				$assocConfig = call_user_func(array($sp['Handler'], 'getAssociationConfig'), $assocIdP, $sp);
-				$sp['core:Logout-IFrame:Timeout'] = $assocConfig->getInteger('core:logout-timeout', 5) + time();
-			} else {
-				$sp['core:Logout-IFrame:Timeout'] = time() + 5;
-			}
-		}
-	}
+$associations = $idp->getAssociations();
+foreach ($state['core:Logout-IFrame:Associations'] as $assocId => &$sp) {
+    // in case we are refreshing a page
+    if (!isset($associations[$assocId])) {
+        $sp['core:Logout-IFrame:State'] = 'completed';
+    }
+
+    try {
+        $assocIdP = SimpleSAML_IdP::getByState($sp);
+        $url = call_user_func(array($sp['Handler'], 'getLogoutURL'), $assocIdP, $sp, null);
+        $sp['core:Logout-IFrame:URL'] = $url;
+    } catch (Exception $e) {
+        $sp['core:Logout-IFrame:State'] = 'failed';
+    }
 }
 
-if ($type === 'js' || $type === 'nojs') {
-	foreach ($state['core:Logout-IFrame:Associations'] as $assocId => &$sp) {
-
-		if ($sp['core:Logout-IFrame:State'] !== 'inprogress') {
-			/* This SP isn't logging out. */
-			continue;
-		}
-
-		try {
-			$assocIdP = SimpleSAML_IdP::getByState($sp);
-			$url = call_user_func(array($sp['Handler'], 'getLogoutURL'), $assocIdP, $sp, NULL);
-			$sp['core:Logout-IFrame:URL'] = $url;
-		} catch (Exception $e) {
-			$sp['core:Logout-IFrame:State'] = 'failed';
-		}
-	}
+// get the metadata of the service that initiated logout, if any
+$terminated = null;
+if ($state['core:TerminatedAssocId'] !== null) {
+    $mdset = 'saml20-sp-remote';
+    if (substr($state['core:TerminatedAssocId'], 0, 4) === 'adfs') {
+        $mdset = 'adfs-sp-remote';
+    }
+    $terminated = $mdh->getMetaDataConfig($state['saml:SPEntityId'], $mdset)->toArray();
 }
 
-$id = SimpleSAML_Auth_State::saveState($state, 'core:Logout-IFrame');
+// build an array with information about all services currently logged in
+$remaining = array();
+foreach ($state['core:Logout-IFrame:Associations'] as $association) {
+    $key = sha1($association['id']);
+    $mdset = 'saml20-sp-remote';
+    if (substr($association['id'], 0, 4) === 'adfs') {
+        $mdset = 'adfs-sp-remote';
+    }
+
+    $remaining[$key] = array(
+        'id' => $association['id'],
+        'expires_on' => $association['Expires'],
+        'entityID' => $association['saml:entityID'],
+        'subject' => $association['saml:NameID'],
+        'status' => $association['core:Logout-IFrame:State'],
+        'logoutURL' => $association['core:Logout-IFrame:URL'],
+        'metadata' => $mdh->getMetaDataConfig($association['saml:entityID'], $mdset)->toArray(),
+    );
+    if (isset($association['core:Logout-IFrame:Timeout'])) {
+        $remaining[$key]['timeout'] = $association['core:Logout-IFrame:Timeout'];
+    }
+}
 
+$id = SimpleSAML_Auth_State::saveState($state, 'core:Logout-IFrame');
 $globalConfig = SimpleSAML_Configuration::getInstance();
 
+$template_id = 'core:logout-iframe.php';
 if ($type === 'nojs') {
-	$t = new SimpleSAML_XHTML_Template($globalConfig, 'core:logout-iframe-wrapper.php');
-	$t->data['id'] = $id;
-	$t->data['SPs'] = $state['core:Logout-IFrame:Associations'];
-	$t->show();
-	exit(0);
+    $template_id = 'core:logout-iframe-wrapper.php';
 }
 
-$t = new SimpleSAML_XHTML_Template($globalConfig, 'core:logout-iframe.php');
+$t = new SimpleSAML_XHTML_Template($globalConfig, $template_id);
+$t->data['auth_state'] = $id;
+/**
+ * @deprecated The "id" variable will be removed. Please use "auth_state" instead.
+ */
 $t->data['id'] = $id;
 $t->data['type'] = $type;
+$t->data['terminated_service'] = $terminated;
+$t->data['remaining_services'] = $remaining;
+
+/** @deprecated The "from" array will be removed in 2.0, use the "terminated_service" array instead */
 $t->data['from'] = $state['core:Logout-IFrame:From'];
+
+/** @deprecated The "SPs" array will be removed, use the "remaining_services" array instead */
 $t->data['SPs'] = $state['core:Logout-IFrame:Associations'];
-$t->data['jquery'] = array('core' => TRUE, 'ui' => FALSE, 'css' => FALSE);
+
+if ($type !== 'nojs') {
+    /** @deprecated The "jquery" array will be removed in 2.0 */
+    $t->data['jquery'] = array('core' => true, 'ui' => false, 'css' => false);
+}
+
 $t->show();
-exit(0);
diff --git a/modules/core/www/idp/resumelogout.php b/modules/core/www/idp/resumelogout.php
index 7a524f61bd5667cd5e99f16dba6331ce24523a74..199dad3b6f33515cf24ea1c944d89bfa1eb9b252 100644
--- a/modules/core/www/idp/resumelogout.php
+++ b/modules/core/www/idp/resumelogout.php
@@ -1,7 +1,7 @@
 <?php
 
 if (!isset($_REQUEST['id'])) {
-	throw new SimpleSAML_Error_BadRequest('Missing id-parameter.');
+    throw new SimpleSAML_Error_BadRequest('Missing id-parameter.');
 }
 $state = SimpleSAML_Auth_State::loadState($_REQUEST['id'], 'core:Logout:afterbridge');
 $idp = SimpleSAML_IdP::getByState($state);
@@ -10,4 +10,3 @@ $assocId = $state['core:TerminatedAssocId'];
 
 $handler = $idp->getLogoutHandler();
 $handler->startLogout($state, $assocId);
-assert('FALSE');
diff --git a/modules/core/www/loginuserpass.php b/modules/core/www/loginuserpass.php
index 1ccf6b58b46df854104340541493cf6f4bca20de..78ba32a0a384f38983bbb6bc25c495794f2fbe08 100644
--- a/modules/core/www/loginuserpass.php
+++ b/modules/core/www/loginuserpass.php
@@ -49,7 +49,7 @@ if (!empty($_REQUEST['username']) || !empty($password)) {
 	}
 
 	if ($source->getRememberUsernameEnabled()) {
-		$sessionHandler = SimpleSAML_SessionHandler::getSessionHandler();
+		$sessionHandler = \SimpleSAML\SessionHandler::getSessionHandler();
 		$params = $sessionHandler->getCookieParams();
 		$params['expire'] = time();
 		$params['expire'] += (isset($_REQUEST['remember_username']) && $_REQUEST['remember_username'] == 'Yes' ? 31536000 : -300);
diff --git a/modules/core/www/loginuserpassorg.php b/modules/core/www/loginuserpassorg.php
index 4f33b36f186b1b790e8e6fa8777b592a9cbe9399..61ba28354f836db1695f8cf89ff09f4804fdc8b8 100644
--- a/modules/core/www/loginuserpassorg.php
+++ b/modules/core/www/loginuserpassorg.php
@@ -53,7 +53,7 @@ if ($organizations === NULL || !empty($organization)) {
 	if (!empty($username) && !empty($password)) {
 
 		if ($source->getRememberUsernameEnabled()) {
-			$sessionHandler = SimpleSAML_SessionHandler::getSessionHandler();
+			$sessionHandler = \SimpleSAML\SessionHandler::getSessionHandler();
 			$params = $sessionHandler->getCookieParams();
 			$params['expire'] = time();
 			$params['expire'] += (isset($_REQUEST['remember_username']) && $_REQUEST['remember_username'] == 'Yes' ? 31536000 : -300);
diff --git a/modules/discopower/www/js/suggest.js b/modules/discopower/www/js/suggest.js
index 770260ac32486f61dc42c2ed96f8f27a015e0d75..667b35ac944d2aead91124e72542bba5409e00e6 100644
--- a/modules/discopower/www/js/suggest.js
+++ b/modules/discopower/www/js/suggest.js
@@ -12,7 +12,7 @@ String.prototype.score = function(abbreviation,offset) {
 			suggest_cache['re'][i] = new Array();
 			// /\b<x>/ doesn't work when <x> i a non-ascii - oddly enough \s does ...
 			suggest_cache['re'][i]['initialword'] = new RegExp("^"+words[i], "i");
-			suggest_cache['re'][i]['word'] = new RegExp("\\s"+words[i], "i");
+			suggest_cache['re'][i]['word'] = new RegExp("[\\s-()_]"+words[i], "i");
 		}
 	}
 
@@ -20,4 +20,4 @@ String.prototype.score = function(abbreviation,offset) {
 		if (!(this.match(suggest_cache['re'][i]['initialword']) || this.match(suggest_cache['re'][i]['word']))) return 0;
 	}
 	return 1;
-}
\ No newline at end of file
+}
diff --git a/modules/ldap/lib/Auth/Process/AttributeAddFromLDAP.php b/modules/ldap/lib/Auth/Process/AttributeAddFromLDAP.php
index b1c5910cb3e77de32295e8cf03b09c628479d7e0..f124ecc8b6ed2c7bfe6cd3303b7c839468f53c66 100644
--- a/modules/ldap/lib/Auth/Process/AttributeAddFromLDAP.php
+++ b/modules/ldap/lib/Auth/Process/AttributeAddFromLDAP.php
@@ -19,14 +19,17 @@
  *          - Updated the constructor to use the new config method
  *          - Updated the process method to use the new config variable names
  * Updated: 20131119 Yørn de Jong / Jaime Perez
- *      - Added support for retrieving multiple values at once from LDAP
- *      - Don't crash but fail silently on LDAP errors; the plugin is to complement attributes
+ *          - Added support for retrieving multiple values at once from LDAP
+ *          - Don't crash but fail silently on LDAP errors; the plugin is to complement attributes
+ * Updated: 20161223 Remy Blom <remy.blom@hku.nl>
+ *          - Adjusted the silent fail so it does show a warning in log when $this->getLdap() fails
  *
  * @author Yørn de Jong
  * @author Jaime Perez
  * @author Steve Moitozo
  * @author JAARS, Inc.
  * @author Ryan Panning
+ * @author Remy Blom <remy.blom@hku.nl>
  * @package SimpleSAMLphp
  */
 class sspmod_ldap_Auth_Process_AttributeAddFromLDAP extends sspmod_ldap_Auth_Process_BaseFilter
@@ -167,9 +170,17 @@ class sspmod_ldap_Auth_Process_AttributeAddFromLDAP extends sspmod_ldap_Auth_Pro
             return;
         }
 
+        // getLdap
+        try {
+          $ldap = $this->getLdap();
+        } catch (Exception $e) {
+          // Added this warning in case $this->getLdap() fails
+          SimpleSAML\Logger::warning("AttributeAddFromLDAP: exception = " . $e);
+          return;
+        }
         // search for matching entries
         try {
-            $entries = $this->getLdap()->searchformultiple($this->base_dn, $filter,
+            $entries = $ldap->searchformultiple($this->base_dn, $filter,
                                                            array_values($this->search_attributes), true, false);
         } catch (Exception $e) {
             return; // silent fail, error is still logged by LDAP search
diff --git a/modules/ldap/lib/Auth/Process/BaseFilter.php b/modules/ldap/lib/Auth/Process/BaseFilter.php
index 22aa197bf1bbda1f9348f0d8081543d8520730d8..41261d2448b59a68199672daf7e02ae59105cdc6 100644
--- a/modules/ldap/lib/Auth/Process/BaseFilter.php
+++ b/modules/ldap/lib/Auth/Process/BaseFilter.php
@@ -5,7 +5,11 @@
  * filter classes direct access to the authsource ldap config
  * and connects to the ldap server.
  *
+ * Updated: 20161223 Remy Blom
+ *          - Wrapped the building of authsource config with issets
+ *
  * @author Ryan Panning <panman@traileyes.com>
+ * @author Remy Blom <remy.blom@hku.nl>
  * @package SimpleSAMLphp
  */
 abstract class sspmod_ldap_Auth_Process_BaseFilter extends SimpleSAML_Auth_ProcessingFilter
@@ -137,21 +141,46 @@ abstract class sspmod_ldap_Auth_Process_BaseFilter extends SimpleSAML_Auth_Proce
 
             // Build the authsource config
             $authconfig = array();
-            $authconfig['ldap.hostname']   = @$authsource['hostname'];
-            $authconfig['ldap.enable_tls'] = @$authsource['enable_tls'];
-            $authconfig['ldap.port']       = @$authsource['port'];
-            $authconfig['ldap.timeout']    = @$authsource['timeout'];
-            $authconfig['ldap.debug']      = @$authsource['debug'];
-            $authconfig['ldap.basedn']     = (@$authsource['search.enable'] ? @$authsource['search.base'] : null);
-            $authconfig['ldap.username']   = (@$authsource['search.enable'] ? @$authsource['search.username'] : null);
-            $authconfig['ldap.password']   = (@$authsource['search.enable'] ? @$authsource['search.password'] : null);
-            $authconfig['ldap.username']   = (@$authsource['priv.read'] ? @$authsource['priv.username'] : $authconfig['ldap.username']);
-            $authconfig['ldap.password']   = (@$authsource['priv.read'] ? @$authsource['priv.password'] : $authconfig['ldap.password']);
-
-            // Only set the username attribute if the authsource specifies one attribute
-            if (@$authsource['search.enable'] && is_array(@$authsource['search.attributes'])
-                 && count($authsource['search.attributes']) == 1) {
-                $authconfig['attribute.username'] = reset($authsource['search.attributes']);
+            if (isset($authsource['hostname'])) {
+                $authconfig['ldap.hostname']   = $authsource['hostname'];
+            }
+            if (isset($authsource['enable_tls'])) {
+                $authconfig['ldap.enable_tls'] = $authsource['enable_tls'];
+            }
+            if (isset($authsource['port'])) {
+                $authconfig['ldap.port']       = $authsource['port'];
+            }
+            if (isset($authsource['timeout'])) {
+                $authconfig['ldap.timeout']    = $authsource['timeout'];
+            }
+            if (isset($authsource['debug'])) {
+                $authconfig['ldap.debug']      = $authsource['debug'];
+            }
+            // only set when search.enabled = true
+            if (isset($authsource['search.enable']) && $authsource['search.enable']) {
+                if (isset($authsource['search.base'])) {
+                    $authconfig['ldap.basedn']     = $authsource['search.base'];
+                }
+                if (isset($authsource['search.username'])) {
+                    $authconfig['ldap.username']   = $authsource['search.username'];
+                }
+                if (isset($authsource['search.password'])) {
+                    $authconfig['ldap.password']   = $authsource['search.password'];
+                }
+                // Only set the username attribute if the authsource specifies one attribute
+                if (isset($authsource['search.attributes']) && is_array($authsource['search.attributes'])
+                     && count($authsource['search.attributes']) == 1) {
+                    $authconfig['attribute.username'] = reset($authsource['search.attributes']);
+                }
+            }
+            // only set when priv.read = true
+            if (isset($authsource['priv.read']) && $authsource['priv.read']) {
+                if (isset($authsource['priv.username'])) {
+                    $authconfig['ldap.username']   = $authsource['priv.username'];
+                }
+                if (isset($authsource['priv.password'])) {
+                    $authconfig['ldap.password']   = $authsource['priv.password'];
+                }
             }
 
             // Merge the authsource config with the filter config,
diff --git a/modules/multiauth/lib/Auth/Source/MultiAuth.php b/modules/multiauth/lib/Auth/Source/MultiAuth.php
index f3acc9016a432c44f7ab9e7c7daac5d6e20a1966..07c14810505c55990a1e3a0eb5ae1bcd4b0fb856 100644
--- a/modules/multiauth/lib/Auth/Source/MultiAuth.php
+++ b/modules/multiauth/lib/Auth/Source/MultiAuth.php
@@ -143,7 +143,13 @@ class sspmod_multiauth_Auth_Source_MultiAuth extends SimpleSAML_Auth_Source {
 		assert('is_array($state)');
 
 		$as = SimpleSAML_Auth_Source::getById($authId);
-		if ($as === NULL) {
+		$valid_sources = array_map(
+			function($src) {
+				return $src['source'];
+			},
+			$state[self::SOURCESID]
+        );
+		if ($as === NULL || !in_array($authId, $valid_sources)) {
 			throw new Exception('Invalid authentication source: ' . $authId);
 		}
 
diff --git a/modules/oauth/libextinc/OAuth.php b/modules/oauth/libextinc/OAuth.php
index c90e4f914e5f9446e2f483f634dd4be0116a9fe5..24e68382a90e9a1b6747c806e2ce8fe5c9d94dd0 100644
--- a/modules/oauth/libextinc/OAuth.php
+++ b/modules/oauth/libextinc/OAuth.php
@@ -1,55 +1,68 @@
 <?php
-// vim: foldmethod=marker
-
-// Generic exception class
+/**
+ * @file
+ * OAuth 1.0 server and client library.
+ */
 
-class OAuthException extends Exception {
-  // pass
+/**
+ * OAuth PECL extension includes an OAuth Exception class, so we need to wrap
+ * the definition of this class in order to avoid a PHP error.
+ */
+if (!class_exists('OAuthException')) {
+    /*
+     * Generic exception class
+     */
+    class OAuthException extends Exception {
+        // pass
+    }
 }
 
-class OAuthConsumer {
-  public $key;
-  public $secret;
+if (!class_exists('OAuthConsumer')) {
+    class OAuthConsumer {
+        public $key;
+        public $secret;
 
-  function __construct($key, $secret, $callback_url=NULL) {
-    $this->key = $key;
-    $this->secret = $secret;
-    $this->callback_url = $callback_url;
-  }
+        function __construct($key, $secret, $callback_url=NULL) {
+            $this->key = $key;
+            $this->secret = $secret;
+            $this->callback_url = $callback_url;
+        }
 
-  function __toString() {
-    return "OAuthConsumer[key=$this->key,secret=$this->secret]";
-  }
+        function __toString() {
+            return "OAuthConsumer[key=$this->key,secret=$this->secret]";
+        }
+    }
 }
 
 class OAuthToken {
-  // access tokens and request tokens
-  public $key;
-  public $secret;
-
-  /**
-   * key = the token
-   * secret = the token secret
-   */
-  function __construct($key, $secret) {
-    $this->key = $key;
-    $this->secret = $secret;
-  }
-
-  /**
-   * generates the basic string serialization of a token that a server
-   * would respond to request_token and access_token calls with
-   */
-  function to_string() {
-    return "oauth_token=" .
-           OAuthUtil::urlencode_rfc3986($this->key) .
-           "&oauth_token_secret=" .
-           OAuthUtil::urlencode_rfc3986($this->secret);
-  }
-
-  function __toString() {
-    return $this->to_string();
-  }
+    // access tokens and request tokens
+    public $key;
+    public $secret;
+
+    /**
+     * key = the token
+     * secret = the token secret
+     */
+    function __construct($key, $secret) {
+        $this->key = $key;
+        $this->secret = $secret;
+    }
+
+    /**
+     * generates the basic string serialization of a token that a server
+     * would respond to request_token and access_token calls with
+     */
+    function to_string() {
+        return "oauth_token=" .
+        OAuthUtil::urlencode_rfc3986($this->key) .
+        "&oauth_token_secret=" .
+        OAuthUtil::urlencode_rfc3986($this->secret) .
+        "&oauth_callback_confirmed=true";
+    }
+
+    function __toString() {
+        return $this->to_string();
+    }
 }
 
 /**
@@ -57,837 +70,846 @@ class OAuthToken {
  * See section 9 ("Signing Requests") in the spec
  */
 abstract class OAuthSignatureMethod {
-  /**
-   * Needs to return the name of the Signature Method (ie HMAC-SHA1)
-   * @return string
-   */
-  abstract public function get_name();
-
-  /**
-   * Build up the signature
-   * NOTE: The output of this function MUST NOT be urlencoded.
-   * the encoding is handled in OAuthRequest when the final
-   * request is serialized
-   * @param OAuthRequest $request
-   * @param OAuthConsumer $consumer
-   * @param OAuthToken $token
-   * @return string
-   */
-  abstract public function build_signature($request, $consumer, $token);
-
-  /**
-   * Verifies that a given signature is correct
-   * @param OAuthRequest $request
-   * @param OAuthConsumer $consumer
-   * @param OAuthToken $token
-   * @param string $signature
-   * @return bool
-   */
-  public function check_signature($request, $consumer, $token, $signature) {
-    $built = $this->build_signature($request, $consumer, $token);
-
-    // Check for zero length, although unlikely here
-    if (strlen($built) == 0 || strlen($signature) == 0) {
-      return false;
-    }
-
-    if (strlen($built) != strlen($signature)) {
-      return false;
-    }
-
-    // Avoid a timing leak with a (hopefully) time insensitive compare
-    $result = 0;
-    for ($i = 0; $i < strlen($signature); $i++) {
-      $result |= ord($built{$i}) ^ ord($signature{$i});
-    }
-
-    return $result == 0;
-  }
-  }
+    /**
+     * Needs to return the name of the Signature Method (ie HMAC-SHA1)
+     * @return string
+     */
+    abstract public function get_name();
+
+    /**
+     * Build up the signature
+     * NOTE: The output of this function MUST NOT be urlencoded.
+     * the encoding is handled in OAuthRequest when the final
+     * request is serialized
+     * @param OAuthRequest $request
+     * @param OAuthConsumer $consumer
+     * @param OAuthToken $token
+     * @return string
+     */
+    abstract public function build_signature($request, $consumer, $token);
+
+    /**
+     * Verifies that a given signature is correct
+     * @param OAuthRequest $request
+     * @param OAuthConsumer $consumer
+     * @param OAuthToken $token
+     * @param string $signature
+     * @return bool
+     */
+    public function check_signature($request, $consumer, $token, $signature) {
+        $built = $this->build_signature($request, $consumer, $token);
+
+        // Check for zero length, although unlikely here
+        if (strlen($built) == 0 || strlen($signature) == 0) {
+            return false;
+        }
+
+        if (strlen($built) != strlen($signature)) {
+            return false;
+        }
+
+        // Avoid a timing leak with a (hopefully) time insensitive compare
+        $result = 0;
+        for ($i = 0; $i < strlen($signature); $i++) {
+            $result |= ord($built{$i}) ^ ord($signature{$i});
+        }
+
+        return $result == 0;
+    }
+}
 
 /**
- * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104] 
- * where the Signature Base String is the text and the key is the concatenated values (each first 
- * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&' 
+ * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104]
+ * where the Signature Base String is the text and the key is the concatenated values (each first
+ * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&'
  * character (ASCII code 38) even if empty.
  *   - Chapter 9.2 ("HMAC-SHA1")
  */
 class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {
-  function get_name() {
-    return "HMAC-SHA1";
-  }
+    function get_name() {
+        return "HMAC-SHA1";
+    }
 
-  public function build_signature($request, $consumer, $token) {
-    $base_string = $request->get_signature_base_string();
-    $request->base_string = $base_string;
+    public function build_signature($request, $consumer, $token) {
+        $base_string = $request->get_signature_base_string();
+        $request->base_string = $base_string;
 
-    $key_parts = array(
-      $consumer->secret,
-      ($token) ? $token->secret : ""
-    );
+        $key_parts = array(
+            $consumer->secret,
+            ($token) ? $token->secret : ""
+        );
 
-    $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
-    $key = implode('&', $key_parts);
+        $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
+        $key = implode('&', $key_parts);
 
-    return base64_encode( hash_hmac('sha1', $base_string, $key, true));
-  }
+        return base64_encode(hash_hmac('sha1', $base_string, $key, true));
+    }
 }
 
 /**
- * The PLAINTEXT method does not provide any security protection and SHOULD only be used 
+ * The PLAINTEXT method does not provide any security protection and SHOULD only be used
  * over a secure channel such as HTTPS. It does not use the Signature Base String.
  *   - Chapter 9.4 ("PLAINTEXT")
  */
 class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {
-  public function get_name() {
-    return "PLAINTEXT";
-  }
-
-  /**
-   * oauth_signature is set to the concatenated encoded values of the Consumer Secret and 
-   * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is 
-   * empty. The result MUST be encoded again.
-   *   - Chapter 9.4.1 ("Generating Signatures")
-   *
-   * Please note that the second encoding MUST NOT happen in the SignatureMethod, as
-   * OAuthRequest handles this!
-   */
-  public function build_signature($request, $consumer, $token) {
-    $key_parts = array(
-      $consumer->secret,
-      ($token) ? $token->secret : ""
-    );
-
-    $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
-    $key = implode('&', $key_parts);
-    $request->base_string = $key;
-
-    return $key;
-  }
+    public function get_name() {
+        return "PLAINTEXT";
+    }
+
+    /**
+     * oauth_signature is set to the concatenated encoded values of the Consumer Secret and
+     * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is
+     * empty. The result MUST be encoded again.
+     *   - Chapter 9.4.1 ("Generating Signatures")
+     *
+     * Please note that the second encoding MUST NOT happen in the SignatureMethod, as
+     * OAuthRequest handles this!
+     */
+    public function build_signature($request, $consumer, $token) {
+        $key_parts = array(
+            $consumer->secret,
+            ($token) ? $token->secret : ""
+        );
+
+        $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
+        $key = implode('&', $key_parts);
+        $request->base_string = $key;
+
+        return $key;
+    }
 }
 
 /**
- * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in 
- * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for 
- * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a 
- * verified way to the Service Provider, in a manner which is beyond the scope of this 
+ * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in
+ * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for
+ * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a
+ * verified way to the Service Provider, in a manner which is beyond the scope of this
  * specification.
  *   - Chapter 9.3 ("RSA-SHA1")
  */
 abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {
-  public function get_name() {
-    return "RSA-SHA1";
-  }
+    public function get_name() {
+        return "RSA-SHA1";
+    }
 
-  // Up to the SP to implement this lookup of keys. Possible ideas are:
+    // Up to the SP to implement this lookup of keys. Possible ideas are:
     // (1) do a lookup in a table of trusted certs keyed off of consumer
-    // (2) fetch via http using a URL provided by the requester
+    // (2) fetch via http using a url provided by the requester
     // (3) some sort of specific discovery code based on request
     //
-  // Either way should return a string representation of the certificate
-  protected abstract function fetch_public_cert(&$request);
+    // Either way should return a string representation of the certificate
+    protected abstract function fetch_public_cert(&$request);
 
-  // Up to the SP to implement this lookup of keys. Possible ideas are:
+    // Up to the SP to implement this lookup of keys. Possible ideas are:
     // (1) do a lookup in a table of trusted certs keyed off of consumer
     //
-  // Either way should return a string representation of the certificate
-  protected abstract function fetch_private_cert(&$request);
-
-  public function build_signature($request, $consumer, $token) {
-    $base_string = $request->get_signature_base_string();
-    $request->base_string = $base_string;
-  
-    // Fetch the private key cert based on the request
-    $cert = $this->fetch_private_cert($request);
-
-    // Pull the private key ID from the certificate
-    $privatekeyid = openssl_get_privatekey($cert);
-
-    // Sign using the key
-    $ok = openssl_sign($base_string, $signature, $privatekeyid);   
-
-    // Release the key resource
-    openssl_free_key($privatekeyid);
-  
-    return base64_encode($signature);
-  }
-
-  public function check_signature($request, $consumer, $token, $signature) {
-    $decoded_sig = base64_decode($signature);
-
-    $base_string = $request->get_signature_base_string();
-  
-    // Fetch the public key cert based on the request
-    $cert = $this->fetch_public_cert($request);
-
-    // Pull the public key ID from the certificate
-    $publickeyid = openssl_get_publickey($cert);
-
-    // Check the computed signature against the one passed in the query
-    $ok = openssl_verify($base_string, $decoded_sig, $publickeyid);   
-
-    // Release the key resource
-    openssl_free_key($publickeyid);
-  
-    return $ok == 1;
-  }
+    // Either way should return a string representation of the certificate
+    protected abstract function fetch_private_cert(&$request);
+
+    public function build_signature($request, $consumer, $token) {
+        $base_string = $request->get_signature_base_string();
+        $request->base_string = $base_string;
+
+        // Fetch the private key cert based on the request
+        $cert = $this->fetch_private_cert($request);
+
+        // Pull the private key ID from the certificate
+        $privatekeyid = openssl_get_privatekey($cert);
+
+        // Sign using the key
+        $ok = openssl_sign($base_string, $signature, $privatekeyid);
+
+        // Release the key resource
+        openssl_free_key($privatekeyid);
+
+        return base64_encode($signature);
+    }
+
+    public function check_signature($request, $consumer, $token, $signature) {
+        $decoded_sig = base64_decode($signature);
+
+        $base_string = $request->get_signature_base_string();
+
+        // Fetch the public key cert based on the request
+        $cert = $this->fetch_public_cert($request);
+
+        // Pull the public key ID from the certificate
+        $publickeyid = openssl_get_publickey($cert);
+
+        // Check the computed signature against the one passed in the query
+        $ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
+
+        // Release the key resource
+        openssl_free_key($publickeyid);
+
+        return $ok == 1;
+    }
 }
 
 class OAuthRequest {
-  protected $parameters;
-  protected $http_method;
-  protected $http_url;
-  // for debug purposes
-  public $base_string;
-  public static $version = '1.0';
-  public static $POST_INPUT = 'php://input';
-
-  function __construct($http_method, $http_url, $parameters=NULL) {
-    $parameters = ($parameters) ? $parameters : array();
-    $parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);
-    $this->parameters = $parameters;
-    $this->http_method = $http_method;
-    $this->http_url = $http_url;
-  }
-
-
-  /**
-   * attempt to build up a request from what was passed to the server
-   */
-  public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {
-    $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
-              ? 'http'
-              : 'https';
-    $http_url = ($http_url) ? $http_url : $scheme .
-                              '://' . $_SERVER['SERVER_NAME'] .
-                              ':' .
-                              $_SERVER['SERVER_PORT'] .
-                              $_SERVER['REQUEST_URI'];
-    $http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD'];
-
-    // We weren't handed any parameters, so let's find the ones relevant to
-    // this request
-    // If you run XML-RPC or similar you should use this to provide your own
-    // parsed parameter-list
-    if (!$parameters) {
-      // Find request headers
-      $request_headers = OAuthUtil::get_headers();
-
-      // Parse the query-string to find GET parameters
-      $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
-
-      // It's a POST request of the proper content-type, so parse POST
-      // parameters and add those overriding any duplicates from GET
-      if ($http_method == "POST"
-          &&  isset($request_headers['Content-Type'])
-          && strstr($request_headers['Content-Type'],
-                     'application/x-www-form-urlencoded')
-          ) {
-        $post_data = OAuthUtil::parse_parameters(
-          file_get_contents(self::$POST_INPUT)
-        );
-        $parameters = array_merge($parameters, $post_data);
-      }
-
-      // We have a Authorization-header with OAuth data. Parse the header
-      // and add those overriding any duplicates from GET or POST
-      if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') {
-        $header_parameters = OAuthUtil::split_header(
-          $request_headers['Authorization']
+    protected $parameters;
+    protected $http_method;
+    protected $http_url;
+    // for debug purposes
+    public $base_string;
+    public static $version = '1.0';
+    public static $POST_INPUT = 'php://input';
+
+    function __construct($http_method, $http_url, $parameters=NULL) {
+        $parameters = ($parameters) ? $parameters : array();
+        $parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);
+        $this->parameters = $parameters;
+        $this->http_method = $http_method;
+        $this->http_url = $http_url;
+    }
+
+
+    /**
+     * attempt to build up a request from what was passed to the server
+     */
+    public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {
+        $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
+            ? 'http'
+            : 'https';
+        $http_url = ($http_url) ? $http_url : $scheme .
+            '://' . $_SERVER['SERVER_NAME'] .
+            ':' .
+            $_SERVER['SERVER_PORT'] .
+            $_SERVER['REQUEST_URI'];
+        $http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD'];
+
+        // We weren't handed any parameters, so let's find the ones relevant to
+        // this request.
+        // If you run XML-RPC or similar you should use this to provide your own
+        // parsed parameter-list
+        if (!$parameters) {
+            // Find request headers
+            $request_headers = OAuthUtil::get_headers();
+
+            // Parse the query-string to find GET parameters
+            $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
+
+            // It's a POST request of the proper content-type, so parse POST
+            // parameters and add those overriding any duplicates from GET
+            if ($http_method == "POST"
+                &&  isset($request_headers['Content-Type'])
+                && strstr($request_headers['Content-Type'],
+                    'application/x-www-form-urlencoded')
+            ) {
+                $post_data = OAuthUtil::parse_parameters(
+                    file_get_contents(self::$POST_INPUT)
+                );
+                $parameters = array_merge($parameters, $post_data);
+            }
+
+            // We have a Authorization-header with OAuth data. Parse the header
+            // and add those overriding any duplicates from GET or POST
+            if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') {
+                $header_parameters = OAuthUtil::split_header(
+                    $request_headers['Authorization']
+                );
+                $parameters = array_merge($parameters, $header_parameters);
+            }
+
+        }
+
+        return new OAuthRequest($http_method, $http_url, $parameters);
+    }
+
+    /**
+     * pretty much a helper function to set up the request
+     */
+    public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {
+        $parameters = ($parameters) ?  $parameters : array();
+        $defaults = array("oauth_version" => OAuthRequest::$version,
+                          "oauth_nonce" => OAuthRequest::generate_nonce(),
+                          "oauth_timestamp" => OAuthRequest::generate_timestamp(),
+                          "oauth_consumer_key" => $consumer->key);
+        if ($token)
+            $defaults['oauth_token'] = $token->key;
+
+        $parameters = array_merge($defaults, $parameters);
+
+        return new OAuthRequest($http_method, $http_url, $parameters);
+    }
+
+    public function set_parameter($name, $value, $allow_duplicates = true) {
+        if ($allow_duplicates && isset($this->parameters[$name])) {
+            // We have already added parameter(s) with this name, so add to the list
+            if (is_scalar($this->parameters[$name])) {
+                // This is the first duplicate, so transform scalar (string)
+                // into an array so we can add the duplicates
+                $this->parameters[$name] = array($this->parameters[$name]);
+            }
+
+            $this->parameters[$name][] = $value;
+        } else {
+            $this->parameters[$name] = $value;
+        }
+    }
+
+    public function get_parameter($name) {
+        return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
+    }
+
+    public function get_parameters() {
+        return $this->parameters;
+    }
+
+    public function unset_parameter($name) {
+        unset($this->parameters[$name]);
+    }
+
+    /**
+     * The request parameters, sorted and concatenated into a normalized string.
+     * @return string
+     */
+    public function get_signable_parameters() {
+        // Grab all parameters
+        $params = $this->parameters;
+
+        // Remove oauth_signature if present
+        // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
+        if (isset($params['oauth_signature'])) {
+            unset($params['oauth_signature']);
+        }
+
+        return OAuthUtil::build_http_query($params);
+    }
+
+    /**
+     * Returns the base string of this request
+     *
+     * The base string defined as the method, the url
+     * and the parameters (normalized), each urlencoded
+     * and the concated with &.
+     */
+    public function get_signature_base_string() {
+        $parts = array(
+            $this->get_normalized_http_method(),
+            $this->get_normalized_http_url(),
+            $this->get_signable_parameters()
         );
-        $parameters = array_merge($parameters, $header_parameters);
-      }
-
-    }
-
-    return new OAuthRequest($http_method, $http_url, $parameters);
-  }
-
-  /**
-   * pretty much a helper function to set up the request
-   */
-  public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {
-    $parameters = ($parameters) ?  $parameters : array();
-    $defaults = array("oauth_version" => OAuthRequest::$version,
-                      "oauth_nonce" => OAuthRequest::generate_nonce(),
-                      "oauth_timestamp" => OAuthRequest::generate_timestamp(),
-                      "oauth_consumer_key" => $consumer->key);
-    if ($token)
-      $defaults['oauth_token'] = $token->key;
-
-    $parameters = array_merge($defaults, $parameters);
-
-    return new OAuthRequest($http_method, $http_url, $parameters);
-  }
-
-  public function set_parameter($name, $value, $allow_duplicates = true) {
-    if ($allow_duplicates && isset($this->parameters[$name])) {
-      // We have already added parameter(s) with this name, so add to the list
-      if (is_scalar($this->parameters[$name])) {
-        // This is the first duplicate, so transform scalar (string)
-        // into an array so we can add the duplicates
-        $this->parameters[$name] = array($this->parameters[$name]);
-      }
-
-      $this->parameters[$name][] = $value;
-    } else {
-    $this->parameters[$name] = $value;
-    }
-  }
-
-  public function get_parameter($name) {
-    return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
-  }
-
-  public function get_parameters() {
-    return $this->parameters;
-  }
-  
-  public function unset_parameter($name) {
-    unset($this->parameters[$name]);
-  }
-
-  /**
-   * The request parameters, sorted and concatenated into a normalized string.
-   * @return string
-   */
-  public function get_signable_parameters() {
-    // Grab all parameters
-    $params = $this->parameters;
-		
-    // Remove oauth_signature if present
-    // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
-    if (isset($params['oauth_signature'])) {
-      unset($params['oauth_signature']);
-    }
-		
-    return OAuthUtil::build_http_query($params);
+
+        $parts = OAuthUtil::urlencode_rfc3986($parts);
+
+        return implode('&', $parts);
+    }
+
+    /**
+     * just uppercases the http method
+     */
+    public function get_normalized_http_method() {
+        return strtoupper($this->http_method);
+    }
+
+    /**
+     * parses the url and rebuilds it to be
+     * scheme://host/path
+     */
+    public function get_normalized_http_url() {
+        $parts = parse_url($this->http_url);
+
+        $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http';
+        $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80');
+        $host = (isset($parts['host'])) ? strtolower($parts['host']) : '';
+        $path = (isset($parts['path'])) ? $parts['path'] : '';
+
+        if (($scheme == 'https' && $port != '443')
+            || ($scheme == 'http' && $port != '80')) {
+            $host = "$host:$port";
         }
+        return "$scheme://$host$path";
+    }
+
+    /**
+     * builds a url usable for a GET request
+     */
+    public function to_url() {
+        $post_data = $this->to_postdata();
+        $out = $this->get_normalized_http_url();
+        if ($post_data) {
+            $out .= '?'.$post_data;
+        }
+        return $out;
+    }
+
+    /**
+     * builds the data one would send in a POST request
+     */
+    public function to_postdata() {
+        return OAuthUtil::build_http_query($this->parameters);
+    }
 
-  /**
-   * Returns the base string of this request
-   *
-   * The base string defined as the method, the url
-   * and the parameters (normalized), each urlencoded
-   * and the concated with &.
-   */
-  public function get_signature_base_string() {
-    $parts = array(
-      $this->get_normalized_http_method(),
-      $this->get_normalized_http_url(),
-      $this->get_signable_parameters()
-    );
-
-    $parts = OAuthUtil::urlencode_rfc3986($parts);
-
-    return implode('&', $parts);
-  }
-
-  /**
-   * just uppercases the http method
-   */
-  public function get_normalized_http_method() {
-    return strtoupper($this->http_method);
-  }
-
-  /**
-   * parses the URL and rebuilds it to be
-   * scheme://host/path
-   */
-  public function get_normalized_http_url() {
-    $parts = parse_url($this->http_url);
-
-    $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http';
-    $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80');
-    $host = (isset($parts['host'])) ? strtolower($parts['host']) : '';
-    $path = (isset($parts['path'])) ? $parts['path'] : '';
-
-    if (($scheme == 'https' && $port != '443')
-        || ($scheme == 'http' && $port != '80')) {
-      $host = "$host:$port";
-    }
-    return "$scheme://$host$path";
-  }
-
-  /**
-   * builds a URL usable for a GET request
-   */
-  public function to_url() {
-    $post_data = $this->to_postdata();
-    $out = $this->get_normalized_http_url();
-    if ($post_data) {
-      $out .= '?'.$post_data;
-    }
-    return $out;
-  }
-
-  /**
-   * builds the data one would send in a POST request
-   */
-  public function to_postdata() {
-    return OAuthUtil::build_http_query($this->parameters);
+    /**
+     * builds the Authorization: header
+     */
+    public function to_header($realm=null) {
+        $first = true;
+        if($realm) {
+            $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"';
+            $first = false;
+        } else
+            $out = 'Authorization: OAuth';
+
+        $total = array();
+        foreach ($this->parameters as $k => $v) {
+            if (substr($k, 0, 5) != "oauth") continue;
+            if (is_array($v)) {
+                throw new OAuthException('Arrays not supported in headers');
+            }
+            $out .= ($first) ? ' ' : ',';
+            $out .= OAuthUtil::urlencode_rfc3986($k) .
+                '="' .
+                OAuthUtil::urlencode_rfc3986($v) .
+                '"';
+            $first = false;
         }
+        return $out;
+    }
+
+    public function __toString() {
+        return $this->to_url();
+    }
+
 
-  /**
-   * builds the Authorization: header
-   */
-  public function to_header($realm=null) {
-    $first = true;
-	if($realm) {
-      $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"';
-      $first = false;
-    } else
-      $out = 'Authorization: OAuth';
-
-    $total = array();
-    foreach ($this->parameters as $k => $v) {
-      if (substr($k, 0, 5) != "oauth") continue;
-      if (is_array($v)) {
-        throw new OAuthException('Arrays not supported in headers');
-      }
-      $out .= ($first) ? ' ' : ',';
-      $out .= OAuthUtil::urlencode_rfc3986($k) .
-              '="' .
-              OAuthUtil::urlencode_rfc3986($v) .
-              '"';
-      $first = false;
-    }
-    return $out;
-  }
-
-  public function __toString() {
-    return $this->to_url();
-  }
-
-
-  public function sign_request($signature_method, $consumer, $token) {
-    $this->set_parameter(
-      "oauth_signature_method",
-      $signature_method->get_name(),
-      false
-    );
-    $signature = $this->build_signature($signature_method, $consumer, $token);
-    $this->set_parameter("oauth_signature", $signature, false);
-  }
-
-  public function build_signature($signature_method, $consumer, $token) {
-    $signature = $signature_method->build_signature($this, $consumer, $token);
-    return $signature;
-  }
-
-  /**
-   * util function: current timestamp
-   */
-  private static function generate_timestamp() {
-    return time();
-  }
-
-  /**
-   * util function: current nonce
-   */
-  private static function generate_nonce() {
-    $mt = microtime();
-    $rand = mt_rand();
-
-    return md5($mt . $rand); // md5s look nicer than numbers
-      }
+    public function sign_request($signature_method, $consumer, $token) {
+        $this->set_parameter(
+            "oauth_signature_method",
+            $signature_method->get_name(),
+            false
+        );
+        $signature = $this->build_signature($signature_method, $consumer, $token);
+        $this->set_parameter("oauth_signature", $signature, false);
     }
 
+    public function build_signature($signature_method, $consumer, $token) {
+        $signature = $signature_method->build_signature($this, $consumer, $token);
+        return $signature;
+    }
+
+    /**
+     * util function: current timestamp
+     */
+    private static function generate_timestamp() {
+        return time();
+    }
+
+    /**
+     * util function: current nonce
+     */
+    private static function generate_nonce() {
+        $mt = microtime();
+        $rand = mt_rand();
+
+        return md5($mt . $rand); // md5s look nicer than numbers
+    }
+}
+
 class OAuthServer {
-  protected $timestamp_threshold = 300; // in seconds, five minutes
-  protected $version = '1.0';             // hi blaine
-  protected $signature_methods = array();
-
-  protected $data_store;
-
-  function __construct($data_store) {
-    $this->data_store = $data_store;
-  }
-
-  public function add_signature_method($signature_method) {
-    $this->signature_methods[$signature_method->get_name()] = 
-        $signature_method;
-  }
-  
-  // high level functions
-
-  /**
-   * process a request_token request
-   * returns the request token on success
-   */
-  public function fetch_request_token(&$request) {
-    $this->get_version($request);
-
-    $consumer = $this->get_consumer($request);
-
-    // no token required for the initial token request
-    $token = NULL;
-
-    $this->check_signature($request, $consumer, $token);
-
-    // Rev A change
-    $callback = $request->get_parameter('oauth_callback');
-    $new_token = $this->data_store->new_request_token($consumer, $callback);
-
-    return $new_token;
-  }
-
-  /**
-   * process an access_token request
-   * returns the access token on success
-   */
-  public function fetch_access_token(&$request) {
-    $this->get_version($request);
-
-    $consumer = $this->get_consumer($request);
-
-    // requires authorized request token
-    $token = $this->get_token($request, $consumer, "request");
-
-    $this->check_signature($request, $consumer, $token);
-
-    // Rev A change
-    $verifier = $request->get_parameter('oauth_verifier');
-    $new_token = $this->data_store->new_access_token($token, $consumer, $verifier);
-    
-    return $new_token;
-  }
-
-  /**
-   * verify an api call, checks all the parameters
-   */
-  public function verify_request(&$request) {
-    $this->get_version($request);
-    $consumer = $this->get_consumer($request);
-    $token = $this->get_token($request, $consumer, "access");
-    $this->check_signature($request, $consumer, $token);
-    return array($consumer, $token);
-  }
-
-  // Internals from here
-  /**
-   * version 1
-   */
-  private function get_version(&$request) {
-    $version = $request->get_parameter("oauth_version");
-    if (!$version) {
-      // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present. 
-      // Chapter 7.0 ("Accessing Protected Ressources")
-      $version = '1.0';
-    }
-    if ($version !== $this->version) {
-      throw new OAuthException("OAuth version '$version' not supported");
-    }
-    return $version;
-  }
-
-  /**
-   * figure out the signature with some defaults
-   */
-  private function get_signature_method($request) {
-    $signature_method = $request instanceof OAuthRequest 
-        ? $request->get_parameter("oauth_signature_method")
-        : NULL;
-
-    if (!$signature_method) {
-      // According to chapter 7 ("Accessing Protected Ressources") the signature-method
-      // parameter is required, and we can't just fallback to PLAINTEXT
-      throw new OAuthException('No signature method parameter. This parameter is required');
-    }
-
-    if (!in_array($signature_method, 
-                  array_keys($this->signature_methods))) {
-      throw new OAuthException(
-        "Signature method '$signature_method' not supported " .
-        "try one of the following: " .
-        implode(", ", array_keys($this->signature_methods))
-      );      
-    }
-    return $this->signature_methods[$signature_method];
-  }
-
-  /**
-   * try to find the consumer for the provided request's consumer key
-   */
-  private function get_consumer($request) {
-    $consumer_key = $request instanceof OAuthRequest 
-        ? $request->get_parameter("oauth_consumer_key")
-        : NULL;
-
-    if (!$consumer_key) {
-      throw new OAuthException("Invalid consumer key");
-    }
-
-    $consumer = $this->data_store->lookup_consumer($consumer_key);
-    if (!$consumer) {
-      throw new OAuthException("Invalid consumer");
-    }
-
-    return $consumer;
-  }
-
-  /**
-   * try to find the token for the provided request's token key
-   */
-  private function get_token($request, $consumer, $token_type="access") {
-    $token_field = $request instanceof OAuthRequest
-         ? $request->get_parameter('oauth_token')
-         : NULL;
-
-    $token = $this->data_store->lookup_token(
-      $consumer, $token_type, $token_field
-    );
-    if (!$token) {
-      throw new OAuthException("Invalid $token_type token: $token_field");
-    }
-    return $token;
-  }
-
-  /**
-   * all-in-one function to check the signature on a request
-   * should guess the signature method appropriately
-   */
-  private function check_signature($request, $consumer, $token) {
-    // this should probably be in a different method
-    $timestamp = $request instanceof OAuthRequest
-        ? $request->get_parameter('oauth_timestamp')
-        : NULL;
-    $nonce = $request instanceof OAuthRequest
-        ? $request->get_parameter('oauth_nonce')
-        : NULL;
-
-    $this->check_timestamp($timestamp);
-    $this->check_nonce($consumer, $token, $nonce, $timestamp);
-
-    $signature_method = $this->get_signature_method($request);
-
-    $signature = $request->get_parameter('oauth_signature'); 
-    $valid_sig = $signature_method->check_signature(
-      $request, 
-      $consumer, 
-      $token, 
-      $signature
-    );
-
-    if (!$valid_sig) {
-      throw new OAuthException("Invalid signature");
-    }
-  }
-
-  /**
-   * check that the timestamp is new enough
-   */
-  private function check_timestamp($timestamp) {
-    if( ! $timestamp )
-      throw new OAuthException(
-        'Missing timestamp parameter. The parameter is required'
-      );
-    
-    // verify that timestamp is recentish
-    $now = time();
-    if (abs($now - $timestamp) > $this->timestamp_threshold) {
-      throw new OAuthException(
-        "Expired timestamp, yours $timestamp, ours $now"
-      );
-    }
-    }
-
-  /**
-   * check that the nonce is not repeated
-   */
-  private function check_nonce($consumer, $token, $nonce, $timestamp) {
-    if( ! $nonce )
-      throw new OAuthException(
-        'Missing nonce parameter. The parameter is required'
-      );
-
-    // verify that the nonce is uniqueish
-    $found = $this->data_store->lookup_nonce(
-      $consumer,
-      $token,
-      $nonce,
-      $timestamp
-    );
-    if ($found) {
-      throw new OAuthException("Nonce already used: $nonce");
-    }
-  }
+    protected $timestamp_threshold = 300; // in seconds, five minutes
+    protected $version = '1.0';             // hi blaine
+    protected $signature_methods = array();
+
+    protected $data_store;
+
+    function __construct($data_store) {
+        $this->data_store = $data_store;
+    }
+
+    public function add_signature_method($signature_method) {
+        $this->signature_methods[$signature_method->get_name()] =
+            $signature_method;
+    }
+
+    // high level functions
+
+    /**
+     * process a request_token request
+     * returns the request token on success
+     */
+    public function fetch_request_token(&$request) {
+        $this->get_version($request);
+
+        $consumer = $this->get_consumer($request);
+
+        // no token required for the initial token request
+        $token = NULL;
+
+        $this->check_signature($request, $consumer, $token);
+
+        // Rev A change
+        $callback = $request->get_parameter('oauth_callback');
+        $new_token = $this->data_store->new_request_token($consumer, $callback);
+
+        return $new_token;
+    }
+
+    /**
+     * process an access_token request
+     * returns the access token on success
+     */
+    public function fetch_access_token(&$request) {
+        $this->get_version($request);
+
+        $consumer = $this->get_consumer($request);
+
+        // requires authorized request token
+        $token = $this->get_token($request, $consumer, "request");
+
+        $this->check_signature($request, $consumer, $token);
+
+        // Rev A change
+        $verifier = $request->get_parameter('oauth_verifier');
+        $new_token = $this->data_store->new_access_token($token, $consumer, $verifier);
+
+        return $new_token;
+    }
+
+    /**
+     * verify an api call, checks all the parameters
+     */
+    public function verify_request(&$request) {
+        $this->get_version($request);
+        $consumer = $this->get_consumer($request);
+        $token = $this->get_token($request, $consumer, "access");
+        $this->check_signature($request, $consumer, $token);
+        return array($consumer, $token);
+    }
+
+    // Internals from here
+    /**
+     * version 1
+     */
+    private function get_version(&$request) {
+        $version = $request->get_parameter("oauth_version");
+        if (!$version) {
+            // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present.
+            // Chapter 7.0 ("Accessing Protected Ressources")
+            $version = '1.0';
+        }
+        if ($version !== $this->version) {
+            throw new OAuthException("OAuth version '$version' not supported");
+        }
+        return $version;
+    }
+
+    /**
+     * figure out the signature with some defaults
+     */
+    private function get_signature_method($request) {
+        $signature_method = $request instanceof OAuthRequest
+            ? $request->get_parameter("oauth_signature_method")
+            : NULL;
+
+        if (!$signature_method) {
+            // According to chapter 7 ("Accessing Protected Ressources") the signature-method
+            // parameter is required, and we can't just fallback to PLAINTEXT
+            throw new OAuthException('No signature method parameter. This parameter is required');
+        }
+
+        if (!in_array($signature_method,
+            array_keys($this->signature_methods))) {
+            throw new OAuthException(
+                "Signature method '$signature_method' not supported " .
+                "try one of the following: " .
+                implode(", ", array_keys($this->signature_methods))
+            );
+        }
+        return $this->signature_methods[$signature_method];
+    }
+
+    /**
+     * try to find the consumer for the provided request's consumer key
+     */
+    private function get_consumer($request) {
+        $consumer_key = $request instanceof OAuthRequest
+            ? $request->get_parameter("oauth_consumer_key")
+            : NULL;
+
+        if (!$consumer_key) {
+            throw new OAuthException("Invalid consumer key");
+        }
+
+        $consumer = $this->data_store->lookup_consumer($consumer_key);
+        if (!$consumer) {
+            throw new OAuthException("Invalid consumer");
+        }
+
+        return $consumer;
+    }
+
+    /**
+     * try to find the token for the provided request's token key
+     */
+    private function get_token($request, $consumer, $token_type="access") {
+        $token_field = $request instanceof OAuthRequest
+            ? $request->get_parameter('oauth_token')
+            : NULL;
+
+        if (!empty($token_field)) {
+            $token = $this->data_store->lookup_token(
+                $consumer, $token_type, $token_field
+            );
+            if (!$token) {
+                throw new OAuthException("Invalid $token_type token: $token_field");
+            }
+        }
+        else {
+            $token = new OAuthToken('', '');
+        }
+        return $token;
+    }
+
+    /**
+     * all-in-one function to check the signature on a request
+     * should guess the signature method appropriately
+     */
+    private function check_signature($request, $consumer, $token) {
+        // this should probably be in a different method
+        $timestamp = $request instanceof OAuthRequest
+            ? $request->get_parameter('oauth_timestamp')
+            : NULL;
+        $nonce = $request instanceof OAuthRequest
+            ? $request->get_parameter('oauth_nonce')
+            : NULL;
+
+        $this->check_timestamp($timestamp);
+        $this->check_nonce($consumer, $token, $nonce, $timestamp);
+
+        $signature_method = $this->get_signature_method($request);
+
+        $signature = $request->get_parameter('oauth_signature');
+        $valid_sig = $signature_method->check_signature(
+            $request,
+            $consumer,
+            $token,
+            $signature
+        );
+
+        if (!$valid_sig) {
+            throw new OAuthException("Invalid signature");
+        }
+    }
+
+    /**
+     * check that the timestamp is new enough
+     */
+    private function check_timestamp($timestamp) {
+        if( ! $timestamp )
+            throw new OAuthException(
+                'Missing timestamp parameter. The parameter is required'
+            );
+
+        // verify that timestamp is recentish
+        $now = time();
+        if (abs($now - $timestamp) > $this->timestamp_threshold) {
+            throw new OAuthException(
+                "Expired timestamp, yours $timestamp, ours $now"
+            );
+        }
+    }
+
+    /**
+     * check that the nonce is not repeated
+     */
+    private function check_nonce($consumer, $token, $nonce, $timestamp) {
+        if( ! $nonce )
+            throw new OAuthException(
+                'Missing nonce parameter. The parameter is required'
+            );
+
+        // verify that the nonce is uniqueish
+        $found = $this->data_store->lookup_nonce(
+            $consumer,
+            $token,
+            $nonce,
+            $timestamp
+        );
+        if ($found) {
+            throw new OAuthException("Nonce already used: $nonce");
+        }
+    }
 
 }
 
 class OAuthDataStore {
-  function lookup_consumer($consumer_key) {
-    // implement me
-  }
+    function lookup_consumer($consumer_key) {
+        // implement me
+    }
 
-  function lookup_token($consumer, $token_type, $token) {
-    // implement me
-  }
+    function lookup_token($consumer, $token_type, $token) {
+        // implement me
+    }
 
-  function lookup_nonce($consumer, $token, $nonce, $timestamp) {
-    // implement me
-  }
+    function lookup_nonce($consumer, $token, $nonce, $timestamp) {
+        // implement me
+    }
 
-  function new_request_token($consumer, $callback = null) {
-    // return a new token attached to this consumer
-  }
+    function new_request_token($consumer, $callback = null) {
+        // return a new token attached to this consumer
+    }
 
-  function new_access_token($token, $consumer, $verifier = null) {
-    // return a new access token attached to this consumer
-    // for the user associated with this token if the request token
-    // is authorized
-    // should also invalidate the request token
-  }
+    function new_access_token($token, $consumer, $verifier = null) {
+        // return a new access token attached to this consumer
+        // for the user associated with this token if the request token
+        // is authorized
+        // should also invalidate the request token
+    }
 
 }
 
 class OAuthUtil {
-  public static function urlencode_rfc3986($input) {
-	if (is_array($input)) {
-		return array_map(array('OAuthUtil','urlencode_rfc3986'), $input);
-	} else if (is_scalar($input)) {
-    return str_replace(
-      '+',
-      ' ',
-      str_replace('%7E', '~', rawurlencode($input))
-    );
-	} else {
-		return '';
-	}
-}
-    
-
-  // This decode function isn't taking into consideration the above 
-  // modifications to the encoding process. However, this method doesn't 
-  // seem to be used anywhere so leaving it as is.
-  public static function urldecode_rfc3986($string) {
-    return urldecode($string);
-  }
-
-  // Utility function for turning the Authorization: header into
-  // parameters, has to do some unescaping
-  // Can filter out any non-oauth parameters if needed (default behaviour)
-  // May 28th, 2010 - method updated to tjerk.meesters for a speed improvement.
-  //                  see http://code.google.com/p/oauth/issues/detail?id=163
-  public static function split_header($header, $only_allow_oauth_parameters = true) {
-    $params = array();
-    if (preg_match_all('/('.($only_allow_oauth_parameters ? 'oauth_' : '').'[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches)) {
-      foreach ($matches[1] as $i => $h) {
-        $params[$h] = OAuthUtil::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]);
-      }
-      if (isset($params['realm'])) {
-        unset($params['realm']);
-      }
-    }
-    return $params;
-  }
-
-  // helper to try to sort out headers for people who aren't running apache
-  public static function get_headers() {
-    if (function_exists('apache_request_headers')) {
-      // we need this to get the actual Authorization: header
-      // because apache tends to tell us it doesn't exist
-      $headers = apache_request_headers();
-
-      // sanitize the output of apache_request_headers because
-      // we always want the keys to be Cased-Like-This and arh()
-      // returns the headers in the same case as they are in the
-      // request
-      $out = array();
-      foreach ($headers AS $key => $value) {
-        $key = str_replace(
-            " ",
-            "-",
-            ucwords(strtolower(str_replace("-", " ", $key)))
-          );
-        $out[$key] = $value;
-      }
-    } else {
-      // otherwise we don't have apache and are just going to have to hope
-      // that $_SERVER actually contains what we need
-      $out = array();
-      if( isset($_SERVER['CONTENT_TYPE']) )
-        $out['Content-Type'] = $_SERVER['CONTENT_TYPE'];
-      if( isset($_ENV['CONTENT_TYPE']) )
-        $out['Content-Type'] = $_ENV['CONTENT_TYPE'];
-
-      foreach ($_SERVER as $key => $value) {
-        if (substr($key, 0, 5) == "HTTP_") {
-          // this is chaos, basically it is just there to capitalize the first
-          // letter of every word that is not an initial HTTP and strip HTTP
-          // code from przemek
-          $key = str_replace(
-            " ",
-            "-",
-            ucwords(strtolower(str_replace("_", " ", substr($key, 5))))
-          );
-          $out[$key] = $value;
+    public static function urlencode_rfc3986($input) {
+        if (is_array($input)) {
+            return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input);
+        } else if (is_scalar($input)) {
+            return str_replace(
+                '+',
+                ' ',
+                str_replace('%7E', '~', rawurlencode($input))
+            );
+        } else {
+            return '';
         }
-      }
     }
-    return $out;
-  }
-
-  // This function takes a input like a=b&a=c&d=e and returns the parsed
-  // parameters like this
-  // array('a' => array('b','c'), 'd' => 'e')
-  public static function parse_parameters( $input ) {
-    if (!isset($input) || !$input) return array();
 
-    $pairs = explode('&', $input);
 
-    $parsed_parameters = array();
-    foreach ($pairs as $pair) {
-      $split = explode('=', $pair, 2);
-      $parameter = OAuthUtil::urldecode_rfc3986($split[0]);
-      $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : '';
-
-      if (isset($parsed_parameters[$parameter])) {
-        // We have already recieved parameter(s) with this name, so add to the list
-        // of parameters with this name
+    // This decode function isn't taking into consideration the above
+    // modifications to the encoding process. However, this method doesn't
+    // seem to be used anywhere so leaving it as is.
+    public static function urldecode_rfc3986($string) {
+        return urldecode($string);
+    }
 
-        if (is_scalar($parsed_parameters[$parameter])) {
-          // This is the first duplicate, so transform scalar (string) into an array
-          // so we can add the duplicates
-          $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]);
+    // Utility function for turning the Authorization: header into
+    // parameters, has to do some unescaping
+    // Can filter out any non-oauth parameters if needed (default behaviour)
+    // May 28th, 2010 - method updated to tjerk.meesters for a speed improvement.
+    //                  see http://code.google.com/p/oauth/issues/detail?id=163
+    public static function split_header($header, $only_allow_oauth_parameters = true) {
+        $params = array();
+        if (preg_match_all('/('.($only_allow_oauth_parameters ? 'oauth_' : '').'[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches)) {
+            foreach ($matches[1] as $i => $h) {
+                $params[$h] = OAuthUtil::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]);
+            }
+            if (isset($params['realm'])) {
+                unset($params['realm']);
+            }
         }
+        return $params;
+    }
 
-        $parsed_parameters[$parameter][] = $value;
-      } else {
-        $parsed_parameters[$parameter] = $value;
-      }
+    // helper to try to sort out headers for people who aren't running apache
+    public static function get_headers() {
+        if (function_exists('apache_request_headers')) {
+            // we need this to get the actual Authorization: header
+            // because apache tends to tell us it doesn't exist
+            $headers = apache_request_headers();
+
+            // sanitize the output of apache_request_headers because
+            // we always want the keys to be Cased-Like-This and arh()
+            // returns the headers in the same case as they are in the
+            // request
+            $out = array();
+            foreach ($headers AS $key => $value) {
+                $key = str_replace(
+                    " ",
+                    "-",
+                    ucwords(strtolower(str_replace("-", " ", $key)))
+                );
+                $out[$key] = $value;
+            }
+        } else {
+            // otherwise we don't have apache and are just going to have to hope
+            // that $_SERVER actually contains what we need
+            $out = array();
+            if( isset($_SERVER['CONTENT_TYPE']) )
+                $out['Content-Type'] = $_SERVER['CONTENT_TYPE'];
+            if( isset($_ENV['CONTENT_TYPE']) )
+                $out['Content-Type'] = $_ENV['CONTENT_TYPE'];
+
+            foreach ($_SERVER as $key => $value) {
+                if (substr($key, 0, 5) == "HTTP_") {
+                    // this is chaos, basically it is just there to capitalize the first
+                    // letter of every word that is not an initial HTTP and strip HTTP
+                    // code from przemek
+                    $key = str_replace(
+                        " ",
+                        "-",
+                        ucwords(strtolower(str_replace("_", " ", substr($key, 5))))
+                    );
+                    $out[$key] = $value;
+                }
+            }
+            // The "Authorization" header may get turned into "Auth".
+            if (isset($out['Auth'])) {
+                $out['Authorization'] = $out['Auth'];
+            }
+        }
+        return $out;
     }
-    return $parsed_parameters;
-  }
 
-  public static function build_http_query($params) {
-    if (!$params) return '';
+    // This function takes a input like a=b&a=c&d=e and returns the parsed
+    // parameters like this
+    // array('a' => array('b','c'), 'd' => 'e')
+    public static function parse_parameters( $input ) {
+        if (!isset($input) || !$input) return array();
+
+        $pairs = explode('&', $input);
+
+        $parsed_parameters = array();
+        foreach ($pairs as $pair) {
+            $split = explode('=', $pair, 2);
+            $parameter = OAuthUtil::urldecode_rfc3986($split[0]);
+            $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : '';
+
+            if (isset($parsed_parameters[$parameter])) {
+                // We have already recieved parameter(s) with this name, so add to the list
+                // of parameters with this name
+
+                if (is_scalar($parsed_parameters[$parameter])) {
+                    // This is the first duplicate, so transform scalar (string) into an array
+                    // so we can add the duplicates
+                    $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]);
+                }
+
+                $parsed_parameters[$parameter][] = $value;
+            } else {
+                $parsed_parameters[$parameter] = $value;
+            }
+        }
+        return $parsed_parameters;
+    }
 
-    // Urlencode both keys and values
-    $keys = OAuthUtil::urlencode_rfc3986(array_keys($params));
-    $values = OAuthUtil::urlencode_rfc3986(array_values($params));
-    $params = array_combine($keys, $values);
+    public static function build_http_query($params) {
+        if (!$params) return '';
 
-    // Parameters are sorted by name, using lexicographical byte value ordering.
-    // Ref: Spec: 9.1.1 (1)
-    uksort($params, 'strcmp');
+        // Urlencode both keys and values
+        $keys = OAuthUtil::urlencode_rfc3986(array_keys($params));
+        $values = OAuthUtil::urlencode_rfc3986(array_values($params));
+        $params = array_combine($keys, $values);
 
-    $pairs = array();
-    foreach ($params as $parameter => $value) {
-      if (is_array($value)) {
-        // If two or more parameters share the same name, they are sorted by their value
+        // Parameters are sorted by name, using lexicographical byte value ordering.
         // Ref: Spec: 9.1.1 (1)
-        // June 12th, 2010 - changed to sort because of issue 164 by hidetaka
-        sort($value, SORT_STRING);
-        foreach ($value as $duplicate_value) {
-          $pairs[] = $parameter . '=' . $duplicate_value;
+        uksort($params, 'strcmp');
+
+        $pairs = array();
+        foreach ($params as $parameter => $value) {
+            if (is_array($value)) {
+                // If two or more parameters share the same name, they are sorted by their value
+                // Ref: Spec: 9.1.1 (1)
+                // June 12th, 2010 - changed to sort because of issue 164 by hidetaka
+                sort($value, SORT_STRING);
+                foreach ($value as $duplicate_value) {
+                    $pairs[] = $parameter . '=' . $duplicate_value;
+                }
+            } else {
+                $pairs[] = $parameter . '=' . $value;
+            }
         }
-      } else {
-        $pairs[] = $parameter . '=' . $value;
-      }
-    }
-    // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
-    // Each name-value pair is separated by an '&' character (ASCII code 38)
-    return implode('&', $pairs);
-  }
+        // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
+        // Each name-value pair is separated by an '&' character (ASCII code 38)
+        return implode('&', $pairs);
+    }
 }
diff --git a/modules/portal/lib/Portal.php b/modules/portal/lib/Portal.php
index 72effb6367a3cbd5d0e68cfe57597e8c4b07d44b..6b9ce045aff5a12b23e6d9ce7ebb6487b4be73b2 100644
--- a/modules/portal/lib/Portal.php
+++ b/modules/portal/lib/Portal.php
@@ -30,15 +30,15 @@ class sspmod_portal_Portal {
 		return FALSE;
 	}
 	
-	function getLoginInfo($t, $thispage) {
-		$info = array('info' => '', 'template' => $t, 'thispage' => $thispage);
+	function getLoginInfo($translator, $thispage) {
+		$info = array('info' => '', 'translator' => $translator, 'thispage' => $thispage);
 		SimpleSAML\Module::callHooks('portalLoginInfo', $info);
 		return $info['info'];
 	}
 	
 	function getMenu($thispage) {
 		$config = SimpleSAML_Configuration::getInstance();
-		$t = new SimpleSAML_XHTML_Template($config, 'sanitycheck:check.tpl.php');
+		$t = new SimpleSAML\Locale\Translate($config);
 		$tabset = $this->getTabset($thispage);
 		$logininfo = $this->getLoginInfo($t, $thispage);
 		$text = '';
diff --git a/modules/saml/dictionaries/proxy.translation.json b/modules/saml/dictionaries/proxy.translation.json
index c78ea653fbf62f96fc955879dbc0928e9a4f2ed9..e06c0e610ebd146b857e7db39ea64066477b15bd 100644
--- a/modules/saml/dictionaries/proxy.translation.json
+++ b/modules/saml/dictionaries/proxy.translation.json
@@ -1,8 +1,10 @@
 {
   "invalid_idp": {
+    "zh-tw": "\u7121\u6548\u7684\u9a57\u8b49\u63d0\u4f9b\u8005",
     "es": "Proveedor de Identidad inválido"
   },
   "invalid_idp_description": {
+    "zh-tw": "\u60a8\u5df2\u7d93\u64c1\u6709\u4e00\u500b\u9a57\u8b49\u63d0\u4f9b\u8005 (<em>%IDP%</em>) \u7684\u6709\u6548\u7684\u9023\u7dda\uff0c\u4f46\u8a72\u9023\u7dda\u7121\u6cd5\u88ab <em>%SP%</em> \u6240\u63a5\u53d7\u3002\u60a8\u662f\u5426\u60f3\u8981\u767b\u51fa\u65e2\u6709\u7684\u9023\u7dda\u4e26\u91cd\u65b0\u7531\u5176\u4ed6\u9a57\u8b49\u63d0\u4f9b\u8005\u9032\u884c\u767b\u5165\uff1f",
     "es": "Ya existe una sesión válida con un proveedor de identidad (<em>%IDP%</em>) que <em>%SP%</em> no acepta. ¿Desea cerrar su sesión actual e iniciar una nueva con otro proveedor de identidad?"
   }
-}
\ No newline at end of file
+}
diff --git a/modules/saml/dictionaries/wrong_authncontextclassref.translation.json b/modules/saml/dictionaries/wrong_authncontextclassref.translation.json
index 54dffcc51dda2b6efc3883179f2de242339b910f..9cb912f457fabcba491c0cf109556599ff41e3f3 100644
--- a/modules/saml/dictionaries/wrong_authncontextclassref.translation.json
+++ b/modules/saml/dictionaries/wrong_authncontextclassref.translation.json
@@ -20,7 +20,7 @@
 		"eu": "Kautotze testu inguru okerra",
 		"de": "Falscher Authentifizierungskontext",
 		"id": "Konteks otentikasi keliru",
-		"zh-tw": "\u932f\u8aa4\u9a57\u8b49\u78bc",
+		"zh-tw": "\u932f\u8aa4\u7684\u9a57\u8b49\u5167\u5bb9",
 		"af": "Verkeerde verifikasie konteks",
 		"el": "\u0395\u03c3\u03c6\u03b1\u03bb\u03bc\u03ad\u03bd\u03b7 \u03bc\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2"
 	},
@@ -45,7 +45,7 @@
 		"eu": "Zerbitzu honek ez du onartzen zure kautotze testuingurua. Ziurrenik ahulegia da edo ez da bi faktoredun kautotzea. ",
 		"de": "Ihr Authentifizierungskontext wird von diesem Dienst nicht akzeptiert. Wahrscheinlich ist er zu schwach oder nicht Zwei-Faktor.",
 		"id": "Konteks otentikasi Anda tidak tersedia pada layanan ini. Mungkin terlalu lemah atau bukan keamanan-ganda.",
-		"zh-tw": "\u60a8\u7684\u9a57\u8b49\u78bc\u7121\u6cd5\u88ab\u63a5\u53d7\u3002\u53ef\u80fd\u662f\u5f37\u5ea6\u592a\u5f31\u6216\u662f\u672a\u4f7f\u7528\u5169\u6bb5\u5f0f\u9a57\u8b49\u3002",
+		"zh-tw": "\u60a8\u7684\u9a57\u8b49\u5167\u5bb9\u7121\u6cd5\u88ab\u6b64\u670d\u52d9\u63a5\u53d7\u3002\u53ef\u80fd\u662f\u5f37\u5ea6\u592a\u5f31\u6216\u662f\u672a\u4f7f\u7528\u5169\u6bb5\u5f0f\u9a57\u8b49 (2-factor)\u3002",
 		"af": "Jou verifikasie konteks is nie deur die diens aanvaar nie. Waarskynlik te swak of nie twee-faktor nie.",
 		"el": "\u0397 \u03bc\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03bf\u03b4\u03b5\u03ba\u03c4\u03ae \u03c3\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1. \u03a0\u03b9\u03b8\u03b1\u03bd\u03ce\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03bf\u03bb\u03cd \u03b1\u03b4\u03cd\u03bd\u03b1\u03bc\u03b7, \u03c0.\u03c7. \u03b4\u03b5\u03bd \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5 \u03b5\u03c0\u03b1\u03bb\u03ae\u03b8\u03b5\u03c5\u03c3\u03b7 \u03b4\u03cd\u03bf \u03b2\u03b7\u03bc\u03ac\u03c4\u03c9\u03bd."
 	}
diff --git a/modules/saml/lib/Auth/Source/SP.php b/modules/saml/lib/Auth/Source/SP.php
index c866a2de4fe4d953bf3747f2f0e4beb5df7ecb56..504ce9f18e458e2ea8df6f722a06fdbddec305be 100644
--- a/modules/saml/lib/Auth/Source/SP.php
+++ b/modules/saml/lib/Auth/Source/SP.php
@@ -147,7 +147,7 @@ class sspmod_saml_Auth_Source_SP extends SimpleSAML_Auth_Source {
 
 		$state['saml:idp'] = $idpEntityId;
 
-		$ar = new SimpleSAML_XML_Shib13_AuthnRequest();
+		$ar = new \SimpleSAML\XML\Shib13\AuthnRequest();
 		$ar->setIssuer($this->entityId);
 
 		$id = SimpleSAML_Auth_State::saveState($state, 'saml:sp:sso');
diff --git a/modules/saml/lib/IdP/SAML1.php b/modules/saml/lib/IdP/SAML1.php
index d6f5e3bd094b2a10c0f5bbb007db074314cdaf70..f0e40dc9dd44b7a517f89ff2ba05480ed9218c5e 100644
--- a/modules/saml/lib/IdP/SAML1.php
+++ b/modules/saml/lib/IdP/SAML1.php
@@ -1,4 +1,5 @@
 <?php
+use SimpleSAML\Bindings\Shib13\HTTPPost;
 
 /**
  * IdP implementation for SAML 1.1 protocol.
@@ -47,10 +48,10 @@ class sspmod_saml_IdP_SAML1 {
 		SimpleSAML_Stats::log('saml:idp:Response', $statsData);
 
 		// Generate and send response.
-		$ar = new SimpleSAML_XML_Shib13_AuthnResponse();
+		$ar = new \SimpleSAML\XML\Shib13\AuthnResponse();
 		$authnResponseXML = $ar->generate($idpMetadata, $spMetadata, $shire, $attributes);
 
-		$httppost = new SimpleSAML_Bindings_Shib13_HTTPPost($config, $metadata);
+		$httppost = new HTTPPost($config, $metadata);
 		$httppost->sendResponse($authnResponseXML, $idpMetadata, $spMetadata, $target, $shire);
 	}
 
diff --git a/modules/saml/lib/SP/LogoutStore.php b/modules/saml/lib/SP/LogoutStore.php
index 94ffe467c58aec2aa77da3b1f08b69d867982381..f04447b31338ce2a3a70607c390bea1314278798 100644
--- a/modules/saml/lib/SP/LogoutStore.php
+++ b/modules/saml/lib/SP/LogoutStore.php
@@ -14,12 +14,24 @@ class sspmod_saml_SP_LogoutStore {
 	 */
 	private static function createLogoutTable(\SimpleSAML\Store\SQL $store) {
 
-		if ($store->getTableVersion('saml_LogoutStore') === 1) {
+		$tableVer = $store->getTableVersion('saml_LogoutStore');
+		if ($tableVer === 2) {
+			return;
+		} elseif ($tableVer === 1) {
+			/* TableVersion 2 increased the column size to 255 which is the maximum length of a FQDN. */
+			$query = 'ALTER TABLE ' . $store->prefix . '_saml_LogoutStore MODIFY _authSource VARCHAR(255) NOT NULL';
+			try {
+				$ret = $store->pdo->exec($query);
+			} catch (Exception $e) {
+				SimpleSAML\Logger::warning($store->pdo->errorInfo());
+				return;
+			}
+			$store->setTableVersion('saml_LogoutStore', 2);
 			return;
 		}
 
 		$query = 'CREATE TABLE ' . $store->prefix . '_saml_LogoutStore (
-			_authSource VARCHAR(30) NOT NULL,
+			_authSource VARCHAR(255) NOT NULL,
 			_nameId VARCHAR(40) NOT NULL,
 			_sessionIndex VARCHAR(50) NOT NULL,
 			_expire TIMESTAMP NOT NULL,
@@ -34,7 +46,7 @@ class sspmod_saml_SP_LogoutStore {
 		$query = 'CREATE INDEX ' . $store->prefix . '_saml_LogoutStore_nameId ON '  . $store->prefix . '_saml_LogoutStore (_authSource, _nameId)';
 		$store->pdo->exec($query);
 
-		$store->setTableVersion('saml_LogoutStore', 1);
+		$store->setTableVersion('saml_LogoutStore', 2);
 	}
 
 
@@ -210,11 +222,11 @@ class sspmod_saml_SP_LogoutStore {
 	 * Log out of the given sessions.
 	 *
 	 * @param string $authId  The authsource ID.
-	 * @param array $nameId  The NameID of the user.
+	 * @param \SAML2\XML\saml\NameID $nameId The NameID of the user.
 	 * @param array $sessionIndexes  The SessionIndexes we should log out of. Logs out of all if this is empty.
 	 * @returns int|FALSE  Number of sessions logged out, or FALSE if not supported.
 	 */
-	public static function logoutSessions($authId, array $nameId, array $sessionIndexes) {
+	public static function logoutSessions($authId, $nameId, array $sessionIndexes) {
 		assert('is_string($authId)');
 
 		$store = \SimpleSAML\Store::getInstance();
@@ -223,8 +235,11 @@ class sspmod_saml_SP_LogoutStore {
 			return FALSE;
 		}
 
-		/* Normalize NameID. */
-		ksort($nameId);
+		// serialize and anonymize the NameID
+		// TODO: remove this conditional statement
+		if (is_array($nameId)) {
+			$nameId = \SAML2\XML\saml\NameID::fromArray($nameId);
+		}
 		$strNameId = serialize($nameId);
 		$strNameId = sha1($strNameId);
 
@@ -252,7 +267,7 @@ class sspmod_saml_SP_LogoutStore {
 			$sessionIndexes = array_keys($sessions);
 		}
 
-		$sessionHandler = SimpleSAML_SessionHandler::getSessionHandler();
+		$sessionHandler = \SimpleSAML\SessionHandler::getSessionHandler();
 
 		$numLoggedOut = 0;
 		foreach ($sessionIndexes as $sessionIndex) {
diff --git a/modules/saml/www/sp/saml1-acs.php b/modules/saml/www/sp/saml1-acs.php
index fb5b40d5adda3f67ef03bf46520a55eeece743e9..de340e8d96c9b14a2f9d9899d9569c18f91be832 100644
--- a/modules/saml/www/sp/saml1-acs.php
+++ b/modules/saml/www/sp/saml1-acs.php
@@ -1,5 +1,7 @@
 <?php
 
+use SimpleSAML\Bindings\Shib13\Artifact;
+
 if (!array_key_exists('SAMLResponse', $_REQUEST) && !array_key_exists('SAMLart', $_REQUEST)) {
 	throw new SimpleSAML_Error_BadRequest('Missing SAMLResponse or SAMLart parameter.');
 }
@@ -53,7 +55,7 @@ if (array_key_exists('SAMLart', $_REQUEST)) {
 	}
 	$idpMetadata = $source->getIdPMetadata($state['saml:idp']);
 
-	$responseXML = SimpleSAML_Bindings_Shib13_Artifact::receive($spMetadata, $idpMetadata);
+	$responseXML = Artifact::receive($spMetadata, $idpMetadata);
 	$isValidated = TRUE; /* Artifact binding validated with ssl certificate. */
 } elseif (array_key_exists('SAMLResponse', $_REQUEST)) {
 	$responseXML = $_REQUEST['SAMLResponse'];
@@ -63,7 +65,7 @@ if (array_key_exists('SAMLart', $_REQUEST)) {
 	assert('FALSE');
 }
 
-$response = new SimpleSAML_XML_Shib13_AuthnResponse();
+$response = new \SimpleSAML\XML\Shib13\AuthnResponse();
 $response->setXML($responseXML);
 
 $response->setMessageValidated($isValidated);
diff --git a/modules/saml/www/sp/saml2-acs.php b/modules/saml/www/sp/saml2-acs.php
index e98f60b4232eb203b6e3e9199c94c6db9bbec2c4..194e00954d51fbd7d02ec8530c3ed54a0797705a 100644
--- a/modules/saml/www/sp/saml2-acs.php
+++ b/modules/saml/www/sp/saml2-acs.php
@@ -72,11 +72,20 @@ if ($prevAuth !== null && $prevAuth['id'] === $response->getId() && $prevAuth['i
 
 $idpMetadata = array();
 
+$state = null;
 $stateId = $response->getInResponseTo();
 if (!empty($stateId)) {
-    // this is a response to a request we sent earlier
-    $state = SimpleSAML_Auth_State::loadState($stateId, 'saml:sp:sso');
+    // this should be a response to a request we sent earlier
+    try {
+        $state = SimpleSAML_Auth_State::loadState($stateId, 'saml:sp:sso');
+    } catch (Exception $e) {
+        // something went wrong,
+        SimpleSAML_Logger::warning('Could not load state specified by InResponseTo: '.$e->getMessage().
+            ' Processing response as unsolicited.');
+    }
+}
 
+if ($state) {
     // check that the authentication source is correct
     assert('array_key_exists("saml:sp:AuthId", $state)');
     if ($state['saml:sp:AuthId'] !== $sourceId) {
diff --git a/modules/statistics/bin/loganalyzer.php b/modules/statistics/bin/loganalyzer.php
index 37578856b2dc07b38383b06893d9e4f12b49f264..bf9a1641893d805a55aaacc93cc0e892ccecddd4 100755
--- a/modules/statistics/bin/loganalyzer.php
+++ b/modules/statistics/bin/loganalyzer.php
@@ -1,93 +1,89 @@
 #!/usr/bin/env php
 <?php
 
-
 // This is the base directory of the SimpleSAMLphp installation
 $baseDir = dirname(dirname(dirname(dirname(__FILE__))));
 
 // Add library autoloader.
 require_once($baseDir . '/lib/_autoload.php');
 
-/* Initialize the configuration. */
+// Initialize the configuration.
 $configdir = SimpleSAML\Utils\Config::getConfigDir();
 SimpleSAML_Configuration::setConfigDir($configdir);
-
 SimpleSAML\Utils\Time::initTimezone();
 
 $progName = array_shift($argv);
-$debug = FALSE;
-$dryrun = FALSE;
+$debug = false;
+$dryrun = false;
 
 foreach($argv as $a) {
-	if(strlen($a) === 0) continue;
-
-	if(strpos($a, '=') !== FALSE) {
-		$p = strpos($a, '=');
-		$v = substr($a, $p + 1);
-		$a = substr($a, 0, $p);
-	} else {
-		$v = NULL;
-	}
-
-	/* Map short options to long options. */
-	$shortOptMap = array(
-		'-d' => '--debug',
-	);
-	if(array_key_exists($a, $shortOptMap))  $a = $shortOptMap[$a];
-
-	switch($a) {
-		case '--help':
-			printHelp();
-			exit(0);
-		case '--debug':
-			$debug = TRUE;
-			break;
-		case '--dry-run':
-			$dryrun = TRUE;
-			break;
-		default:
-			echo('Unknown option: ' . $a . "\n");
-			echo('Please run `' . $progName . ' --help` for usage information.' . "\n");
-			exit(1);
-		}
+    if (strlen($a) === 0) {
+        continue;
+    }
+    if (strpos($a, '=') !== false) {
+        $p = strpos($a, '=');
+        $v = substr($a, $p + 1);
+        $a = substr($a, 0, $p);
+    } else {
+        $v = null;
+    }
+
+    // Map short options to long options.
+    $shortOptMap = array('-d' => '--debug');
+    if (array_key_exists($a, $shortOptMap)) {
+        $a = $shortOptMap[$a];
+    }
+    switch ($a) {
+        case '--help':
+            printHelp();
+            exit(0);
+        case '--debug':
+            $debug = true;
+            break;
+        case '--dry-run':
+            $dryrun = true;
+            break;
+        default:
+            echo('Unknown option: ' . $a . "\n");
+            echo('Please run `' . $progName . ' --help` for usage information.' . "\n");
+            exit(1);
+    }
 }
 
-$aggregator = new sspmod_statistics_Aggregator(TRUE);
+$aggregator = new sspmod_statistics_Aggregator(true);
 $aggregator->dumpConfig();
 $aggregator->debugInfo();
 $results = $aggregator->aggregate($debug);
 $aggregator->debugInfo();
 
 if (!$dryrun) {
-	$aggregator->store($results);
+    $aggregator->store($results);
 }
 
-
 foreach ($results AS $slot => $val) {
-	 foreach ($val AS $sp => $no) {
-	 	echo $sp . " " . count($no) . " - ";
-	 }
-	 echo "\n";
+    foreach ($val AS $sp => $no) {
+        echo $sp . " " . count($no) . " - ";
+    }
+    echo "\n";
 }
 
 
-
-
 /**
  * This function prints the help output.
  */
+
 function printHelp() {
-	global $progName;
+    global $progName;
 
-	/*   '======================================================================' */
-	echo('Usage: ' . $progName . ' [options]
+    echo <<<END
+Usage: $progName [options]
 
 This program parses and aggregates SimpleSAMLphp log files.
 
 Options:
-	-d, --debug			Used when configuring the log file syntax. See doc.
-	--dry-run			Aggregate but do not store the results.
+ -d, --debug			Used when configuring the log file syntax. See doc.
+ --dry-run			Aggregate but do not store the results.
+END;
 
-');
 }
 
diff --git a/modules/statistics/bin/logcleaner.php b/modules/statistics/bin/logcleaner.php
index 3ed5e21119e08844831dca1ebbf1f077907f0132..b845a139602c756e9b28cde423fcba30057639f5 100755
--- a/modules/statistics/bin/logcleaner.php
+++ b/modules/statistics/bin/logcleaner.php
@@ -1,63 +1,61 @@
 #!/usr/bin/env php
 <?php
 
-
 // This is the base directory of the SimpleSAMLphp installation
 $baseDir = dirname(dirname(dirname(dirname(__FILE__))));
 
 // Add library autoloader.
 require_once($baseDir . '/lib/_autoload.php');
 
-/* Initialize the configuration. */
+// Initialize the configuration.
 $configdir = SimpleSAML\Utils\Config::getConfigDir();
 SimpleSAML_Configuration::setConfigDir($configdir);
 
-
-
 $progName = array_shift($argv);
-$debug = FALSE;
-$dryrun = FALSE;
+$debug = false;
+$dryrun = false;
 $output = '/tmp/simplesamlphp-new.log';
-$infile = NULL;
-
-foreach($argv as $a) {
-	if(strlen($a) === 0) continue;
-
-	if(strpos($a, '=') !== FALSE) {
-		$p = strpos($a, '=');
-		$v = substr($a, $p + 1);
-		$a = substr($a, 0, $p);
-	} else {
-		$v = NULL;
-	}
-
-	/* Map short options to long options. */
-	$shortOptMap = array(
-		'-d' => '--debug',
-	);
-	if(array_key_exists($a, $shortOptMap))  $a = $shortOptMap[$a];
-
-	switch($a) {
-		case '--help':
-			printHelp();
-			exit(0);
-		case '--debug':
-			$debug = TRUE;
-			break;
-		case '--dry-run':
-			$dryrun = TRUE;
-			break;
-		case '--infile':
-			$infile = $v;
-			break;
-		case '--outfile':
-			$output = $v;
-			break;
-		default:
-			echo('Unknown option: ' . $a . "\n");
-			echo('Please run `' . $progName . ' --help` for usage information.' . "\n");
-			exit(1);
-		}
+$infile = null;
+
+foreach ($argv as $a) {
+    if (strlen($a) === 0) {
+        continue;
+    }
+    if (strpos($a, '=') !== false) {
+        $p = strpos($a, '=');
+        $v = substr($a, $p + 1);
+        $a = substr($a, 0, $p);
+    } else {
+        $v = null;
+    }
+
+    // Map short options to long options.
+    $shortOptMap = array('-d' => '--debug');
+    if (array_key_exists($a, $shortOptMap)) {
+        $a = $shortOptMap[$a];
+    }
+
+    switch ($a) {
+        case '--help':
+            printHelp();
+            exit(0);
+        case '--debug':
+            $debug = true;
+            break;
+        case '--dry-run':
+            $dryrun = true;
+            break;
+        case '--infile':
+            $infile = $v;
+            break;
+        case '--outfile':
+            $output = $v;
+            break;
+        default:
+            echo('Unknown option: ' . $a . "\n");
+            echo('Please run `' . $progName . ' --help` for usage information.' . "\n");
+            exit(1);
+    }
 }
 
 $cleaner = new sspmod_statistics_LogCleaner($infile);
@@ -67,17 +65,18 @@ $todelete = $cleaner->clean($debug);
 echo "Cleaning these trackIDs: " . join(', ', $todelete) . "\n";
 
 if (!$dryrun) {
-	$cleaner->store($todelete, $output);
+    $cleaner->store($todelete, $output);
 }
 
 /**
  * This function prints the help output.
  */
+
 function printHelp() {
-	global $progName;
+    global $progName;
 
-	/*   '======================================================================' */
-	echo('Usage: ' . $progName . ' [options]
+    echo <<<END
+Usage: $progName [options]
 
 This program cleans logs. This script is experimental. Do not run it unless you have talked to Andreas about it. 
 The script deletes log lines related to sessions that produce more than 200 lines.
@@ -88,6 +87,6 @@ Options:
 	--infile			File input.
 	--outfile			File to output the results.
 
-');
+END;
 }
 
diff --git a/modules/statistics/config-templates/module_statistics.php b/modules/statistics/config-templates/module_statistics.php
index 99d1de010b728227334c61d7e45fd8bff88fef6b..89c97c4250ceb7baab34ba5711a4fd336b4a9a78 100644
--- a/modules/statistics/config-templates/module_statistics.php
+++ b/modules/statistics/config-templates/module_statistics.php
@@ -5,234 +5,184 @@
 
 $config = array (
 
-	// Authentication & authorization for statistics
+    // Authentication & authorization for statistics
 
-	// Whether the statistics require authentication before use.
-	'protected' => FALSE,
+    // Whether the statistics require authentication before use.
+    'protected' => false,
 
-	/* The authentication source that should be used. */
-	'auth' => 'admin',
+    // The authentication source that should be used.
+    'auth' => 'admin',
 
-	/* Alternative 1: List of allowed users. */
-	//'useridattr' => 'eduPersonPrincipalName',
-	//'allowedUsers' => array('andreas@uninett.no', 'ola.normann@sp.example.org'),
+    // Alternative 1: List of allowed users.
+    //'useridattr' => 'eduPersonPrincipalName',
+    //'allowedUsers' => array('andreas@uninett.no', 'ola.normann@sp.example.org'),
 
-	/* Alternative 2: External ACL list. */
-	//'acl' => 'adminlist',
+    // Alternative 2: External ACL list.
+    //'acl' => 'adminlist',
 
+    'default' => 'sso',
 
-
-	'default' => 'sso',
-
-	'statdir' => '/tmp/stats/',
-	'inputfile' => '/var/log/simplesamlphp.stat',
-	'offset' => 60*60*2 + 60*60*24*3, // Two hours offset to match epoch and norwegian winter time.
+    'statdir' => '/tmp/stats/',
+    'inputfile' => '/var/log/simplesamlphp.stat',
+    'offset' => 60*60*2 + 60*60*24*3, // Two hours offset to match epoch and norwegian winter time.
 	
-	'datestart' => 1,
-	'datelength' => 15,
-	'offsetspan' => 21,
+    'datestart' => 1,
+    'datelength' => 15,
+    'offsetspan' => 21,
 	
-	// Dimensions on graph from Google Charts in pixels...
-	'dimension.x' => 800,
-	'dimension.y' => 350,
+    // Dimensions on graph from Google Charts in pixels...
+    'dimension.x' => 800,
+    'dimension.y' => 350,
 	
-	/*
-	 * Do you want to generate statistics using the cron module? If so, specify which cron tag to use.
-	 * Examples: daily, weekly
-	 * To not run statistics in cron, set value to 
-	 *     'cron_tag' => NULL,
-	 */
-	'cron_tag' => 'daily',
+    /*
+     * Do you want to generate statistics using the cron module? If so, specify which cron tag to use.
+     * Examples: daily, weekly
+     * To not run statistics in cron, set value to 
+     *     'cron_tag' => null,
+     */
+    'cron_tag' => 'daily',
 
-	/*
-	 * Set max running time for this script. This is also controlle by max_execution_time in php.ini
-	 * and is defalut set to 30 sec. Your web server can have other timeout configurations that may
-	 * also interrupt PHP execution. Apache has a Timeout directive and IIS has a
-	 * CGI timeout function. Both default to 300 seconds.
-	 */
-	'time_limit' => 300,
-	
-	'timeres' => array(
-		'day' => array(
-			'name' => 'Day',
-			'slot'		=> 60*15,			// Slots of 15 minutes
-			'fileslot'	=> 60*60*24,		// One day (24 hours) file slots
-			'axislabelint' => 6*4,			// Number of slots per label. 4 per hour *6 = 6 hours 
-			'dateformat-period'	=> 'j. M', 			//  4. Mars
-			'dateformat-intra'	=> 'j. M H:i', 		//  4. Mars 12:30	
-		),
-		'week' => array(
-			'name' => 'Week',
-			'slot'		=> 60*60,			// Slots of one hour
-			'fileslot'	=> 60*60*24*7,		// 7 days of data in each file
-			'axislabelint' => 24,			// Number of slots per label. 24 is one each day
-			'dateformat-period'	=> 'j. M', 			//  4. Mars
-			'dateformat-intra'	=> 'j. M H:i', 		//  4. Mars 12:30
-		),
-		'month' => array(
-			'name' => 'Month',
-			'slot'		=> 60*60*24,		// Slots of one day
-			'fileslot'	=> 60*60*24*30,		// 30 days of data in each file
-			'axislabelint' => 7,			// Number of slots per label. 7 days => 1 week
-			'dateformat-period'	=> 'j. M Y H:i', 	//  4. Mars 12:30
-			'dateformat-intra'	=> 'j. M', 			//  4. Mars
-		),
-		'monthaligned' => array(
-			'name' => 'AlignedMonth',
-			'slot'		=> 60*60*24,		// Slots of one day
-			'fileslot'	=> NULL,		// 30 days of data in each file
-			'customDateHandler' => 'month',
-			'axislabelint' => 7,			// Number of slots per label. 7 days => 1 week
-			'dateformat-period'	=> 'j. M Y H:i', 	//  4. Mars 12:30
-			'dateformat-intra'	=> 'j. M', 			//  4. Mars
-		),
-		
-		'days180' => array(
-			'name' => '180 days',
-			'slot'		=> 60*60*24,		// Slots of 1 day (24 hours)
-			'fileslot'	=> 60*60*24*180,	// 80 days of data in each file
-			'axislabelint' => 30,			// Number of slots per label. 7 days => 1 week
-			'dateformat-period'	=> 'j. M', 		//  4. Mars
-			'dateformat-intra'	=> 'j. M', 		//  4. Mars
-		),
-	),
+    /*
+     * Set max running time for this script. This is also controlle by max_execution_time in php.ini
+     * and is defalut set to 30 sec. Your web server can have other timeout configurations that may
+     * also interrupt PHP execution. Apache has a Timeout directive and IIS has a
+     * CGI timeout function. Both default to 300 seconds.
+     */
+    'time_limit' => 300,
 	
-	'time_limit' => 300,
-	
-	'timeres' => array(
-		'day' => array(
-			'name' => 'Day',
-			'slot'		=> 60*15,			// Slots of 15 minutes
-			'fileslot'	=> 60*60*24,		// One day (24 hours) file slots
-			'axislabelint' => 6*4,			// Number of slots per label. 4 per hour *6 = 6 hours 
-			'dateformat-period'	=> 'j. M', 			//  4. Mars
-			'dateformat-intra'	=> 'j. M H:i', 		//  4. Mars 12:30	
-		),
-		'week' => array(
-			'name' => 'Week',
-			'slot'		=> 60*60,			// Slots of one hour
-			'fileslot'	=> 60*60*24*7,		// 7 days of data in each file
-			'axislabelint' => 24,			// Number of slots per label. 24 is one each day
-			'dateformat-period'	=> 'j. M', 			//  4. Mars
-			'dateformat-intra'	=> 'j. M H:i', 		//  4. Mars 12:30
-		),
-		'month' => array(
-			'name' => 'Month',
-			'slot'		=> 60*60*24,		// Slots of one day
-			'fileslot'	=> 60*60*24*30,		// 30 days of data in each file
-			'axislabelint' => 7,			// Number of slots per label. 7 days => 1 week
-			'dateformat-period'	=> 'j. M Y H:i', 	//  4. Mars 12:30
-			'dateformat-intra'	=> 'j. M', 			//  4. Mars
-		),
-		'monthaligned' => array(
-			'name' => 'AlignedMonth',
-			'slot'		=> 60*60*24,		// Slots of one day
-			'fileslot'	=> NULL,		// 30 days of data in each file
-			'customDateHandler' => 'month',
-			'axislabelint' => 7,			// Number of slots per label. 7 days => 1 week
-			'dateformat-period'	=> 'j. M Y H:i', 	//  4. Mars 12:30
-			'dateformat-intra'	=> 'j. M', 			//  4. Mars
-		),
-		
-		'days180' => array(
-			'name' => '180 days',
-			'slot'		=> 60*60*24,		// Slots of 1 day (24 hours)
-			'fileslot'	=> 60*60*24*180,	// 80 days of data in each file
-			'axislabelint' => 30,			// Number of slots per label. 7 days => 1 week
-			'dateformat-period'	=> 'j. M', 		//  4. Mars
-			'dateformat-intra'	=> 'j. M', 		//  4. Mars
-		),
-	),
-	'statrules' => array(
-		'sloratio' => array(
-			'name' 		=> 'SLO to SSO ratio',
-			'descr'		=> 'Comparison of the number of Single Log-Out compared to Single Sign-On. Graph shows how many logouts where initiated for each Single Sign-On.',
-			'type' => 'calculated',
-			'presenter' => 'statistics:Ratio',
-			'ref' => array('slo', 'sso'),
-			'fieldPresentation' => array(
-				'class' => 'statistics:Entity',
-				'config' => 'saml20-sp-remote',
-			),
-		),
-		'ssomulti' => array(
-			'name' 		=> 'Requests per session',
-			'descr'		=> 'Number of SSO request pairs exchanged between IdP and SP within the same IdP session. A high number indicates that the session at the SP is timing out faster than at the IdP.',
-			'type' => 'calculated',
-			'presenter' => 'statistics:Ratio',
-			'ref' => array('sso', 'ssofirst'),
-			'fieldPresentation' => array(
-				'class' => 'statistics:Entity',
-				'config' => 'saml20-sp-remote',
-			),
-		),
-		'sso' => array(
-			'name' 		=> 'SSO to service',
-			'descr'		=> 'The number of logins at a Service Provider.',
-			'action' 	=> 'saml20-idp-SSO',
-			'col'		=> 6,				// Service Provider EntityID
-			'fieldPresentation' => array(
-				'class' => 'statistics:Entity',
-				'config' => 'saml20-sp-remote',
-			),
-		),
-		'ssofirst' => array(
-			'name' 		=> 'SSO-first to service',
-			'descr'		=> 'The number of logins at a Service Provider.',
-			'action' 	=> 'saml20-idp-SSO-first',
-			'col'		=> 6,				// Service Provider EntityID
-			'fieldPresentation' => array(
-				'class' => 'statistics:Entity',
-				'config' => 'saml20-sp-remote',
-			),
-		),
-		'slo' => array(
-			'name' 		=> 'SLO initiated from service',
-			'descr'		=> 'The number of initated Sinlge Logout from each of the service providers.',
-			'action' 	=> 'saml20-idp-SLO',
-			'col'		=> 7,				// Service Provider EntityID that initiated the logout.
-			'fieldPresentation' => array(
-				'class' => 'statistics:Entity',
-				'config' => 'saml20-sp-remote',
-			),
-		),
-		'consent' => array(
-			'name' 		=> 'Consent',
-			'descr'		=> 'Consent statistics. Everytime a user logs in to a service an entry is logged for one of three states: consent was found, consent was not found or consent storage was not available.',
-			'action' 	=> 'consent',
-			'col'		=> 6,
-			'fieldPresentation' => array(
-				'class' => 'statistics:Entity',
-				'config' => 'saml20-sp-remote',
-			),
-		),
-		'consentresponse' => array(
-			'name' 		=> 'Consent response',
-			'descr'		=> 'Consent response statistics. Everytime a user accepts consent, it is logged whether the user selected to remember the consent to next time.',
-			'action' 	=> 'consentResponse',
-			'col'		=> 6,
-			'fieldPresentation' => array(
-				'class' => 'statistics:Entity',
-				'config' => 'saml20-sp-remote',
-			),
-		),
-		'slopages' => array(
-			'name' 		=> 'SLO iframe pages',
-			'descr'		=> 'The varioust IFrame SLO pages a user visits',
-			'action' 	=> 'slo-iframe',
-			'col'		=> 6,				// Page the user visits.
-		),
-		'slofail' => array(
-			'name' 		=> 'Failed iframe IdP-init SLOs',
-			'descr'		=> 'The number of logout failures from various SPs',
-			'action' 	=> 'slo-iframe-fail',
-			'col'		=> 6,				// Service Provider EntityID that wasn't logged out.
-			'fieldPresentation' => array(
-				'class' => 'statistics:Entity',
-				'config' => 'saml20-sp-remote',
-			),
-		),
-	),
+    'timeres' => array(
+        'day' => array(
+            'name' => 'Day',
+            'slot'              => 60*15,            // Slots of 15 minutes
+            'fileslot'	        => 60*60*24,         // One day (24 hours) file slots
+            'axislabelint'      => 6*4,              // Number of slots per label. 4 per hour *6 = 6 hours 
+            'dateformat-period'	=> 'j. M',           //  4. Mars
+            'dateformat-intra'	=> 'j. M H:i',       //  4. Mars 12:30	
+        ),
+        'week' => array(
+            'name' => 'Week',
+            'slot'		=> 60*60,            // Slots of one hour
+            'fileslot'	        => 60*60*24*7,       // 7 days of data in each file
+            'axislabelint'      => 24,               // Number of slots per label. 24 is one each day
+            'dateformat-period'	=> 'j. M',           //  4. Mars
+            'dateformat-intra'	=> 'j. M H:i',       //  4. Mars 12:30
+        ),
+        'month' => array(
+            'name' => 'Month',
+            'slot'              => 60*60*24,         // Slots of one day
+            'fileslot'          => 60*60*24*30,      // 30 days of data in each file
+            'axislabelint'      => 7,                // Number of slots per label. 7 days => 1 week
+            'dateformat-period'	=> 'j. M Y H:i',     //  4. Mars 12:30
+            'dateformat-intra'	=> 'j. M',           //  4. Mars
+        ),
+        'monthaligned' => array(
+            'name'              => 'AlignedMonth',
+            'slot'              => 60*60*24,         // Slots of one day
+            'fileslot'          => null,             // 30 days of data in each file
+            'customDateHandler' => 'month',
+            'axislabelint'      => 7,                // Number of slots per label. 7 days => 1 week
+            'dateformat-period'	=> 'j. M Y H:i',     //  4. Mars 12:30
+            'dateformat-intra'	=> 'j. M',           //  4. Mars
+        ),
+        'days180' => array(
+            'name'              => '180 days',
+            'slot'              => 60*60*24,         // Slots of 1 day (24 hours)
+            'fileslot'          => 60*60*24*180,     // 80 days of data in each file
+            'axislabelint'      => 30,               // Number of slots per label. 7 days => 1 week
+            'dateformat-period' => 'j. M',           //  4. Mars
+            'dateformat-intra'  => 'j. M',           //  4. Mars
+        ),
+    ),
 
-);
+    'statrules' => array(
+        'sloratio' => array(
+            'name'         => 'SLO to SSO ratio',
+            'descr'        => 'Comparison of the number of Single Log-Out compared to Single Sign-On. Graph shows how many logouts where initiated for each Single Sign-On.',
+            'type'         => 'calculated',
+            'presenter'    => 'statistics:Ratio',
+            'ref'          => array('slo', 'sso'),
+            'fieldPresentation' => array(
+                'class'    => 'statistics:Entity',
+                'config'   => 'saml20-sp-remote',
+            ),
+        ),
+        'ssomulti' => array(
+            'name'         => 'Requests per session',
+            'descr'        => 'Number of SSO request pairs exchanged between IdP and SP within the same IdP session. A high number indicates that the session at the SP is timing out faster than at the IdP.',
+            'type'         => 'calculated',
+            'presenter'    => 'statistics:Ratio',
+            'ref'          => array('sso', 'ssofirst'),
+            'fieldPresentation' => array(
+                'class'    => 'statistics:Entity',
+                'config'   => 'saml20-sp-remote',
+            ),
+        ),
+        'sso' => array(
+            'name'         => 'SSO to service',
+            'descr'        => 'The number of logins at a Service Provider.',
+            'action'       => 'saml20-idp-SSO',
+            'col'          => 6,                     // Service Provider EntityID
+            'fieldPresentation' => array(
+                'class'    => 'statistics:Entity',
+                'config'   => 'saml20-sp-remote',
+            ),
+        ),
+        'ssofirst' => array(
+            'name'         => 'SSO-first to service',
+            'descr'        => 'The number of logins at a Service Provider.',
+            'action'       => 'saml20-idp-SSO-first',
+            'col'          => 6,                     // Service Provider EntityID
+            'fieldPresentation' => array(
+                'class'    => 'statistics:Entity',
+                'config'   => 'saml20-sp-remote',
+            ),
+        ),
+        'slo' => array(
+            'name'         => 'SLO initiated from service',
+            'descr'        => 'The number of initated Sinlge Logout from each of the service providers.',
+            'action'       => 'saml20-idp-SLO',
+            'col'          => 7,                     // Service Provider EntityID that initiated the logout.
+            'fieldPresentation' => array(
+                'class'    => 'statistics:Entity',
+                'config'   => 'saml20-sp-remote',
+            ),
+        ),
+        'consent' => array(
+            'name'         => 'Consent',
+            'descr'        => 'Consent statistics. Everytime a user logs in to a service an entry is logged for one of three states: consent was found, consent was not found or consent storage was not available.',
+            'action'       => 'consent',
+            'col'          => 6,
+            'fieldPresentation' => array(
+                'class'    => 'statistics:Entity',
+                'config'   => 'saml20-sp-remote',
+            ),
+        ),
+        'consentresponse' => array(
+            'name'         => 'Consent response',
+            'descr'        => 'Consent response statistics. Everytime a user accepts consent, it is logged whether the user selected to remember the consent to next time.',
+            'action'       => 'consentResponse',
+            'col'          => 6,
+            'fieldPresentation' => array(
+                'class'    => 'statistics:Entity',
+                'config'   => 'saml20-sp-remote',
+            ),
+        ),
+        'slopages' => array(
+            'name'         => 'SLO iframe pages',
+            'descr'        => 'The varioust IFrame SLO pages a user visits',
+            'action'       => 'slo-iframe',
+            'col'          => 6,                     // Page the user visits.
+        ),
+        'slofail' => array(
+            'name'         => 'Failed iframe IdP-init SLOs',
+            'descr'        => 'The number of logout failures from various SPs',
+            'action'       => 'slo-iframe-fail',
+            'col'          => 6,                     // Service Provider EntityID that wasn't logged out.
+            'fieldPresentation' => array(
+                'class'    => 'statistics:Entity',
+                'config'   => 'saml20-sp-remote',
+            ),
+        ),
+    ),
 
+);
diff --git a/modules/statistics/hooks/hook_cron.php b/modules/statistics/hooks/hook_cron.php
index cff0093c522f418c681eabb70f0997999fe29e72..15bea92960c27b184eb0b2cece12d87317ce67ec 100644
--- a/modules/statistics/hooks/hook_cron.php
+++ b/modules/statistics/hooks/hook_cron.php
@@ -4,32 +4,37 @@
  *
  * @param array &$croninfo  Output
  */
-function statistics_hook_cron(&$croninfo) {
-	assert('is_array($croninfo)');
-	assert('array_key_exists("summary", $croninfo)');
-	assert('array_key_exists("tag", $croninfo)');
+function statistics_hook_cron(&$croninfo)
+{
+    assert('is_array($croninfo)');
+    assert('array_key_exists("summary", $croninfo)');
+    assert('array_key_exists("tag", $croninfo)');
 
-	$statconfig = SimpleSAML_Configuration::getConfig('module_statistics.php');
+    $statconfig = SimpleSAML_Configuration::getConfig('module_statistics.php');
 	
-	if (is_null($statconfig->getValue('cron_tag', NULL))) return;
-	if ($statconfig->getValue('cron_tag', NULL) !== $croninfo['tag']) return;
+    if (is_null($statconfig->getValue('cron_tag', nul))) {
+        return;
+    }
+    if ($statconfig->getValue('cron_tag', null) !== $croninfo['tag']) {
+        return;
+    }
 	
-	$maxtime = $statconfig->getInteger('time_limit', NULL);
-	if($maxtime){
-		set_time_limit($maxtime);
-	}
+    $maxtime = $statconfig->getInteger('time_limit', null);
+    if ($maxtime) {
+        set_time_limit($maxtime);
+    }
 	
-	try {
-		$aggregator = new sspmod_statistics_Aggregator();
-		$results = $aggregator->aggregate();
-		if (empty($results)) {
-			SimpleSAML\Logger::notice('Output from statistics aggregator was empty.');
-		} else {
-			$aggregator->store($results);
-		}
-	} catch (Exception $e) {
-		$message = 'Loganalyzer threw exception: ' . $e->getMessage();
-		SimpleSAML\Logger::warning($message);
-		$croninfo['summary'][] = $message;
-	}
+    try {
+        $aggregator = new sspmod_statistics_Aggregator();
+        $results = $aggregator->aggregate();
+        if (empty($results)) {
+            SimpleSAML\Logger::notice('Output from statistics aggregator was empty.');
+        } else {
+            $aggregator->store($results);
+        }
+    } catch (Exception $e) {
+        $message = 'Loganalyzer threw exception: ' . $e->getMessage();
+        SimpleSAML\Logger::warning($message);
+        $croninfo['summary'][] = $message;
+    }
 }
diff --git a/modules/statistics/hooks/hook_frontpage.php b/modules/statistics/hooks/hook_frontpage.php
index b8fbc724c1603c8287414d749104c9f9cb5a91c8..69a054b35fe36a6d0c532a3af15cd7052cee560c 100644
--- a/modules/statistics/hooks/hook_frontpage.php
+++ b/modules/statistics/hooks/hook_frontpage.php
@@ -4,18 +4,19 @@
  *
  * @param array &$links  The links on the frontpage, split into sections.
  */
-function statistics_hook_frontpage(&$links) {
-	assert('is_array($links)');
-	assert('array_key_exists("links", $links)');
+function statistics_hook_frontpage(&$links)
+{
+    assert('is_array($links)');
+    assert('array_key_exists("links", $links)');
 
-	$links['config']['statistics'] = array(
-		'href' => SimpleSAML\Module::getModuleURL('statistics/showstats.php'),
-		'text' => array('en' => 'Show statistics', 'no' => 'Vis statistikk'),
-		'shorttext' => array('en' => 'Statistics', 'no' => 'Statistikk'),
-	);
-	$links['config']['statisticsmeta'] = array(
-		'href' => SimpleSAML\Module::getModuleURL('statistics/statmeta.php'),
-		'text' => array('en' => 'Show statistics metadata', 'no' => 'Vis statistikk metadata'),
-		'shorttext' => array('en' => 'Statistics metadata', 'no' => 'Statistikk metadata'),
-	);
+    $links['config']['statistics'] = array(
+        'href' => SimpleSAML\Module::getModuleURL('statistics/showstats.php'),
+        'text' => array('en' => 'Show statistics', 'no' => 'Vis statistikk'),
+        'shorttext' => array('en' => 'Statistics', 'no' => 'Statistikk'),
+    );
+    $links['config']['statisticsmeta'] = array(
+        'href' => SimpleSAML\Module::getModuleURL('statistics/statmeta.php'),
+        'text' => array('en' => 'Show statistics metadata', 'no' => 'Vis statistikk metadata'),
+        'shorttext' => array('en' => 'Statistics metadata', 'no' => 'Statistikk metadata'),
+    );
 }
diff --git a/modules/statistics/hooks/hook_sanitycheck.php b/modules/statistics/hooks/hook_sanitycheck.php
index 236802c48ef15003cd4acfe839f663b011e284f8..ff84c27c80ade7bbc39c83fcb4360613d77a883d 100644
--- a/modules/statistics/hooks/hook_sanitycheck.php
+++ b/modules/statistics/hooks/hook_sanitycheck.php
@@ -4,35 +4,35 @@
  *
  * @param array &$hookinfo  hookinfo
  */
-function statistics_hook_sanitycheck(&$hookinfo) {
-	assert('is_array($hookinfo)');
-	assert('array_key_exists("errors", $hookinfo)');
-	assert('array_key_exists("info", $hookinfo)');
+function statistics_hook_sanitycheck(&$hookinfo)
+{
+    assert('is_array($hookinfo)');
+    assert('array_key_exists("errors", $hookinfo)');
+    assert('array_key_exists("info", $hookinfo)');
 
-	try {
-		$statconfig = SimpleSAML_Configuration::getConfig('module_statistics.php');
-	} catch(Exception $e) {
-		$hookinfo['errors'][] = '[statistics] Could not get configuration: ' . $e->getMessage(); return;
-	}
+    try {
+        $statconfig = SimpleSAML_Configuration::getConfig('module_statistics.php');
+    } catch(Exception $e) {
+        $hookinfo['errors'][] = '[statistics] Could not get configuration: ' . $e->getMessage(); return;
+    }
 
-	$statdir = $statconfig->getValue('statdir');
-	$inputfile = $statconfig->getValue('inputfile');
+    $statdir = $statconfig->getValue('statdir');
+    $inputfile = $statconfig->getValue('inputfile');
 
-	if (file_exists($statdir)) {
-		$hookinfo['info'][] = '[statistics] Statistics dir [' . $statdir . '] exists';
-		if (is_writable($statdir)) {
-			$hookinfo['info'][] = '[statistics] Statistics dir [' . $statdir . '] is writable';
-		} else {
-			$hookinfo['errors'][] = '[statistics] Statistics dir [' . $statdir . '] is not writable';
-		}
-	} else {
-		$hookinfo['errors'][] = '[statistics] Statistics dir [' . $statdir . '] does not exists';
-	}
+    if (file_exists($statdir)) {
+        $hookinfo['info'][] = '[statistics] Statistics dir [' . $statdir . '] exists';
+        if (is_writable($statdir)) {
+            $hookinfo['info'][] = '[statistics] Statistics dir [' . $statdir . '] is writable';
+        } else {
+            $hookinfo['errors'][] = '[statistics] Statistics dir [' . $statdir . '] is not writable';
+        }
+    } else {
+        $hookinfo['errors'][] = '[statistics] Statistics dir [' . $statdir . '] does not exists';
+    }
 
-
-	if (file_exists($inputfile)) {
-		$hookinfo['info'][] = '[statistics] Input file [' . $inputfile . '] exists';
-	} else {
-		$hookinfo['errors'][] = '[statistics] Input file [' . $inputfile . '] does not exists';
-	}
+    if (file_exists($inputfile)) {
+        $hookinfo['info'][] = '[statistics] Input file [' . $inputfile . '] exists';
+    } else {
+        $hookinfo['errors'][] = '[statistics] Input file [' . $inputfile . '] does not exists';
+    }
 }
diff --git a/modules/statistics/lib/AccessCheck.php b/modules/statistics/lib/AccessCheck.php
index 4d505356424c99ccb95a237c250f9c9f022c3a8b..a400f3377204f9b5f3671ff466cc1bc799624818 100644
--- a/modules/statistics/lib/AccessCheck.php
+++ b/modules/statistics/lib/AccessCheck.php
@@ -5,79 +5,77 @@
  *
  * @package SimpleSAMLphp
  */
-class sspmod_statistics_AccessCheck {
-
-
-	/**
-	 * Check that the user has access to the statistics.
-	 *
-	 * If the user doesn't have access, send the user to the login page.
-	 */
-	public static function checkAccess(SimpleSAML_Configuration $statconfig) {
-		$protected = $statconfig->getBoolean('protected', FALSE);
-		$authsource = $statconfig->getString('auth', NULL);
-		$allowedusers = $statconfig->getValue('allowedUsers', NULL);
-		$useridattr = $statconfig->getString('useridattr', 'eduPersonPrincipalName');
-
-		$acl = $statconfig->getValue('acl', NULL);
-		if ($acl !== NULL && !is_string($acl) && !is_array($acl)) {
-			throw new SimpleSAML_Error_Exception('Invalid value for \'acl\'-option. Should be an array or a string.');
-		}
-
-		if (!$protected) {
-			return;
-		}
-
-		if (SimpleSAML\Utils\Auth::isAdmin()) {
-			// User logged in as admin. OK.
-			SimpleSAML\Logger::debug('Statistics auth - logged in as admin, access granted');
-			return;
-		}
-
-		if (!isset($authsource)) {
-			// If authsource is not defined, init admin login.
+class sspmod_statistics_AccessCheck
+{
+    /**
+     * Check that the user has access to the statistics.
+     *
+     * If the user doesn't have access, send the user to the login page.
+     */
+    public static function checkAccess(SimpleSAML_Configuration $statconfig)
+    {
+        $protected = $statconfig->getBoolean('protected', false);
+        $authsource = $statconfig->getString('auth', null);
+        $allowedusers = $statconfig->getValue('allowedUsers', null);
+        $useridattr = $statconfig->getString('useridattr', 'eduPersonPrincipalName');
+
+        $acl = $statconfig->getValue('acl', null);
+        if ($acl !== null && !is_string($acl) && !is_array($acl)) {
+            throw new SimpleSAML_Error_Exception('Invalid value for \'acl\'-option. Should be an array or a string.');
+        }
+
+        if (!$protected) {
+            return;
+        }
+
+        if (SimpleSAML\Utils\Auth::isAdmin()) {
+            // User logged in as admin. OK.
+            SimpleSAML\Logger::debug('Statistics auth - logged in as admin, access granted');
+            return;
+        }
+
+        if (!isset($authsource)) {
+            // If authsource is not defined, init admin login.
             SimpleSAML\Utils\Auth::requireAdmin();
-		}
-
-		// We are using an authsource for login.
-
-		$as = new SimpleSAML_Auth_Simple($authsource);
-		$as->requireAuth();
-
-		// User logged in with auth source.
-		SimpleSAML\Logger::debug('Statistics auth - valid login with auth source [' . $authsource . ']');
-
-		// Retrieving attributes
-		$attributes = $as->getAttributes();
-
-		if (!empty($allowedusers)) {
-			// Check if userid exists
-			if (!isset($attributes[$useridattr][0]))
-				throw new Exception('User ID is missing');
-
-			// Check if userid is allowed access..
-			if (in_array($attributes[$useridattr][0], $allowedusers)) {
-				SimpleSAML\Logger::debug('Statistics auth - User granted access by user ID [' . $attributes[$useridattr][0] . ']');
-				return;
-			}
-			SimpleSAML\Logger::debug('Statistics auth - User denied access by user ID [' . $attributes[$useridattr][0] . ']');
-
-		} else {
-			SimpleSAML\Logger::debug('Statistics auth - no allowedUsers list.');
-		}
-
-		if (!is_null($acl)) {
-			$acl = new sspmod_core_ACL($acl);
-			if ($acl->allows($attributes)) {
-				SimpleSAML\Logger::debug('Statistics auth - allowed access by ACL.');
-				return;
-			}
-			SimpleSAML\Logger::debug('Statistics auth - denied access by ACL.');
-		} else {
-			SimpleSAML\Logger::debug('Statistics auth - no ACL configured.');
-		}
-
-		throw new SimpleSAML_Error_Exception('Access denied to the current user.');
-	}
-
-}
\ No newline at end of file
+        }
+
+        // We are using an authsource for login.
+
+        $as = new SimpleSAML_Auth_Simple($authsource);
+        $as->requireAuth();
+
+        // User logged in with auth source.
+        SimpleSAML\Logger::debug('Statistics auth - valid login with auth source [' . $authsource . ']');
+
+        // Retrieving attributes
+        $attributes = $as->getAttributes();
+
+        if (!empty($allowedusers)) {
+            // Check if userid exists
+            if (!isset($attributes[$useridattr][0])) {
+                throw new Exception('User ID is missing');
+            }
+
+            // Check if userid is allowed access..
+            if (in_array($attributes[$useridattr][0], $allowedusers)) {
+                SimpleSAML\Logger::debug('Statistics auth - User granted access by user ID [' . $attributes[$useridattr][0] . ']');
+                return;
+            }
+            SimpleSAML\Logger::debug('Statistics auth - User denied access by user ID [' . $attributes[$useridattr][0] . ']');
+        } else {
+            SimpleSAML\Logger::debug('Statistics auth - no allowedUsers list.');
+        }
+
+        if (!is_null($acl)) {
+            $acl = new sspmod_core_ACL($acl);
+            if ($acl->allows($attributes)) {
+                SimpleSAML\Logger::debug('Statistics auth - allowed access by ACL.');
+                return;
+            }
+            SimpleSAML\Logger::debug('Statistics auth - denied access by ACL.');
+        } else {
+            SimpleSAML\Logger::debug('Statistics auth - no ACL configured.');
+        }
+        throw new SimpleSAML_Error_Exception('Access denied to the current user.');
+    }
+}
diff --git a/modules/statistics/lib/Aggregator.php b/modules/statistics/lib/Aggregator.php
index 18829878c7d5de2309562428a2e1b2e4daab1260..4ec74019c94df52cc7422193470678ac560cfc9d 100644
--- a/modules/statistics/lib/Aggregator.php
+++ b/modules/statistics/lib/Aggregator.php
@@ -3,273 +3,309 @@
  * @author Andreas Ã…kre Solberg <andreas.solberg@uninett.no>
  * @package SimpleSAMLphp
  */
-class sspmod_statistics_Aggregator {
-
-	private $statconfig;
-	private $statdir;
-	private $inputfile;
-	private $statrules;
-	private $offset;
-	private $metadata;
-	private $fromcmdline;
-	
-	private $starttime;
-
-	/**
-	 * Constructor
-	 */
-	public function __construct($fromcmdline = FALSE) {
-	
-		$this->fromcmdline = $fromcmdline;
-		$this->statconfig = SimpleSAML_Configuration::getConfig('module_statistics.php');
-		
-		$this->statdir = $this->statconfig->getValue('statdir');
-		$this->inputfile = $this->statconfig->getValue('inputfile');
-		$this->statrules = $this->statconfig->getValue('statrules');
-		$this->timeres = $this->statconfig->getValue('timeres');
-		$this->offset = $this->statconfig->getValue('offset', 0);
-		$this->metadata = NULL;
-		
-		$this->starttime = time();
-	}
-	
-	public function dumpConfig() {
-		echo 'Statistics directory   : ' . $this->statdir . "\n";
-		echo 'Input file             : ' . $this->inputfile . "\n";
-		echo 'Offset                 : ' . $this->offset . "\n";
-	}
-	
-	public function debugInfo() {
-		echo 'Memory usage           : ' . number_format(memory_get_usage() / (1024*1024), 2) . " MB\n";
-	}
-	
-	public function loadMetadata() {
-		$filename = $this->statdir . '/.stat.metadata';
-		$metadata = NULL;
-		if (file_exists($filename)) {
-			$metadata = unserialize(file_get_contents($filename));
-		}
-		$this->metadata = $metadata;
-	}
-	
-	public function getMetadata() {
-		return $this->metadata;
-	}
-	
-	public function saveMetadata() {
-		$this->metadata['time'] = time() - $this->starttime;
-		$this->metadata['memory'] = memory_get_usage();
-		$this->metadata['lastrun'] = time();
-		
-		$filename = $this->statdir . '/.stat.metadata';
-		file_put_contents($filename, serialize($this->metadata), LOCK_EX);
-	}
-	
-	public function aggregate($debug = FALSE) {
-		
-		$this->loadMetadata();
-		
-		if (!is_dir($this->statdir)) 
-			throw new Exception('Statistics module: output dir do not exists [' . $this->statdir . ']');
-		
-		if (!file_exists($this->inputfile)) 
-			throw new Exception('Statistics module: input file do not exists [' . $this->inputfile . ']');
-		
-		$file = fopen($this->inputfile, 'r');
-
-		if ($file === FALSE)
-			throw new Exception('Statistics module: unable to open file [' . $this->inputfile . ']');
-		
-		$logparser = new sspmod_statistics_LogParser(
-			$this->statconfig->getValue('datestart', 0), $this->statconfig->getValue('datelength', 15), $this->statconfig->getValue('offsetspan', 44)
-		);
-		$datehandler = array(
-			'default' => new sspmod_statistics_DateHandler($this->offset),
-			'month' => new  sspmod_statistics_DateHandlerMonth($this->offset),
-		);
-		
-		
-		$notBefore = 0; $lastRead = 0; $lastlinehash = '-';
-		if (isset($this->metadata)) {
-			$notBefore = $this->metadata['notBefore'];
-			$lastlinehash = $this->metadata['lastlinehash'];
-		}
-		
-		$lastlogline = 'sdfsdf'; 
-		$lastlineflip = FALSE;
-		$results = array();
-		
-		$i = 0;
-		// Parse through log file, line by line
-		while (!feof($file)) {
-			
-			$logline = fgets($file, 4096);
-			
-			// Continue if STAT is not found on line
-			if (!preg_match('/STAT/', $logline)) continue;
-			$i++; $lastlogline = $logline;
-			
-			// Parse log, and extract epoch time and rest of content.
-			$epoch = $logparser->parseEpoch($logline);
-			$content = $logparser->parseContent($logline);
-			$action = trim($content[5]);
-
-			if ($this->fromcmdline && ($i % 10000) == 0) {
-				echo("Read line " . $i . "\n");
-			}
-			
-			if ($debug) {
-				echo("----------------------------------------\n");
-				echo('Log line: ' . $logline . "\n");
-				echo('Date parse [' . substr($logline, 0, $this->statconfig->getValue('datelength', 15)) . '] to [' . date(DATE_RFC822, $epoch) . ']' . "\n");
-				echo htmlentities(print_r($content, true));
-				if ($i >= 13) exit;
-			}
-			
-			if ($epoch > $lastRead) $lastRead = $epoch;
-			if ($epoch === $notBefore) {
-				if(!$lastlineflip) {
-					if (sha1($logline) === $lastlinehash) { 
-						$lastlineflip = TRUE;
-					}
-					continue;
-				}
-			}
-			if ($epoch < $notBefore) continue;
-			
-			// Iterate all the statrules from config.
-			foreach ($this->statrules AS $rulename => $rule) {
-				
-				$type = 'aggregate';
-				if (array_key_exists('type', $rule)) $type = $rule['type'];
-				if ($type !== 'aggregate') continue;
-				
-				foreach($this->timeres AS $tres => $tresconfig ) {
-
-					$dh = 'default';
-					if (isset($tresconfig['customDateHandler'])) $dh = $tresconfig['customDateHandler'];
-			
-					$timeslot = $datehandler['default']->toSlot($epoch, $tresconfig['slot']);
-					$fileslot = $datehandler[$dh]->toSlot($epoch, $tresconfig['fileslot']);
-				
-					if (isset($rule['action']) && ($action !== $rule['action'])) continue;
-
-					$difcol = self::getDifCol($content, $rule['col']);
-		
-					if (!isset($results[$rulename][$tres][$fileslot][$timeslot]['_'])) $results[$rulename][$tres][$fileslot][$timeslot]['_'] = 0;
-					if (!isset($results[$rulename][$tres][$fileslot][$timeslot][$difcol])) $results[$rulename][$tres][$fileslot][$timeslot][$difcol] = 0;
-		
-					$results[$rulename][$tres][$fileslot][$timeslot]['_']++;
-					$results[$rulename][$tres][$fileslot][$timeslot][$difcol]++;
-				}
-			}
-		}
-		$this->metadata['notBefore'] = $lastRead;
-		$this->metadata['lastline'] = $lastlogline;
-		$this->metadata['lastlinehash'] = sha1($lastlogline);
-		return $results;
-	}
-	
-	private static function getDifCol($content, $colrule) {
-		if (is_int($colrule)) {
-			return trim($content[$colrule]);
-		} elseif(is_array($colrule)) {
-			$difcols = array();
-			foreach($colrule AS $cr) {
-				$difcols[] = trim($content[$cr]);
-			}
-			return join('|', $difcols);
-		} else {
-			return 'NA';
-		}
-	}
-	
-	private function cummulateData($previous, $newdata) {
-		$dataset = array();
-		foreach($previous AS $slot => $dataarray) {
-			if (!array_key_exists($slot, $dataset)) $dataset[$slot] = array();
-			foreach($dataarray AS $key => $data) {
-				if (!array_key_exists($key, $dataset[$slot])) $dataset[$slot][$key] = 0;
-				$dataset[$slot][$key] += $data;
-			}
-		}
-		foreach($newdata AS $slot => $dataarray) {
-			if (!array_key_exists($slot, $dataset)) $dataset[$slot] = array();
-			foreach($dataarray AS $key => $data) {
-				if (!array_key_exists($key, $dataset[$slot])) $dataset[$slot][$key] = 0;
-				$dataset[$slot][$key] += $data;
-			}
-		}
-		return $dataset;
-	}
-	
-	
-	public function store($results) {
-
-		$datehandler = array(
-			'default' => new sspmod_statistics_DateHandler($this->offset),
-			'month' => new  sspmod_statistics_DateHandlerMonth($this->offset),
-		);
-	
-		// Iterate the first level of results, which is per rule, as defined in the config.
-		foreach ($results AS $rulename => $timeresdata) {
-
-			// Iterate over time resolutions
-			foreach($timeresdata AS $tres => $resres) {
-
-				$dh = 'default';
-				if (isset($this->timeres[$tres]['customDateHandler'])) $dh = $this->timeres[$tres]['customDateHandler'];
-			
-				$filenos = array_keys($resres);
-				$lastfile = $filenos[count($filenos)-1];
-			
-				// Iterate the second level of results, which is the fileslot.
-				foreach ($resres AS $fileno => $fileres) {
-					
-					
-					// Slots that have data.
-					$slotlist = array_keys($fileres);
-				
-					// The last slot.
-					$maxslot = $slotlist[count($slotlist)-1];
-		
-					// Get start and end slot number within the file, based on the fileslot.
-					$start = (int)$datehandler['default']->toSlot(
-							$datehandler[$dh]->fromSlot($fileno, $this->timeres[$tres]['fileslot']), 
-							$this->timeres[$tres]['slot']);
-					$end = (int)$datehandler['default']->toSlot(
-							$datehandler[$dh]->fromSlot($fileno+1, $this->timeres[$tres]['fileslot']), 
-							$this->timeres[$tres]['slot']);
-
-					// Fill in missing entries and sort file results
-					$filledresult = array();
-					for ($slot = $start; $slot < $end; $slot++) {
-						if (array_key_exists($slot,  $fileres)) {
-							$filledresult[$slot] = $fileres[$slot];
-						} else {
-							if ($lastfile == $fileno && $slot > $maxslot) {
-								$filledresult[$slot] = array('_' => NULL);
-							} else {
-								$filledresult[$slot] = array('_' => 0);
-							}
-						}
-					}
-					
-					$filename = $this->statdir . '/' . $rulename . '-' . $tres . '-' . $fileno . '.stat';
-					if (file_exists($filename)) {
-						$previousData = unserialize(file_get_contents($filename));
-						$filledresult = $this->cummulateData($previousData, $filledresult);	
-					}
-				
-					// store file
-					file_put_contents($filename, serialize($filledresult), LOCK_EX);
-				}
-				
-			}
-			
-		}
-		$this->saveMetadata();
-	
-	}
+class sspmod_statistics_Aggregator
+{
+    private $statconfig;
+    private $statdir;
+    private $inputfile;
+    private $statrules;
+    private $offset;
+    private $metadata;
+    private $fromcmdline;
+    private $starttime;
 
+    /**
+     * Constructor
+     */
+    public function __construct($fromcmdline = false)
+    {
+        $this->fromcmdline = $fromcmdline;
+        $this->statconfig = SimpleSAML_Configuration::getConfig('module_statistics.php');
+
+        $this->statdir = $this->statconfig->getValue('statdir');
+        $this->inputfile = $this->statconfig->getValue('inputfile');
+        $this->statrules = $this->statconfig->getValue('statrules');
+        $this->timeres = $this->statconfig->getValue('timeres');
+        $this->offset = $this->statconfig->getValue('offset', 0);
+        $this->metadata = null;
+
+        $this->starttime = time();
+    }
+
+    public function dumpConfig()
+    {
+        echo 'Statistics directory   : ' . $this->statdir . "\n";
+        echo 'Input file             : ' . $this->inputfile . "\n";
+        echo 'Offset                 : ' . $this->offset . "\n";
+    }
+
+    public function debugInfo()
+    {
+        echo 'Memory usage           : ' . number_format(memory_get_usage() / (1024*1024), 2) . " MB\n";
+    }
+
+    public function loadMetadata()
+    {
+        $filename = $this->statdir . '/.stat.metadata';
+        $metadata = null;
+        if (file_exists($filename)) {
+            $metadata = unserialize(file_get_contents($filename));
+        }
+        $this->metadata = $metadata;
+    }
+
+    public function getMetadata()
+    {
+        return $this->metadata;
+    }
+
+    public function saveMetadata()
+    {
+        $this->metadata['time'] = time() - $this->starttime;
+        $this->metadata['memory'] = memory_get_usage();
+        $this->metadata['lastrun'] = time();
+
+        $filename = $this->statdir . '/.stat.metadata';
+        file_put_contents($filename, serialize($this->metadata), LOCK_EX);
+    }
+
+    public function aggregate($debug = false) {
+        $this->loadMetadata();
+
+        if (!is_dir($this->statdir)) {
+            throw new Exception('Statistics module: output dir do not exists [' . $this->statdir . ']');
+        }
+
+        if (!file_exists($this->inputfile)) {
+            throw new Exception('Statistics module: input file do not exists [' . $this->inputfile . ']');
+        }
+
+        $file = fopen($this->inputfile, 'r');
+
+        if ($file === false) {
+            throw new Exception('Statistics module: unable to open file [' . $this->inputfile . ']');
+        }
+
+        $logparser = new sspmod_statistics_LogParser(
+            $this->statconfig->getValue('datestart', 0), $this->statconfig->getValue('datelength', 15), $this->statconfig->getValue('offsetspan', 44)
+        );
+        $datehandler = array(
+            'default' => new sspmod_statistics_DateHandler($this->offset),
+            'month' => new  sspmod_statistics_DateHandlerMonth($this->offset),
+        );
+
+        $notBefore = 0;
+        $lastRead = 0;
+        $lastlinehash = '-';
+
+        if (isset($this->metadata)) {
+            $notBefore = $this->metadata['notBefore'];
+            $lastlinehash = $this->metadata['lastlinehash'];
+        }
+
+        $lastlogline = 'sdfsdf'; 
+        $lastlineflip = false;
+        $results = array();
+
+        $i = 0;
+        // Parse through log file, line by line
+        while (!feof($file)) {
+            $logline = fgets($file, 4096);
+
+            // Continue if STAT is not found on line
+            if (!preg_match('/STAT/', $logline)) {
+                continue;
+            }
+
+            $i++;
+            $lastlogline = $logline;
+
+            // Parse log, and extract epoch time and rest of content.
+            $epoch = $logparser->parseEpoch($logline);
+            $content = $logparser->parseContent($logline);
+            $action = trim($content[5]);
+
+            if ($this->fromcmdline && ($i % 10000) == 0) {
+                echo("Read line " . $i . "\n");
+            }
+
+            if ($debug) {
+                echo("----------------------------------------\n");
+                echo('Log line: ' . $logline . "\n");
+                echo('Date parse [' . substr($logline, 0, $this->statconfig->getValue('datelength', 15)) . '] to [' . date(DATE_RFC822, $epoch) . ']' . "\n");
+                echo htmlentities(print_r($content, true));
+                if ($i >= 13) {
+                    exit;
+                }
+            }
+
+            if ($epoch > $lastRead) {
+                $lastRead = $epoch;
+            }
+
+            if ($epoch === $notBefore) {
+                if (!$lastlineflip) {
+                    if (sha1($logline) === $lastlinehash) { 
+                        $lastlineflip = true;
+                    }
+                    continue;
+                }
+            }
+
+            if ($epoch < $notBefore) {
+                continue;
+            }
+
+            // Iterate all the statrules from config.
+            foreach ($this->statrules as $rulename => $rule) {
+                $type = 'aggregate';
+
+                if (array_key_exists('type', $rule)) {
+                    $type = $rule['type'];
+                }
+
+                if ($type !== 'aggregate') {
+                    continue;
+                }
+
+                foreach ($this->timeres AS $tres => $tresconfig ) {
+                    $dh = 'default';
+                    if (isset($tresconfig['customDateHandler'])) {
+                        $dh = $tresconfig['customDateHandler'];
+                    }
+
+                    $timeslot = $datehandler['default']->toSlot($epoch, $tresconfig['slot']);
+                    $fileslot = $datehandler[$dh]->toSlot($epoch, $tresconfig['fileslot']);
+
+                    if (isset($rule['action']) && ($action !== $rule['action'])) {
+                        continue;
+                    }
+
+                    $difcol = self::getDifCol($content, $rule['col']);
+
+                    if (!isset($results[$rulename][$tres][$fileslot][$timeslot]['_'])) {
+                        $results[$rulename][$tres][$fileslot][$timeslot]['_'] = 0;
+                    }
+                    if (!isset($results[$rulename][$tres][$fileslot][$timeslot][$difcol])) {
+                        $results[$rulename][$tres][$fileslot][$timeslot][$difcol] = 0;
+                    }
+
+                    $results[$rulename][$tres][$fileslot][$timeslot]['_']++;
+                    $results[$rulename][$tres][$fileslot][$timeslot][$difcol]++;
+                }
+            }
+        }
+        $this->metadata['notBefore'] = $lastRead;
+        $this->metadata['lastline'] = $lastlogline;
+        $this->metadata['lastlinehash'] = sha1($lastlogline);
+        return $results;
+    }
+
+    private static function getDifCol($content, $colrule)
+    {
+        if (is_int($colrule)) {
+            return trim($content[$colrule]);
+        } elseif (is_array($colrule)) {
+            $difcols = array();
+            foreach ($colrule as $cr) {
+                $difcols[] = trim($content[$cr]);
+            }
+            return join('|', $difcols);
+        } else {
+            return 'NA';
+        }
+    }
+
+    private function cummulateData($previous, $newdata)
+    {
+        $dataset = array();
+        foreach ($previous as $slot => $dataarray) {
+            if (!array_key_exists($slot, $dataset)) {
+                $dataset[$slot] = array();
+            }
+            foreach ($dataarray as $key => $data) {
+                if (!array_key_exists($key, $dataset[$slot])) {
+                    $dataset[$slot][$key] = 0;
+                }
+                $dataset[$slot][$key] += $data;
+            }
+        }
+        foreach ($newdata as $slot => $dataarray) {
+            if (!array_key_exists($slot, $dataset)) {
+                $dataset[$slot] = array();
+            }
+            foreach ($dataarray as $key => $data) {
+                if (!array_key_exists($key, $dataset[$slot])) {
+                    $dataset[$slot][$key] = 0;
+                }
+                $dataset[$slot][$key] += $data;
+            }
+        }
+        return $dataset;
+    }
+
+    public function store($results)
+    {
+        $datehandler = array(
+            'default' => new sspmod_statistics_DateHandler($this->offset),
+            'month' => new  sspmod_statistics_DateHandlerMonth($this->offset),
+        );
+
+        // Iterate the first level of results, which is per rule, as defined in the config.
+        foreach ($results as $rulename => $timeresdata) {
+            // Iterate over time resolutions
+            foreach ($timeresdata as $tres => $resres) {
+                $dh = 'default';
+                if (isset($this->timeres[$tres]['customDateHandler'])) {
+                    $dh = $this->timeres[$tres]['customDateHandler'];
+                }
+
+                $filenos = array_keys($resres);
+                $lastfile = $filenos[count($filenos) - 1];
+
+                // Iterate the second level of results, which is the fileslot.
+                foreach ($resres as $fileno => $fileres) {
+                    // Slots that have data.
+                    $slotlist = array_keys($fileres);
+
+                    // The last slot.
+                    $maxslot = $slotlist[count($slotlist) - 1];
+
+                    // Get start and end slot number within the file, based on the fileslot.
+                    $start = (int)$datehandler['default']->toSlot(
+                        $datehandler[$dh]->fromSlot($fileno, $this->timeres[$tres]['fileslot']), 
+                        $this->timeres[$tres]['slot']
+                    );
+                    $end = (int)$datehandler['default']->toSlot(
+                        $datehandler[$dh]->fromSlot($fileno+1, $this->timeres[$tres]['fileslot']), 
+                        $this->timeres[$tres]['slot']
+                    );
+
+                    // Fill in missing entries and sort file results
+                    $filledresult = array();
+                    for ($slot = $start; $slot < $end; $slot++) {
+                        if (array_key_exists($slot,  $fileres)) {
+                            $filledresult[$slot] = $fileres[$slot];
+                        } else {
+                            if ($lastfile == $fileno && $slot > $maxslot) {
+                                $filledresult[$slot] = array('_' => null);
+                            } else {
+                                $filledresult[$slot] = array('_' => 0);
+                            }
+                        }
+                    }
+
+                    $filename = $this->statdir . '/' . $rulename . '-' . $tres . '-' . $fileno . '.stat';
+                    if (file_exists($filename)) {
+                        $previousData = unserialize(file_get_contents($filename));
+                        $filledresult = $this->cummulateData($previousData, $filledresult);	
+                    }
+
+                    // store file
+                    file_put_contents($filename, serialize($filledresult), LOCK_EX);
+                }
+            }
+        }
+        $this->saveMetadata();
+    }
 }
diff --git a/modules/statistics/lib/DateHandler.php b/modules/statistics/lib/DateHandler.php
index 34eab33591434c9ebfc8eed00896779be1eaa211..13ed07c7f5dd5b8d25d40d1fca87ad4da784100a 100644
--- a/modules/statistics/lib/DateHandler.php
+++ b/modules/statistics/lib/DateHandler.php
@@ -3,48 +3,56 @@
  * @author Andreas Ã…kre Solberg <andreas.solberg@uninett.no>
  * @package SimpleSAMLphp
  */
-class sspmod_statistics_DateHandler {
+class sspmod_statistics_DateHandler
+{
+    protected $offset;
 
-	protected $offset;
+    /**
+     * Constructor
+     *
+     * @param array $offset 	Date offset
+     */
+    public function __construct($offset)
+    {
+        $this->offset = $offset;
+    }
 
-	/**
-	 * Constructor
-	 *
-	 * @param array $offset 	Date offset
-	 */
-	public function __construct($offset) {
-		$this->offset = $offset;
-	}
-	
-	protected function getDST($timestamp) {
-		if (idate('I', $timestamp)) return 3600;
-		return 0;
-	}
+    protected function getDST($timestamp)
+    {
+        if (idate('I', $timestamp)) {
+            return 3600;
+        }
+        return 0;
+    }
 
-	public function toSlot($epoch, $slotsize) {
-		$dst = $this->getDST($epoch);
-		return floor( ($epoch + $this->offset + $dst) / $slotsize);
-	}
+    public function toSlot($epoch, $slotsize)
+    {
+        $dst = $this->getDST($epoch);
+        return floor( ($epoch + $this->offset + $dst) / $slotsize);
+    }
 
-	public function fromSlot($slot, $slotsize) {
-		$temp = $slot*$slotsize - $this->offset;
-		$dst = $this->getDST($temp);
-		return $slot*$slotsize - $this->offset - $dst;
-	}
+    public function fromSlot($slot, $slotsize)
+    {
+        $temp = $slot*$slotsize - $this->offset;
+        $dst = $this->getDST($temp);
+        return $slot*$slotsize - $this->offset - $dst;
+    }
 
-	public function prettyDateEpoch($epoch, $dateformat) {
-		return date($dateformat, $epoch);
-	}
+    public function prettyDateEpoch($epoch, $dateformat)
+    {
+        return date($dateformat, $epoch);
+    }
 
-	public function prettyDateSlot($slot, $slotsize, $dateformat) {
-		return $this->prettyDateEpoch($this->fromSlot($slot, $slotsize), $dateformat);
+    public function prettyDateSlot($slot, $slotsize, $dateformat)
+    {
+        return $this->prettyDateEpoch($this->fromSlot($slot, $slotsize), $dateformat);
+    }
 
-	}
-	
-	public function prettyHeader($from, $to, $slotsize, $dateformat) {
-		$text = $this->prettyDateSlot($from, $slotsize, $dateformat);
-		$text .= ' to ';
-		$text .= $this->prettyDateSlot($to, $slotsize, $dateformat);
-		return $text;
-	}
+    public function prettyHeader($from, $to, $slotsize, $dateformat)
+    {
+        $text = $this->prettyDateSlot($from, $slotsize, $dateformat);
+        $text .= ' to ';
+        $text .= $this->prettyDateSlot($to, $slotsize, $dateformat);
+        return $text;
+    }
 }
diff --git a/modules/statistics/lib/DateHandlerMonth.php b/modules/statistics/lib/DateHandlerMonth.php
index b0f4e2cd15c058b8c476e66dfda37bafaa828c87..33f9762d91a3c707fb9be1410f879d957125144e 100644
--- a/modules/statistics/lib/DateHandlerMonth.php
+++ b/modules/statistics/lib/DateHandlerMonth.php
@@ -3,39 +3,37 @@
  * @author Andreas Ã…kre Solberg <andreas.solberg@uninett.no>
  * @package SimpleSAMLphp
  */
-class sspmod_statistics_DateHandlerMonth extends sspmod_statistics_DateHandler {
+class sspmod_statistics_DateHandlerMonth extends sspmod_statistics_DateHandler
+{
+    /**
+     * Constructor
+     *
+     * @param array $offset 	Date offset
+     */
+    public function __construct($offset)
+    {
+        $this->offset = $offset;
+    }
 
-	/**
-	 * Constructor
-	 *
-	 * @param array $offset 	Date offset
-	 */
-	public function __construct($offset) {
-		$this->offset = $offset;
-	}
-	
+    public function toSlot($epoch, $slotsize)
+    {
+        $dsttime = $this->getDST($epoch) + $epoch;
+        $parsed = getdate($dsttime);
+        $slot = (($parsed['year'] - 2000) * 12) + $parsed['mon'] - 1;
+        return $slot;
+    }
 
-	public function toSlot($epoch, $slotsize) {
-		$dsttime = $this->getDST($epoch) + $epoch;
-		$parsed = getdate($dsttime);
-		$slot = (($parsed['year'] - 2000) * 12) + $parsed['mon'] - 1;
-		return $slot;
-	}
+    public function fromSlot($slot, $slotsize)
+    {
+        $month = ($slot % 12);
+        $year = 2000 + floor($slot / 12);
+        return mktime(0, 0, 0, $month + 1, 1, $year);
+    }
 
-	public function fromSlot($slot, $slotsize) {
-		
-		$month = ($slot % 12);
-		$year = 2000 + floor($slot / 12);
-		
-		$epoch = mktime(0, 0, 0, $month + 1, 1, $year);
-		return $epoch;
-	}
-
-	public function prettyHeader($from, $to, $slotsize, $dateformat) {
-		
-		$month = ($from % 12) + 1;
-		$year = 2000 + floor($from / 12);
-		
-		return $year . '-' . $month;
-	}
+    public function prettyHeader($from, $to, $slotsize, $dateformat)
+    {
+        $month = ($from % 12) + 1;
+        $year = 2000 + floor($from / 12);
+        return $year . '-' . $month;
+    }
 }
diff --git a/modules/statistics/lib/Graph/GoogleCharts.php b/modules/statistics/lib/Graph/GoogleCharts.php
index b062fe41987b373ecf09853536d207d44f05ce0f..d86ccc788d4384875966cf590e737c1641a807aa 100644
--- a/modules/statistics/lib/Graph/GoogleCharts.php
+++ b/modules/statistics/lib/Graph/GoogleCharts.php
@@ -6,172 +6,174 @@
  * @author Andreas Ã…kre Solberg <andreas.solberg@uninett.no>
  * @package SimpleSAMLphp
  */
-class sspmod_statistics_Graph_GoogleCharts {
-
-	private $x, $y;
-
-	/**
-	 * Constructor.
-	 *
-	 * Takes dimension of graph as parameters. X and Y.
-	 *
-	 * @param $x 	X dimension. Default 800.
-	 * @param $y 	Y dimension. Default 350.
-	 */
-	public function __construct($x = 800, $y = 350) {
-		$this->x = $x; $this->y = $y;
-	}
-
-	private function encodeaxis($axis) {
-		return join('|', $axis);
-	}
-
-	# t:10.0,58.0,95.0
-	private function encodedata($datasets) {
-		$setstr = array();
-		foreach ($datasets AS $dataset) {
-			$setstr[] = self::extEncode($dataset);
-		}
-		return 'e:' . join(',', $setstr);
-	}
-	
-	
-	public static function extEncode($values) { #}, $max = 4095, $min = 0){ 
-		$extended_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.'; 
-		$chardata = ''; 
-		$delta = 4095;
-		$size = (strlen($extended_table)); 
-		
-		foreach($values as $k => $v){ 
-			if($v >= 0 && $v <= 100){ 
-				$first = substr($extended_table, intval( ($delta*$v/100) / $size),1);
-				$second = substr($extended_table, intval( ($delta*$v/100) % $size), 1);
-				$chardata .= "$first$second";
-			} else { 
-				$chardata .= '__'; // Value out of max range; 
-			} 
-		}
-		return($chardata); 
-	}
-	
-	
-	/**
-	 * Return colors between multiple graphs...
-	 */
-	private function getFillArea($datasets) {
-		if (count($datasets) < 2) return '';
-		
-		$colors = array('eeeeee', 'cccccc', 'aaaaaa', '99eecc');
-		$num = count($datasets) ;
-		$colstr = array();
-		for ($i = 0; $i < $num; $i++) {
-			$colstr[] = 'b' . ',' . $colors[$i] . ',' . ($i) . ',' . ($i+1) . ',0';
-		}
-		return '&chm=' . join('|', $colstr);
-	}
-	
-	
-	
-	/**
-	 * Generate a Google Charts URL which points to a generated image.
-	 * More documentation on Google Charts here: 
-	 *   http://code.google.com/apis/chart/
-	 *
-	 * @param $axis		Axis
-	 * @param $axpis	Axis positions
-	 * @param $datasets	Datasets values
-	 * @param $max		Max value. Will be the topmost value on the Y-axis.
-	 */
-	public function show($axis, $axispos, $datasets, $maxes) {
-	
-		$labeld = '&chxt=x,y' . '&chxr=0,0,1|1,0,' . $maxes[0];
-		if (count($datasets) > 1) {
-			if (count($datasets) !== count($maxes)) {
-				throw new Exception('Incorrect number of max calculations for graph plotting.');
-			}
-			$labeld = '&chxt=x,y,r' . 
-				'&chxr=0,0,1|1,0,' . $maxes[0] . '|2,0,' . $maxes[1];
-			
-		}
- 	
-		$url = 'http://chart.apis.google.com/chart?' .
-			
-			// Dimension of graph. Default is 800x350
-			'chs=' . $this->x . 'x' . $this->y . 
-			
-			// Dateset values
-			'&chd=' . $this->encodedata($datasets) .
-			
-			// Fill area...
-			'&chco=ff5c00,cca600' . 
-			'&chls=1,1,0|1,6,3' .
-			
-			// chart type is linechart
-			'&cht=lc' .
-			$labeld .
-			'&chxl=0:|' . $this->encodeaxis($axis) . # . $'|1:||top' .
-			'&chxp=0,' . join(',', $axispos) .
-			'&chg=' . (2400/(count($datasets[0])-1)) . ',-1,3,3';   // lines
-		return $url;
-	}
-	
-	public function showPie($axis, $datasets) {
-		
-		$url = 'http://chart.apis.google.com/chart?' .
-
-			// Dimension of graph. Default is 800x350
-			'chs=' . $this->x . 'x' . $this->y . 
-
-			// Dateset values.
-			'&chd=' . $this->encodedata(array($datasets)) .
-
-			// chart type is linechart
-			'&cht=p' .
-
-			'&chl=' . $this->encodeaxis($axis);
-		return $url;
-	}
-	
-	/**
-	 * Takes a input value, and generates a value that suits better as a max
-	 * value on the Y-axis. In example 37.6 will not make a good max value, instead
-	 * it will return 40. It will always return an equal or larger number than it gets
-	 * as input.
-	 *
-	 * Here is some test code:
-	 * <code>
-	 * 		$foo = array(0, 2, 2.3, 2.6, 6, 10, 15, 98, 198, 256, 487, 563, 763, 801, 899, 999, 987, 198234.485, 283746);
-	 *		foreach ($foo AS $f) {
-	 *			echo '<p>' . $f . ' => ' . sspmod_statistics_Graph_GoogleCharts::roof($f);
-	 *		}
-	 * </code>
-	 * 
-	 * @param $max 	Input value.
-	 */
-	public static function roof($max) {
-		$mul = 1;
-
-		if ($max < 1) {
-			return 1;
-		}
-
-		$t = intval(ceil($max));
-		while ($t > 100) {
-			$t /= 10;
-			$mul *= 10;
-		}
-
-		$maxGridLines = 10;
-		$candidates = array(1, 2, 5, 10, 20, 25, 50, 100);
-
-		foreach ($candidates as $c) {
-			if ($t / $c < $maxGridLines) {
-				$tick_y = $c * $mul;
-				$target_top = intval(ceil($max / $tick_y) * $tick_y);
-				return $target_top;
-			}
-		}
-
-	}
+class sspmod_statistics_Graph_GoogleCharts
+{
+    private $x, $y;
 
+    /**
+     * Constructor.
+     *
+     * Takes dimension of graph as parameters. X and Y.
+     *
+     * @param $x    X dimension. Default 800.
+     * @param $y    Y dimension. Default 350.
+     */
+    public function __construct($x = 800, $y = 350)
+    {
+        $this->x = $x;
+        $this->y = $y;
+    }
+
+    private function encodeaxis($axis)
+    {
+        return join('|', $axis);
+    }
+
+    // t:10.0,58.0,95.0
+    private function encodedata($datasets)
+    {
+        $setstr = array();
+        foreach ($datasets as $dataset) {
+            $setstr[] = self::extEncode($dataset);
+        }
+        return 'e:' . join(',', $setstr);
+    }
+
+    public static function extEncode($values) // $max = 4095, $min = 0
+    {
+        $extended_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.';
+        $chardata = '';
+        $delta = 4095;
+        $size = (strlen($extended_table));
+
+        foreach ($values as $k => $v) {
+            if ($v >= 0 && $v <= 100) {
+                $first = substr($extended_table, intval(($delta * $v / 100) / $size), 1);
+                $second = substr($extended_table, intval(($delta * $v / 100) % $size), 1);
+                $chardata .= "$first$second";
+            } else { 
+                $chardata .= '__'; // Value out of max range; 
+            } 
+        }
+        return($chardata); 
+    }
+
+    /**
+     * Return colors between multiple graphs...
+     */
+    private function getFillArea($datasets)
+    {
+        if (count($datasets) < 2) {
+            return '';
+        }
+
+        $colors = array('eeeeee', 'cccccc', 'aaaaaa', '99eecc');
+        $num = count($datasets) ;
+        $colstr = array();
+        for ($i = 0; $i < $num; $i++) {
+            $colstr[] = 'b' . ',' . $colors[$i] . ',' . ($i) . ',' . ($i+1) . ',0';
+        }
+        return '&chm=' . join('|', $colstr);
+    }
+
+    /**
+     * Generate a Google Charts URL which points to a generated image.
+     * More documentation on Google Charts here: 
+     *   http://code.google.com/apis/chart/
+     *
+     * @param $axis        Axis
+     * @param $axpis       Axis positions
+     * @param $datasets    Datasets values
+     * @param $max         Max value. Will be the topmost value on the Y-axis.
+     */
+    public function show($axis, $axispos, $datasets, $maxes)
+    {
+        $labeld = '&chxt=x,y' . '&chxr=0,0,1|1,0,' . $maxes[0];
+        if (count($datasets) > 1) {
+            if (count($datasets) !== count($maxes)) {
+                throw new Exception('Incorrect number of max calculations for graph plotting.');
+            }
+            $labeld = '&chxt=x,y,r' . '&chxr=0,0,1|1,0,' . $maxes[0] . '|2,0,' . $maxes[1];
+        }
+
+        $url = 'http://chart.apis.google.com/chart?' .
+            // Dimension of graph. Default is 800x350
+            'chs=' . $this->x . 'x' . $this->y . 
+
+            // Dateset values
+            '&chd=' . $this->encodedata($datasets) .
+
+            // Fill area...
+            '&chco=ff5c00,cca600' . 
+            '&chls=1,1,0|1,6,3' .
+
+            // chart type is linechart
+            '&cht=lc' .
+            $labeld .
+            '&chxl=0:|' . $this->encodeaxis($axis) . # . $'|1:||top' .
+            '&chxp=0,' . join(',', $axispos) .
+            '&chg=' . (2400/(count($datasets[0])-1)) . ',-1,3,3';   // lines
+
+        return $url;
+    }
+
+    public function showPie($axis, $datasets)
+    {
+        $url = 'http://chart.apis.google.com/chart?' .
+
+        // Dimension of graph. Default is 800x350
+        'chs=' . $this->x . 'x' . $this->y . 
+
+        // Dateset values.
+        '&chd=' . $this->encodedata(array($datasets)) .
+
+        // chart type is linechart
+        '&cht=p' .
+
+        '&chl=' . $this->encodeaxis($axis);
+
+        return $url;
+    }
+
+    /**
+     * Takes a input value, and generates a value that suits better as a max
+     * value on the Y-axis. In example 37.6 will not make a good max value, instead
+     * it will return 40. It will always return an equal or larger number than it gets
+     * as input.
+     *
+     * Here is some test code:
+     * <code>
+     * 	    $foo = array(0, 2, 2.3, 2.6, 6, 10, 15, 98, 198, 256, 487, 563, 763, 801, 899, 999, 987, 198234.485, 283746);
+     *	    foreach ($foo AS $f) {
+     *	        echo '<p>' . $f . ' => ' . sspmod_statistics_Graph_GoogleCharts::roof($f);
+     *	    }
+     * </code>
+     * 
+     * @param $max    Input value.
+     */
+    public static function roof($max)
+    {
+        $mul = 1;
+
+        if ($max < 1) {
+            return 1;
+        }
+
+        $t = intval(ceil($max));
+        while ($t > 100) {
+            $t /= 10;
+            $mul *= 10;
+        }
+
+        $maxGridLines = 10;
+        $candidates = array(1, 2, 5, 10, 20, 25, 50, 100);
+
+        foreach ($candidates as $c) {
+            if ($t / $c < $maxGridLines) {
+                $tick_y = $c * $mul;
+                $target_top = intval(ceil($max / $tick_y) * $tick_y);
+                return $target_top;
+            }
+        }
+    }
 }
diff --git a/modules/statistics/lib/LogCleaner.php b/modules/statistics/lib/LogCleaner.php
index 9b6646495129f20e6772b0518c69f05688f70874..a39d83fb6c5ca750033f0fae2afc3f05e33ecccc 100644
--- a/modules/statistics/lib/LogCleaner.php
+++ b/modules/statistics/lib/LogCleaner.php
@@ -3,165 +3,165 @@
  * @author Andreas Ã…kre Solberg <andreas.solberg@uninett.no>
  * @package SimpleSAMLphp
  */
-class sspmod_statistics_LogCleaner {
-
-	private $statconfig;
-	private $statdir;
-	private $inputfile;
-	private $statrules;
-	private $offset;
-
-	/**
-	 * Constructor
-	 */
-	public function __construct($inputfile = NULL) {
-	
-		$this->statconfig = SimpleSAML_Configuration::getConfig('module_statistics.php');
-		
-		$this->statdir = $this->statconfig->getValue('statdir');
-		$this->inputfile = $this->statconfig->getValue('inputfile');
-		$this->statrules = $this->statconfig->getValue('statrules');
-		$this->offset = $this->statconfig->getValue('offset', 0);
-		
-		if (isset($inputfile)) $this->inputfile = $inputfile;
-	}
-	
-	public function dumpConfig() {
-		
-		echo 'Statistics directory   : ' . $this->statdir . "\n";
-		echo 'Input file             : ' . $this->inputfile . "\n";
-		echo 'Offset                 : ' . $this->offset . "\n";
-		
-	}
-	
-
-
-	public function clean($debug = FALSE) {
-		
-		if (!is_dir($this->statdir)) 
-			throw new Exception('Statistics module: output dir do not exists [' . $this->statdir . ']');
-		
-		if (!file_exists($this->inputfile)) 
-			throw new Exception('Statistics module: input file do not exists [' . $this->inputfile . ']');
-		
-		
-		$file = fopen($this->inputfile, 'r');
-
-		$logparser = new sspmod_statistics_LogParser(
-			$this->statconfig->getValue('datestart', 0), $this->statconfig->getValue('datelength', 15), $this->statconfig->getValue('offsetspan', 44)
-		);
-		$datehandler = new sspmod_statistics_DateHandler($this->offset);
-		
-		$results = array();
-		
-		$sessioncounter = array();
-		
-		$i = 0;
-		// Parse through log file, line by line
-		while (!feof($file)) {
-			
-			$logline = fgets($file, 4096);
-			
-			// Continue if STAT is not found on line
-			if (!preg_match('/STAT/', $logline)) continue;
-			$i++;
-			
-			// Parse log, and extract epoch time and rest of content.
-			$epoch = $logparser->parseEpoch($logline);
-			$content = $logparser->parseContent($logline);
-			$action = trim($content[5]);
-
-			if (($i % 10000) == 0) {
-				echo("Read line " . $i . "\n");
-			}
-			
-			$trackid = $content[4];
-			
-			if(!isset($sessioncounter[$trackid])) $sessioncounter[$trackid] = 0;
-			$sessioncounter[$trackid]++;
-
-			if ($debug) {
-			
-				echo("----------------------------------------\n");
-				echo('Log line: ' . $logline . "\n");
-				echo('Date parse [' . substr($logline, 0, $this->statconfig->getValue('datelength', 15)) . '] to [' . date(DATE_RFC822, $epoch) . ']' . "\n");
-				echo htmlentities(print_r($content, true));
-				if ($i >= 13) exit;
-			}
-
-		}
-
-		$histogram = array();
-		foreach($sessioncounter AS $trackid => $sc) {
-			if(!isset($histogram[$sc])) $histogram[$sc] = 0;
-			$histogram[$sc]++;
-		}
-		ksort($histogram);
-		
-		$todelete = array();
-		foreach($sessioncounter AS $trackid => $sc) {
-			if($sc > 200) $todelete[] = $trackid;
-		}
-
-		return $todelete;
-	}
-	
-	
-	public function store($todelete, $outputfile) {
-		
-		echo "Preparing to delete [" .count($todelete) . "] trackids\n";
-		
-		if (!is_dir($this->statdir)) 
-			throw new Exception('Statistics module: output dir do not exists [' . $this->statdir . ']');
-		
-		if (!file_exists($this->inputfile)) 
-			throw new Exception('Statistics module: input file do not exists [' . $this->inputfile . ']');
-		
-		$file = fopen($this->inputfile, 'r');
-
-		// Open the output file in a way that guarantees that we will not overwrite a random file.
-		if (file_exists($outputfile)) {
-			// Delete existing output file.
-			unlink($outputfile);
-		}
-		$outfile = fopen($outputfile, 'x'); /* Create the output file. */
-
-		
-		$logparser = new sspmod_statistics_LogParser(
-			$this->statconfig->getValue('datestart', 0), $this->statconfig->getValue('datelength', 15), $this->statconfig->getValue('offsetspan', 44)
-		);
-
-		$i = 0;
-		// Parse through log file, line by line
-		while (!feof($file)) {
-			
-			$logline = fgets($file, 4096);
-			
-			// Continue if STAT is not found on line.
-			if (!preg_match('/STAT/', $logline)) continue;
-			$i++;
-			
-			$content = $logparser->parseContent($logline);
-			
-			$action = trim($content[5]);
-
-			if (($i % 10000) == 0) {
-				echo("Read line " . $i . "\n");
-			}
-			
-			$trackid = $content[4];
-			
-			if (in_array($trackid, $todelete)) {
-				continue;
-			}
-			
-			fputs($outfile, $logline);
-
-		}
-		fclose($file);
-		fclose($outfile);
-		
-	}
-
-
+class sspmod_statistics_LogCleaner
+{
+    private $statconfig;
+    private $statdir;
+    private $inputfile;
+    private $statrules;
+    private $offset;
+
+    /**
+     * Constructor
+     */
+    public function __construct($inputfile = null)
+    {
+        $this->statconfig = SimpleSAML_Configuration::getConfig('module_statistics.php');
+
+        $this->statdir = $this->statconfig->getValue('statdir');
+        $this->inputfile = $this->statconfig->getValue('inputfile');
+        $this->statrules = $this->statconfig->getValue('statrules');
+        $this->offset = $this->statconfig->getValue('offset', 0);
+
+        if (isset($inputfile)) {
+            $this->inputfile = $inputfile;
+        }
+    }
+
+    public function dumpConfig()
+    {
+        echo 'Statistics directory   : ' . $this->statdir . "\n";
+        echo 'Input file             : ' . $this->inputfile . "\n";
+        echo 'Offset                 : ' . $this->offset . "\n";
+    }
+
+    public function clean($debug = false) {
+        if (!is_dir($this->statdir)) {
+            throw new Exception('Statistics module: output dir do not exists [' . $this->statdir . ']');
+        }
+
+        if (!file_exists($this->inputfile)) {
+            throw new Exception('Statistics module: input file do not exists [' . $this->inputfile . ']');
+        }
+
+        $file = fopen($this->inputfile, 'r');
+
+        $logparser = new sspmod_statistics_LogParser(
+            $this->statconfig->getValue('datestart', 0), $this->statconfig->getValue('datelength', 15), $this->statconfig->getValue('offsetspan', 44)
+        );
+        $datehandler = new sspmod_statistics_DateHandler($this->offset);
+
+        $results = array();
+        $sessioncounter = array();
+
+        $i = 0;
+        // Parse through log file, line by line
+        while (!feof($file)) {
+            $logline = fgets($file, 4096);
+
+            // Continue if STAT is not found on line
+            if (!preg_match('/STAT/', $logline)) {
+                continue;
+            }
+            $i++;
+
+            // Parse log, and extract epoch time and rest of content.
+            $epoch = $logparser->parseEpoch($logline);
+            $content = $logparser->parseContent($logline);
+            $action = trim($content[5]);
+
+            if (($i % 10000) == 0) {
+                echo("Read line " . $i . "\n");
+            }
+
+            $trackid = $content[4];
+
+            if (!isset($sessioncounter[$trackid])) {
+                $sessioncounter[$trackid] = 0;
+            }
+            $sessioncounter[$trackid]++;
+
+            if ($debug) {
+                echo("----------------------------------------\n");
+                echo('Log line: ' . $logline . "\n");
+                echo('Date parse [' . substr($logline, 0, $this->statconfig->getValue('datelength', 15)) . '] to [' . date(DATE_RFC822, $epoch) . ']' . "\n");
+                echo htmlentities(print_r($content, true));
+                if ($i >= 13) {
+                    exit;
+                }
+            }
+        }
+
+        $histogram = array();
+        foreach ($sessioncounter as $trackid => $sc) {
+            if (!isset($histogram[$sc])) {
+                $histogram[$sc] = 0;
+            }
+            $histogram[$sc]++;
+        }
+        ksort($histogram);
+
+        $todelete = array();
+        foreach ($sessioncounter as $trackid => $sc) {
+            if ($sc > 200) {
+                $todelete[] = $trackid;
+            }
+        }
+
+        return $todelete;
+    }
+
+    public function store($todelete, $outputfile)
+    {
+        echo "Preparing to delete [" .count($todelete) . "] trackids\n";
+
+        if (!is_dir($this->statdir)) {
+            throw new Exception('Statistics module: output dir do not exists [' . $this->statdir . ']');
+        }
+
+        if (!file_exists($this->inputfile)) {
+            throw new Exception('Statistics module: input file do not exists [' . $this->inputfile . ']');
+        }
+
+        $file = fopen($this->inputfile, 'r');
+
+        // Open the output file in a way that guarantees that we will not overwrite a random file.
+        if (file_exists($outputfile)) {
+            // Delete existing output file.
+            unlink($outputfile);
+        }
+        $outfile = fopen($outputfile, 'x'); /* Create the output file. */
+
+        $logparser = new sspmod_statistics_LogParser(
+            $this->statconfig->getValue('datestart', 0), $this->statconfig->getValue('datelength', 15), $this->statconfig->getValue('offsetspan', 44)
+        );
+
+        $i = 0;
+        // Parse through log file, line by line
+        while (!feof($file)) {
+            $logline = fgets($file, 4096);
+
+            // Continue if STAT is not found on line.
+            if (!preg_match('/STAT/', $logline)) {
+                continue;
+            }
+            $i++;
+
+            $content = $logparser->parseContent($logline);
+            $action = trim($content[5]);
+
+            if (($i % 10000) == 0) {
+                echo("Read line " . $i . "\n");
+            }
+
+            $trackid = $content[4];
+            if (in_array($trackid, $todelete)) {
+                continue;
+            }
+
+            fputs($outfile, $logline);
+        }
+        fclose($file);
+        fclose($outfile);
+    }
 }
diff --git a/modules/statistics/lib/LogParser.php b/modules/statistics/lib/LogParser.php
index d1d5d203ce1c32ba9eab598e45aaf76e0c82897a..6ca677a38c5b2d624141c9105fa7746bf8aaa825 100644
--- a/modules/statistics/lib/LogParser.php
+++ b/modules/statistics/lib/LogParser.php
@@ -3,65 +3,64 @@
  * @author Andreas Ã…kre Solberg <andreas.solberg@uninett.no>
  * @package SimpleSAMLphp
  */
-class sspmod_statistics_LogParser {
+class sspmod_statistics_LogParser
+{
+    private $datestart;
+    private $datelength;
+    private $offset;
 
-	private $datestart;
-	private $datelength;
-	private $offset;
+    /**
+     * Constructor
+     *
+     * @param $datestart   At which char is the date starting
+     * @param $datelength  How many characters is the date (on the b
+     * @param $offset      At which char is the rest of the entries starting
+     */
+    public function __construct($datestart, $datelength, $offset)
+    {
+        $this->datestart = $datestart;
+        $this->datelength = $datelength;
+        $this->offset = $offset;
+    }
 
-	/**
-	 * Constructor
-	 *
-	 * @param $datestart   At which char is the date starting
-	 * @param $datelength  How many characters is the date (on the b
-	 * @param $offset      At which char is the rest of the entries starting
-	 */
-	public function __construct($datestart, $datelength, $offset) {
-		$this->datestart = $datestart;
-		$this->datelength = $datelength;
-		$this->offset = $offset;
-	}
+    public function parseEpoch($line)
+    {
+        $epoch = strtotime(substr($line, 0, $this->datelength));
+        if ($epoch > time() + 2678400) {  // 60 * 60 *24 * 31 = 2678400
+            /*
+             * More than a month in the future - probably caused by
+             * the log files missing the year.
+             * We will therefore subtrackt one year.
+             */
+            $hour = gmdate('H', $epoch);
+            $minute = gmdate('i', $epoch);
+            $second = gmdate('s', $epoch);
+            $month = gmdate('n', $epoch);
+            $day = gmdate('j', $epoch);
+            $year = gmdate('Y', $epoch) - 1;
+            $epoch = gmmktime($hour, $minute, $second, $month, $day, $year);
+        }
+        return $epoch;
+    }
 
-	public function parseEpoch($line) {
-		$epoch = strtotime(substr($line, 0, $this->datelength));
-		if ($epoch > time() + 60*60*24*31) {
-			/*
-			 * More than a month in the future - probably caused by
-			 * the log files missing the year.
-			 * We will therefore subtrackt one year.
-			 */
-			$hour = gmdate('H', $epoch);
-			$minute = gmdate('i', $epoch);
-			$second = gmdate('s', $epoch);
-			$month = gmdate('n', $epoch);
-			$day = gmdate('j', $epoch);
-			$year = gmdate('Y', $epoch) - 1;
-			$epoch = gmmktime($hour, $minute, $second, $month, $day, $year);
-		}
-		return $epoch;
-	}
+    public function parseContent($line) {
+        $contentstr = substr($line, $this->offset);
+        $content = explode(' ', $contentstr);
+        return $content;
+    }
 
-	public function parseContent($line) {
-		$contentstr = substr($line, $this->offset);
-		$content = explode(' ', $contentstr);
-		return $content;
-	}
-	
-	
-	# Aug 27 12:54:25 ssp 5 STAT [5416262207] saml20-sp-SSO urn:mace:feide.no:services:no.uninett.wiki-feide sam.feide.no NA
-	# 
-	#Oct 30 11:07:14 www1 simplesamlphp-foodle[12677]: 5 STAT [200b4679af] saml20-sp-SLO spinit urn:mace:feide.no:services:no.feide.foodle sam.feide.no
-	
-	function parse15($str) {
-		$di = date_parse($str);
-		$datestamp = mktime($di['hour'], $di['minute'], $di['second'], $di['month'], $di['day']);	
-		return $datestamp;
-	}
-	
-	function parse23($str) {
-		$timestamp = strtotime($str);
-		return $timestamp;
-	}
+    // Aug 27 12:54:25 ssp 5 STAT [5416262207] saml20-sp-SSO urn:mace:feide.no:services:no.uninett.wiki-feide sam.feide.no NA
+    // 
+    // Oct 30 11:07:14 www1 simplesamlphp-foodle[12677]: 5 STAT [200b4679af] saml20-sp-SLO spinit urn:mace:feide.no:services:no.feide.foodle sam.feide.no
 
+    function parse15($str) {
+        $di = date_parse($str);
+        $datestamp = mktime($di['hour'], $di['minute'], $di['second'], $di['month'], $di['day']);	
+        return $datestamp;
+    }
 
+    function parse23($str) {
+        $timestamp = strtotime($str);
+        return $timestamp;
+    }
 }
diff --git a/modules/statistics/lib/RatioDataset.php b/modules/statistics/lib/RatioDataset.php
index 7cb82c8749ab262102dcdec3510701be9bcf2d00..a88573c65697df752766edc1fa8bf1c42e8dd570 100644
--- a/modules/statistics/lib/RatioDataset.php
+++ b/modules/statistics/lib/RatioDataset.php
@@ -3,69 +3,76 @@
  * @author Andreas Ã…kre Solberg <andreas.solberg@uninett.no>
  * @package SimpleSAMLphp
  */
-class sspmod_statistics_RatioDataset extends sspmod_statistics_StatDataset {
+class sspmod_statistics_RatioDataset extends sspmod_statistics_StatDataset
+{
+    public function aggregateSummary()
+    {
+        /**
+         * Aggregate summary table from dataset. To be used in the table view.
+         */
+        $this->summary = array(); 
+        $noofvalues = array();
+        foreach ($this->results as $slot => $res) {
+            foreach ($res AS $key => $value) {
+                if (array_key_exists($key, $this->summary)) {
+                    $this->summary[$key] += $value;
+                    if ($value > 0) {
+                        $noofvalues[$key]++;
+                    }
+                } else {
+                    $this->summary[$key] = $value;
+                    if ($value > 0) {
+                        $noofvalues[$key] = 1;
+                    } else {
+                        $noofvalues[$key] = 0;
+                    }
+                }
+            }
+        }
 
-	
-	public function aggregateSummary() {
-		/**
-		 * Aggregate summary table from dataset. To be used in the table view.
-		 */
-		$this->summary = array(); 
-		$noofvalues = array();
-		foreach($this->results AS $slot => $res) {
-			foreach ($res AS $key => $value) {
-				if (array_key_exists($key, $this->summary)) {
-					$this->summary[$key] += $value;
-					if ($value > 0) 
-						$noofvalues[$key]++;
-				} else {
-					$this->summary[$key] = $value;
-					if ($value > 0) 
-						$noofvalues[$key] = 1;
-					else 
-						$noofvalues[$key] = 0;
-				}
-			}
-		}
-		
-		foreach($this->summary AS $key => $val) {
-			$this->summary[$key] = $this->divide($this->summary[$key], $noofvalues[$key]);
-		}
-		
-		asort($this->summary);
-		$this->summary = array_reverse($this->summary, TRUE);
-	}
-	
-	private function ag($k, $a) {
-		if (array_key_exists($k, $a)) return $a[$k];
-		return 0;
-	}
-	
-	private function divide($v1, $v2) {
-		if ($v2 == 0) return 0;
-		return ($v1 / $v2);
-	}
-	
-	public function combine($result1, $result2) {
+        foreach ($this->summary as $key => $val) {
+            $this->summary[$key] = $this->divide($this->summary[$key], $noofvalues[$key]);
+        }
 
-		$combined = array();
-		
-		foreach($result2 AS $tick => $val) {
-			$combined[$tick] = array();
-			foreach($val AS $index => $num) {
-				$combined[$tick][$index] = $this->divide( 
-					$this->ag($index, $result1[$tick]),
-					$this->ag($index, $result2[$tick])
-				);
-			}
-			
-		}
-		return $combined;
-	}
-	
-	public function getPieData() {
-		return NULL;
-	}
+        asort($this->summary);
+        $this->summary = array_reverse($this->summary, true);
+    }
 
+    private function ag($k, $a)
+    {
+        if (array_key_exists($k, $a)) {
+            return $a[$k];
+        }
+        return 0;
+    }
+
+    private function divide($v1, $v2)
+    {
+        if ($v2 == 0) {
+            return 0;
+        }
+        return ($v1 / $v2);
+    }
+
+    public function combine($result1, $result2)
+    {
+        $combined = array();
+
+        foreach ($result2 as $tick => $val) {
+            $combined[$tick] = array();
+            foreach ($val as $index => $num) {
+                $combined[$tick][$index] = $this->divide( 
+                    $this->ag($index, $result1[$tick]),
+                    $this->ag($index, $result2[$tick])
+                );
+            }
+        }
+        return $combined;
+    }
+
+    public function getPieData()
+    {
+        return null;
+    }
 }
 
diff --git a/modules/statistics/lib/Ruleset.php b/modules/statistics/lib/Ruleset.php
index 1d3231064d6cd40ff068ae950e74fd026e5ec939..643a118e50cf50c58a1097a9602999690ce4ecae 100644
--- a/modules/statistics/lib/Ruleset.php
+++ b/modules/statistics/lib/Ruleset.php
@@ -3,88 +3,93 @@
  * @author Andreas Ã…kre Solberg <andreas.solberg@uninett.no>
  * @package SimpleSAMLphp
  */
-class sspmod_statistics_Ruleset {
+class sspmod_statistics_Ruleset
+{
+    private $statconfig;
+    private $availrulenames;
+    private $availrules;
+    private $available;
 
-	private $statconfig;
-	private $availrulenames;
-	private $availrules;
-	private $available;
+    /**
+     * Constructor
+     */
+    public function __construct($statconfig)
+    {
+        $this->statconfig = $statconfig;
+        $this->init();
+    }
 
-	/**
-	 * Constructor
-	 */
-	public function __construct($statconfig) {
-		$this->statconfig = $statconfig;
-		$this->init();
-	}
+    private function init()
+    {
+        $statdir = $this->statconfig->getValue('statdir');
+        $inputfile = $this->statconfig->getValue('inputfile');
+        $statrules = $this->statconfig->getValue('statrules');
+        $timeres = $this->statconfig->getValue('timeres');
 
-	private function init() {
-		
-		$statdir = $this->statconfig->getValue('statdir');
-		$inputfile = $this->statconfig->getValue('inputfile');
-		$statrules = $this->statconfig->getValue('statrules');
-		$timeres = $this->statconfig->getValue('timeres');
+        /*
+         * Walk through file lists, and get available [rule][fileslot]...
+         */
+        if (!is_dir($statdir)) {
+            throw new Exception('Statisics output directory [' . $statdir . '] does not exists.');
+        }
+        $filelist = scandir($statdir);
+        $this->available = array();
+        foreach ($filelist as $file) {
+        if (preg_match('/([a-z0-9_]+)-([a-z0-9_]+)-([0-9]+)\.stat/', $file, $matches)) {
+            if (array_key_exists($matches[1], $statrules)) {
+                if (array_key_exists($matches[2], $timeres)) 
+                    $this->available[$matches[1]][$matches[2]][] = $matches[3];
+                }
+            }
+        }
+        if (empty($this->available)) {
+            throw new Exception('No aggregated statistics files found in [' . $statdir . ']');
+        }
 
-		/*
-		 * Walk through file lists, and get available [rule][fileslot]...
-		 */
-		if (!is_dir($statdir))
-			throw new Exception('Statisics output directory [' . $statdir . '] does not exists.');
-		$filelist = scandir($statdir);
-		$this->available = array();
-		foreach ($filelist AS $file) {
-			if (preg_match('/([a-z0-9_]+)-([a-z0-9_]+)-([0-9]+)\.stat/', $file, $matches)) {
-				if (array_key_exists($matches[1], $statrules)) {
-					if (array_key_exists($matches[2], $timeres)) 
-						$this->available[$matches[1]][$matches[2]][] = $matches[3];
-				}
-			}
-		}
-		if (empty($this->available)) 
-			throw new Exception('No aggregated statistics files found in [' . $statdir . ']');
+        /*
+         * Create array with information about available rules..
+         */
+        $this->availrules = array_keys($statrules);
+        $available_rules = array();
+        foreach ($this->availrules as $key) {
+            $available_rules[$key] = array('name' => $statrules[$key]['name'], 'descr' => $statrules[$key]['descr']);
+        }
+        $this->availrulenames = $available_rules;
+    }
 
-		/*
-		 * Create array with information about available rules..
-		 */
-		$this->availrules = array_keys($statrules);
-		$available_rules = array();
-		foreach ($this->availrules AS $key) {
-			$available_rules[$key] = array('name' => $statrules[$key]['name'], 'descr' => $statrules[$key]['descr']);
-		}
-		$this->availrulenames = $available_rules;
-		
-	}
-	
-	public function availableRules() {
-		return $this->availrules;
-	}
-	
-	public function availableRulesNames() {
-		return $this->availrulenames;
-	}
-	
-	/**
-	 * Resolve which rule is selected. Taking user preference and checks if it exists.
-	 */
-	private function resolveSelectedRule($preferRule = NULL) {
-		$rule = $this->statconfig->getString('default', $this->availrules[0]);
-		if(!empty($preferRule)) {
-			if (in_array($preferRule, $this->availrules)) {
-				$rule = $preferRule;
-			}
-		}
-		return $rule;
-	}
-	
-	public function getRule($preferRule) {
-		$rule = $this->resolveSelectedRule($preferRule);
-		$statrulesConfig = $this->statconfig->getConfigItem('statrules');
-		$statruleConfig = $statrulesConfig->getConfigItem($rule);
-		
-		$presenterClass = SimpleSAML\Module::resolveClass($statruleConfig->getValue('presenter', 'statistics:BaseRule'), 'Statistics_Rulesets');
-		$statrule = new $presenterClass($this->statconfig, $statruleConfig, $rule, $this->available);
-		return $statrule;
-	}
+    public function availableRules()
+    {
+        return $this->availrules;
+    }
 
+    public function availableRulesNames()
+    {
+        return $this->availrulenames;
+    }
+
+    /**
+     * Resolve which rule is selected. Taking user preference and checks if it exists.
+     */
+    private function resolveSelectedRule($preferRule = null)
+    {
+        $rule = $this->statconfig->getString('default', $this->availrules[0]);
+        if (!empty($preferRule)) {
+            if (in_array($preferRule, $this->availrules)) {
+                $rule = $preferRule;
+            }
+        }
+        return $rule;
+    }
+
+    public function getRule($preferRule)
+    {
+        $rule = $this->resolveSelectedRule($preferRule);
+        $statrulesConfig = $this->statconfig->getConfigItem('statrules');
+        $statruleConfig = $statrulesConfig->getConfigItem($rule);
+
+        $presenterClass = SimpleSAML\Module::resolveClass($statruleConfig->getValue('presenter', 'statistics:BaseRule'), 'Statistics_Rulesets');
+        $statrule = new $presenterClass($this->statconfig, $statruleConfig, $rule, $this->available);
+        return $statrule;
+    }
 }
 
diff --git a/modules/statistics/lib/StatDataset.php b/modules/statistics/lib/StatDataset.php
index 1c4067196d9d13fc6f3dfd7a56f5587b5db618b8..79eceeecafe5ae5ab24b03e154a3e25c093846d1 100644
--- a/modules/statistics/lib/StatDataset.php
+++ b/modules/statistics/lib/StatDataset.php
@@ -7,7 +7,6 @@
  */
 class sspmod_statistics_StatDataset
 {
-
     protected $statconfig;
     protected $ruleconfig;
     protected $timeresconfig;
@@ -55,19 +54,16 @@ class sspmod_statistics_StatDataset
         $this->loadData();
     }
 
-
     public function getFileSlot()
     {
         return $this->fileslot;
     }
 
-
     public function getTimeRes()
     {
         return $this->timeres;
     }
 
-
     public function setDelimiter($delimiter = '_')
     {
         if (empty($delimiter)) {
@@ -76,7 +72,6 @@ class sspmod_statistics_StatDataset
         $this->delimiter = $delimiter;
     }
 
-
     public function getDelimiter()
     {
         if ($this->delimiter === '_') {
@@ -85,7 +80,6 @@ class sspmod_statistics_StatDataset
         return $this->delimiter;
     }
 
-
     public function calculateMax()
     {
         $maxvalue = 0;
@@ -98,7 +92,6 @@ class sspmod_statistics_StatDataset
         $this->max = sspmod_statistics_Graph_GoogleCharts::roof($maxvalue);
     }
 
-
     public function getDebugData()
     {
         $debugdata = array();
@@ -115,7 +108,6 @@ class sspmod_statistics_StatDataset
         return $debugdata;
     }
 
-
     public function aggregateSummary()
     {
         // aggregate summary table from dataset. To be used in the table view
@@ -133,7 +125,6 @@ class sspmod_statistics_StatDataset
         $this->summary = array_reverse($this->summary, true);
     }
 
-
     public function getTopDelimiters()
     {
         // create a list of delimiter keys that has the highest total summary in this period
@@ -151,7 +142,6 @@ class sspmod_statistics_StatDataset
         return $topdelimiters;
     }
 
-
     public function availDelimiters()
     {
         $availDelimiters = array();
@@ -161,7 +151,6 @@ class sspmod_statistics_StatDataset
         return array_keys($availDelimiters);
     }
 
-
     public function getPieData()
     {
         $piedata = array();
@@ -176,25 +165,21 @@ class sspmod_statistics_StatDataset
         return $piedata;
     }
 
-
     public function getMax()
     {
         return $this->max;
     }
 
-
     public function getSummary()
     {
         return $this->summary;
     }
 
-
     public function getResults()
     {
         return $this->results;
     }
 
-
     public function getAxis()
     {
         $slotsize = $this->timeresconfig->getValue('slot');
@@ -223,7 +208,6 @@ class sspmod_statistics_StatDataset
         return array('axis' => $axis, 'axispos' => $axispos);
     }
 
-
     /*
      * Walk through dataset to get percent values from max into dataset[].
      */
@@ -247,7 +231,6 @@ class sspmod_statistics_StatDataset
         return $dataset;
     }
 
-
     public function getDelimiterPresentation()
     {
         $config = SimpleSAML_Configuration::getInstance();
@@ -273,7 +256,6 @@ class sspmod_statistics_StatDataset
         return array();
     }
 
-
     public function getDelimiterPresentationPie()
     {
         $topdelimiters = $this->getTopDelimiters();
@@ -291,7 +273,6 @@ class sspmod_statistics_StatDataset
         return $pieaxis;
     }
 
-
     public function loadData()
     {
         $statdir = $this->statconfig->getValue('statdir');
diff --git a/modules/statistics/lib/Statistics/FieldPresentation/Base.php b/modules/statistics/lib/Statistics/FieldPresentation/Base.php
index 18eb0bdecc17238b4fe1518388bd1aa29d5f8abd..e02300094bccf70260f34f27eafac8b0382c443a 100644
--- a/modules/statistics/lib/Statistics/FieldPresentation/Base.php
+++ b/modules/statistics/lib/Statistics/FieldPresentation/Base.php
@@ -1,20 +1,20 @@
 <?php
 
-class sspmod_statistics_Statistics_FieldPresentation_Base {
-	
-	protected $fields;
-	protected $template;
-	protected $config;
-	
-	function __construct($fields, $config, $template) {
-		$this->fields = $fields;
-		$this->template = $template;
-		$this->config = $config;
-	}
-	
-	public function getPresentation() {
-		return array('_' => 'Total');
-	}
-	
-	
-}
\ No newline at end of file
+class sspmod_statistics_Statistics_FieldPresentation_Base
+{
+    protected $fields;
+    protected $template;
+    protected $config;
+
+    function __construct($fields, $config, $template)
+    {
+        $this->fields = $fields;
+        $this->template = $template;
+        $this->config = $config;
+    }
+
+    public function getPresentation()
+    {
+        return array('_' => 'Total');
+    }
+}
diff --git a/modules/statistics/lib/Statistics/FieldPresentation/Entity.php b/modules/statistics/lib/Statistics/FieldPresentation/Entity.php
index 6d1bf9d6878038ce3866adc7048532be1bcedf0e..5556d08f819e68ac0d05354adda58c26b506924c 100644
--- a/modules/statistics/lib/Statistics/FieldPresentation/Entity.php
+++ b/modules/statistics/lib/Statistics/FieldPresentation/Entity.php
@@ -1,23 +1,20 @@
 <?php
 
-class sspmod_statistics_Statistics_FieldPresentation_Entity extends sspmod_statistics_Statistics_FieldPresentation_Base {
-	
-	
-	public function getPresentation() {
-		$mh = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
-		$metadata = $mh->getList($this->config);
-		
-		$translation = array('_' => 'All services');
-		foreach($this->fields AS $field) {
-			if (array_key_exists($field, $metadata)) {
-				if (array_key_exists('name', $metadata[$field])) {
-					$translation[$field] = $this->template->t($metadata[$field]['name'], array(), FALSE);
-				}
-			}
-		}
-		return $translation;
-	}
+class sspmod_statistics_Statistics_FieldPresentation_Entity extends sspmod_statistics_Statistics_FieldPresentation_Base
+{
+    public function getPresentation()
+    {
+        $mh = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+        $metadata = $mh->getList($this->config);
 
-	
-	
-}
\ No newline at end of file
+        $translation = array('_' => 'All services');
+        foreach ($this->fields as $field) {
+            if (array_key_exists($field, $metadata)) {
+                if (array_key_exists('name', $metadata[$field])) {
+                    $translation[$field] = $this->template->t($metadata[$field]['name'], array(), false);
+                }
+            }
+        }
+        return $translation;
+    }
+}
diff --git a/modules/statistics/lib/Statistics/Rulesets/BaseRule.php b/modules/statistics/lib/Statistics/Rulesets/BaseRule.php
index 66b8da5f3236fa9f6e3641d1e4db9f269651249b..865cd2782c6af5d3e8977a73c19ad7579d483163 100644
--- a/modules/statistics/lib/Statistics/Rulesets/BaseRule.php
+++ b/modules/statistics/lib/Statistics/Rulesets/BaseRule.php
@@ -3,114 +3,117 @@
  * @author Andreas Ã…kre Solberg <andreas.solberg@uninett.no>
  * @package SimpleSAMLphp
  */
-class sspmod_statistics_Statistics_Rulesets_BaseRule {
-
-	protected $statconfig;
-	protected $ruleconfig;
-	protected $ruleid;
-	protected $available;
-
-	/**
-	 * Constructor
-	 */
-	public function __construct($statconfig, $ruleconfig, $ruleid, $available) {
-		assert('$statconfig instanceof SimpleSAML_Configuration');
-		assert('$ruleconfig instanceof SimpleSAML_Configuration');
-		$this->statconfig = $statconfig;
-		$this->ruleconfig = $ruleconfig;
-		$this->ruleid = $ruleid;
-		
-		$this->available = NULL;
-		if (array_key_exists($ruleid, $available)) $this->available = $available[$ruleid];	
-	}
-	
-	public function getRuleID() {
-		return $this->ruleid;
-	}
-	
-	public function init() {
-
-	}
-	
-	public function availableTimeRes() {
-		$timeresConfigs = $this->statconfig->getValue('timeres');
-		$available_times = array(); 
-		foreach ($timeresConfigs AS $tres => $tresconfig) {
-			if (array_key_exists($tres, $this->available))
-				$available_times[$tres] = $tresconfig['name'];
-		}
-		return $available_times;
-	}
-
-	
-	public function availableFileSlots($timeres) {
-		$timeresConfigs = $this->statconfig->getValue('timeres');
-		$timeresConfig = $timeresConfigs[$timeres];
-		
-		if (isset($timeresConfig['customDateHandler']) && $timeresConfig['customDateHandler'] == 'month') {
-			$datehandler = new sspmod_statistics_DateHandlerMonth(0);
-		} else {
-			$datehandler = new sspmod_statistics_DateHandler($this->statconfig->getValue('offset', 0));
-		}
-		
-		
-		/*
-		 * Get list of avaiable times in current file (rule)
-		 */
-		$available_times = array(); 
-		foreach ($this->available[$timeres] AS $slot) {
-			$available_times[$slot] = $datehandler->prettyHeader($slot, $slot+1, $timeresConfig['fileslot'], $timeresConfig['dateformat-period']);
-		}
-		return $available_times;
-	}
-
-	protected function resolveTimeRes($preferTimeRes) {
-		$timeresavailable = array_keys($this->available);
-		$timeres = $timeresavailable[0];
-
-		// Then check if the user have provided one that is valid
-		if (in_array($preferTimeRes, $timeresavailable)) {
-			$timeres = $preferTimeRes;
-		}
-		return $timeres;
-	}
-	
-	protected function resolveFileSlot($timeres, $preferTime) {
-
-		// Get which time (fileslot) to use.. First get a default, which is the most recent one.
-		$fileslot = $this->available[$timeres][count($this->available[$timeres])-1];
-		// Then check if the user have provided one.
-		if (in_array($preferTime, $this->available[$timeres])) {
-			$fileslot = $preferTime;
-		}
-		return $fileslot;
-	}
-	
-	
-	public function getTimeNavigation($timeres, $preferTime) {
-		$fileslot = $this->resolveFileSlot($timeres, $preferTime);
-		
-		// Extract previous and next time slots...
-		$available_times_prev = NULL; $available_times_next = NULL;
-
-		$timeslots = array_values($this->available[$timeres]);
-		sort($timeslots, SORT_NUMERIC);
-		$timeslotindex = array_flip($timeslots);
-
-		if ($timeslotindex[$fileslot] > 0) 
-			$available_times_prev = $timeslots[$timeslotindex[$fileslot]-1];
-		if ($timeslotindex[$fileslot] < (count($timeslotindex)-1) ) 
-			$available_times_next = $timeslots[$timeslotindex[$fileslot]+1];
-		return array('prev' => $available_times_prev, 'next' => $available_times_next);
-	}
-	
-	public function getDataSet($preferTimeRes, $preferTime) {
-		$timeres = $this->resolveTimeRes($preferTimeRes);
-		$fileslot = $this->resolveFileSlot($timeres, $preferTime);
-		$dataset = new sspmod_statistics_StatDataset($this->statconfig, $this->ruleconfig, $this->ruleid, $timeres, $fileslot);
-		return $dataset;
-	}
-	
+class sspmod_statistics_Statistics_Rulesets_BaseRule
+{
+    protected $statconfig;
+    protected $ruleconfig;
+    protected $ruleid;
+    protected $available;
 
+    /**
+     * Constructor
+     */
+    public function __construct($statconfig, $ruleconfig, $ruleid, $available)
+    {
+        assert('$statconfig instanceof SimpleSAML_Configuration');
+        assert('$ruleconfig instanceof SimpleSAML_Configuration');
+        $this->statconfig = $statconfig;
+        $this->ruleconfig = $ruleconfig;
+        $this->ruleid = $ruleid;
+
+        $this->available = null;
+        if (array_key_exists($ruleid, $available)) {
+            $this->available = $available[$ruleid];	
+        }
+    }
+
+    public function getRuleID()
+    {
+        return $this->ruleid;
+    }
+
+    public function availableTimeRes() {
+        $timeresConfigs = $this->statconfig->getValue('timeres');
+        $available_times = array(); 
+        foreach ($timeresConfigs as $tres => $tresconfig) {
+            if (array_key_exists($tres, $this->available)) {
+                $available_times[$tres] = $tresconfig['name'];
+            }
+        }
+        return $available_times;
+    }
+
+    public function availableFileSlots($timeres)
+    {
+        $timeresConfigs = $this->statconfig->getValue('timeres');
+        $timeresConfig = $timeresConfigs[$timeres];
+
+        if (isset($timeresConfig['customDateHandler']) && $timeresConfig['customDateHandler'] == 'month') {
+            $datehandler = new sspmod_statistics_DateHandlerMonth(0);
+        } else {
+            $datehandler = new sspmod_statistics_DateHandler($this->statconfig->getValue('offset', 0));
+        }
+
+        /*
+         * Get list of avaiable times in current file (rule)
+         */
+        $available_times = array(); 
+        foreach ($this->available[$timeres] as $slot) {
+            $available_times[$slot] = $datehandler->prettyHeader($slot, $slot + 1, $timeresConfig['fileslot'], $timeresConfig['dateformat-period']);
+        }
+        return $available_times;
+    }
+
+    protected function resolveTimeRes($preferTimeRes)
+    {
+        $timeresavailable = array_keys($this->available);
+        $timeres = $timeresavailable[0];
+
+        // Then check if the user have provided one that is valid
+        if (in_array($preferTimeRes, $timeresavailable)) {
+            $timeres = $preferTimeRes;
+        }
+        return $timeres;
+    }
+
+    protected function resolveFileSlot($timeres, $preferTime)
+    {
+        // Get which time (fileslot) to use.. First get a default, which is the most recent one.
+        $fileslot = $this->available[$timeres][count($this->available[$timeres]) - 1];
+        // Then check if the user have provided one.
+        if (in_array($preferTime, $this->available[$timeres])) {
+            $fileslot = $preferTime;
+        }
+        return $fileslot;
+    }
+
+    public function getTimeNavigation($timeres, $preferTime)
+    {
+        $fileslot = $this->resolveFileSlot($timeres, $preferTime);
+
+        // Extract previous and next time slots...
+        $available_times_prev = null;
+        $available_times_next = null;
+
+        $timeslots = array_values($this->available[$timeres]);
+        sort($timeslots, SORT_NUMERIC);
+        $timeslotindex = array_flip($timeslots);
+
+        if ($timeslotindex[$fileslot] > 0) {
+            $available_times_prev = $timeslots[$timeslotindex[$fileslot] - 1];
+        }
+        if ($timeslotindex[$fileslot] < (count($timeslotindex) - 1)) {
+            $available_times_next = $timeslots[$timeslotindex[$fileslot] + 1];
+        }
+        return array('prev' => $available_times_prev, 'next' => $available_times_next);
+    }
+
+    public function getDataSet($preferTimeRes, $preferTime)
+    {
+        $timeres = $this->resolveTimeRes($preferTimeRes);
+        $fileslot = $this->resolveFileSlot($timeres, $preferTime);
+        $dataset = new sspmod_statistics_StatDataset($this->statconfig, $this->ruleconfig, $this->ruleid, $timeres, $fileslot);
+        return $dataset;
+    }
 }
 
diff --git a/modules/statistics/lib/Statistics/Rulesets/Ratio.php b/modules/statistics/lib/Statistics/Rulesets/Ratio.php
index 8b7ab10bfe0f711ac1bb774ad7205add5c991082..39512d3c5bba6eb78ef0734980016caf8effafb0 100644
--- a/modules/statistics/lib/Statistics/Rulesets/Ratio.php
+++ b/modules/statistics/lib/Statistics/Rulesets/Ratio.php
@@ -3,62 +3,66 @@
  * @author Andreas Ã…kre Solberg <andreas.solberg@uninett.no>
  * @package SimpleSAMLphp
  */
-class sspmod_statistics_Statistics_Rulesets_Ratio extends sspmod_statistics_Statistics_Rulesets_BaseRule {
+class sspmod_statistics_Statistics_Rulesets_Ratio extends sspmod_statistics_Statistics_Rulesets_BaseRule
+{
+    protected $refrule1;
+    protected $refrule2;
 
-	protected $refrule1;
-	protected $refrule2;
-	
-	/**
-	 * Constructor
-	 */
-	public function __construct($statconfig, $ruleconfig, $ruleid, $available) {
-		assert('$statconfig instanceof SimpleSAML_Configuration');
-		assert('$ruleconfig instanceof SimpleSAML_Configuration');
-		
-		parent::__construct($statconfig, $ruleconfig, $ruleid, $available);
-		
-		$refNames = $this->ruleconfig->getArray('ref');
-		
-		$statrulesConfig = $this->statconfig->getConfigItem('statrules');
-		
-		$statruleConfig1 = $statrulesConfig->getConfigItem($refNames[0]);
-		$statruleConfig2 = $statrulesConfig->getConfigItem($refNames[1]);
-		
-		$this->refrule1 = new sspmod_statistics_Statistics_Rulesets_BaseRule($this->statconfig, $statruleConfig1, $refNames[0], $available);
-		$this->refrule2 = new sspmod_statistics_Statistics_Rulesets_BaseRule($this->statconfig, $statruleConfig2, $refNames[1], $available);
-	}
-	
-	public function availableTimeRes() {
-		return $this->refrule1->availableTimeRes();
-	}
-	
-	public function availableFileSlots($timeres) {
-		return $this->refrule1->availableFileSlots($timeres);
-	}
+    /**
+     * Constructor
+     */
+    public function __construct($statconfig, $ruleconfig, $ruleid, $available)
+    {
+        assert('$statconfig instanceof SimpleSAML_Configuration');
+        assert('$ruleconfig instanceof SimpleSAML_Configuration');
 
-	protected function resolveTimeRes($preferTimeRes) {
-		return $this->refrule1->resolveTimeRes($preferTimeRes);
-	}
-	
-	protected function resolveFileSlot($timeres, $preferTime) {
-		return $this->refrule1->resolveFileSlot($timeres, $preferTime);
-	}
-	
-	
-	public function getTimeNavigation($timeres, $preferTime) {
-		return $this->refrule1->getTimeNavigation($timeres, $preferTime);
-	}
-	
-	public function getDataSet($preferTimeRes, $preferTime) {
-		$timeres = $this->resolveTimeRes($preferTimeRes);
-		$fileslot = $this->resolveFileSlot($timeres, $preferTime);
-		
-		$refNames = $this->ruleconfig->getArray('ref');
-		
-		$dataset = new sspmod_statistics_RatioDataset($this->statconfig, $this->ruleconfig, $refNames, $timeres, $fileslot);
-		return $dataset;
-	}
-	
+        parent::__construct($statconfig, $ruleconfig, $ruleid, $available);
 
+        $refNames = $this->ruleconfig->getArray('ref');
+
+        $statrulesConfig = $this->statconfig->getConfigItem('statrules');
+
+        $statruleConfig1 = $statrulesConfig->getConfigItem($refNames[0]);
+        $statruleConfig2 = $statrulesConfig->getConfigItem($refNames[1]);
+
+        $this->refrule1 = new sspmod_statistics_Statistics_Rulesets_BaseRule($this->statconfig, $statruleConfig1, $refNames[0], $available);
+        $this->refrule2 = new sspmod_statistics_Statistics_Rulesets_BaseRule($this->statconfig, $statruleConfig2, $refNames[1], $available);
+    }
+
+    public function availableTimeRes()
+    {
+        return $this->refrule1->availableTimeRes();
+    }
+
+    public function availableFileSlots($timeres)
+    {
+        return $this->refrule1->availableFileSlots($timeres);
+    }
+
+    protected function resolveTimeRes($preferTimeRes)
+    {
+        return $this->refrule1->resolveTimeRes($preferTimeRes);
+    }
+
+    protected function resolveFileSlot($timeres, $preferTime)
+    {
+        return $this->refrule1->resolveFileSlot($timeres, $preferTime);
+    }
+
+    public function getTimeNavigation($timeres, $preferTime)
+    {
+        return $this->refrule1->getTimeNavigation($timeres, $preferTime);
+    }
+
+    public function getDataSet($preferTimeRes, $preferTime)
+    {
+        $timeres = $this->resolveTimeRes($preferTimeRes);
+        $fileslot = $this->resolveFileSlot($timeres, $preferTime);
+
+        $refNames = $this->ruleconfig->getArray('ref');
+
+        $dataset = new sspmod_statistics_RatioDataset($this->statconfig, $this->ruleconfig, $refNames, $timeres, $fileslot);
+        return $dataset;
+    }
 }
 
diff --git a/modules/statistics/templates/statistics.tpl.php b/modules/statistics/templates/statistics.tpl.php
index 1efa682d37d32f28aa21153bb2579fa58e07b93d..fa7e1f4e93a8c31c8d0fabbf8f76047a4d9455a9 100644
--- a/modules/statistics/templates/statistics.tpl.php
+++ b/modules/statistics/templates/statistics.tpl.php
@@ -1,206 +1,109 @@
 <?php
 $this->data['header'] = 'SimpleSAMLphp Statistics';
 
-$this->data['jquery'] = array('core' => TRUE, 'ui' => TRUE, 'css' => TRUE);
+$this->data['jquery'] = array('core' => true, 'ui' => true, 'css' => true);
 
-$this->data['head'] ='';
-$this->data['head'] .= '<script type="text/javascript">
-$(document).ready(function() {
-	$("#tabdiv").tabs();
-});
-</script>';
+$this->data['head'] = '<link rel="stylesheet" type="text/css" href="' . SimpleSAML\Module::getModuleURL("statistics/style.css") . '" />' . "\n";
+$this->data['head'] .= '<script type="text/javascript" src="' . SimpleSAML\Module::getModuleURL("statistics/javascript.js") . '"></script>' . "\n";
 
 $this->includeAtTemplateBase('includes/header.php');
 
-
-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'],
-	);
-	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 (isset($key)) {
-		if (isset($vars[$key])) unset($vars[$key]);
-		if (isset($value)) $vars[$key] = $value;
-	}
-
-	if ($type === 'get') {
-		return '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;
-	}
-	
-}
-
-
-
-?>
-
-	<style type="text/css" media="all">
-.ui-tabs-panel { padding: .5em }
-div#content {
-	margin: .4em ! important;
-}
-.tableview {
-	border-collapse: collapse;
-	border: 1px solid #ccc;
-	margin: 1em;
-	width: 80%;
-}
-.tableview th, .tableview td{
-	border: 1px solid: #ccc;
-	padding: 0px 5px;
-}
-.tableview th {
-	background: #e5e5e5;
-}
-.tableview tr.total td {
-	color: #500; font-weight: bold;
-}
-.tableview tr.even td {
-	background: #f5f5f5;
-	border-top: 1px solid #e0e0e0;
-	border-bottom: 1px solid #e0e0e0;
-}
-.tableview th.value, .tableview td.value {
-	text-align: right;
-}
-div.corner_t {
-    max-width: none ! important;
-}
-table.timeseries tr.odd td {
-	background-color: #f4f4f4;
-}
-table.timeseries td {
-	padding-right: 2em; border: 1px solid #ccc
-}
-td.datacontent {
-	text-align: right;
-}
-	</style>
-
-<?php
-echo('<h1>'. $this->data['available.rules'][$this->data['selected.rule']]['name'] . '</h1>');
-echo('<p>' . $this->data['available.rules'][$this->data['selected.rule']]['descr'] . '</p>');
+echo '<h1>'. $this->data['available.rules'][$this->data['selected.rule']]['name'] . '</h1>';
+echo '<p>' . $this->data['available.rules'][$this->data['selected.rule']]['descr'] . '</p>';
 
 // Report settings
-echo '<table class="selecttime" style="width: 100%; border: 1px solid #ccc; background: #eee; margin: 1px 0px; padding: 0px">';
-echo('<tr><td style="width: 50px; padding: 0px"><img style="margin: 0px" src="../../resources/icons/crystal_project/kchart.32x32.png" alt="Report settings" /></td>');
+echo '<table class="selecttime">';
+echo '<tr><td class="selecttime_icon"><img src="' . SimpleSAML\Utils\HTTP::getBaseUrl() . 'resources/icons/crystal_project/kchart.32x32.png" alt="Report settings" /></td>';
 
 // Select report
 echo '<td>';
-echo '<form style="display: inline">';
-echo getBaseURL($this, 'post', 'rule');
-echo '<select onChange="submit();" name="rule">';
-foreach ($this->data['available.rules'] AS $key => $rule) {
-	if ($key === $this->data['selected.rule']) {
-		echo '<option selected="selected" value="' . $key . '">' . $rule['name'] . '</option>';
-	} else {
-		echo '<option value="' . $key . '">' . $rule['name'] . '</option>';
-	}
+echo '<form action="#">';
+echo $this->data['post_rule'];
+echo '<select onchange="submit();" name="rule">';
+foreach ($this->data['available.rules'] as $key => $rule) {
+    if ($key === $this->data['selected.rule']) {
+        echo '<option selected="selected" value="' . $key . '">' . $rule['name'] . '</option>';
+    } else {
+        echo '<option value="' . $key . '">' . $rule['name'] . '</option>';
+    }
 }
 echo '</select></form>';
 echo '</td>';
 
-
 // Select delimiter
-echo '<td style="text-align: right">';
-echo '<form style="display: inline">';
-echo getBaseURL($this, 'post', 'd');
-echo '<select onChange="submit();" name="d">';
-foreach ($this->data['availdelimiters'] AS $key => $delim) {
-
-	$delimName = $delim;
-	if(array_key_exists($delim, $this->data['delimiterPresentation'])) $delimName = $this->data['delimiterPresentation'][$delim];
-
-	if ($key == '_') {
-		echo '<option value="_">Total</option>';
-	} elseif (isset($_REQUEST['d']) && $delim == $_REQUEST['d']) {
-		echo '<option selected="selected" value="' . htmlspecialchars($delim) . '">' . htmlspecialchars($delimName) . '</option>';
-	} else {
-		echo '<option  value="' . htmlspecialchars($delim) . '">' . htmlspecialchars($delimName) . '</option>';
-	}
+echo '<td class="td_right">';
+echo '<form action="#">';
+echo $this->data['post_d'];
+echo '<select onchange="submit();" name="d">';
+foreach ($this->data['availdelimiters'] as $key => $delim) {
+    $delimName = $delim;
+    if (array_key_exists($delim, $this->data['delimiterPresentation'])) {
+        $delimName = $this->data['delimiterPresentation'][$delim];
+    }
+
+    if ($key == '_') {
+        echo '<option value="_">Total</option>';
+    } elseif (isset($_REQUEST['d']) && $delim == $_REQUEST['d']) {
+        echo '<option selected="selected" value="' . htmlspecialchars($delim) . '">' . htmlspecialchars($delimName) . '</option>';
+    } else {
+        echo '<option  value="' . htmlspecialchars($delim) . '">' . htmlspecialchars($delimName) . '</option>';
+    }
 }
 echo '</select></form>';
-echo '</td>';
+echo '</td></tr>';
 
 echo '</table>';
 
 // End report settings
 
 
-
-
 // Select time and date
-echo '<table class="selecttime" style="width: 100%; border: 1px solid #ccc; background: #eee; margin: 1px 0px; padding: 0px">';
-echo('<tr><td style="width: 50px; padding: 0px"><img style="margin: 0px" src="../../resources/icons/crystal_project/date.32x32.png" alt="Select date and time" /></td>');
-
-
-
-
-
+echo '<table class="selecttime">';
+echo '<tr><td class="selecttime_icon"><img src="' . SimpleSAML\Utils\HTTP::getBaseUrl() . 'resources/icons/crystal_project/date.32x32.png" alt="Select date and time" /></td>';
 
 if (isset($this->data['available.times.prev'])) {
-
-	echo('<td style=""><a href="' . getBaseURL($this, 'get', 'time', $this->data['available.times.prev']) . '">« Previous</a></td>');
+    echo '<td><a href="' . $this->data['get_times_prev'] . '">« Previous</a></td>';
 } else {
-	echo('<td style="color: #ccc">« Previous</td>');
+    echo '<td class="selecttime_link_grey">« Previous</td>';
 }
 
-
-echo '<td style="text-align: right">';
-echo '<form style="display: inline">';
-echo getBaseURL($this, 'post', 'res');
-echo '<select onChange="submit();" name="res">';
-foreach ($this->data['available.timeres'] AS $key => $timeresname) {
-	if ($key == $this->data['selected.timeres']) {
-		echo '<option selected="selected" value="' . $key . '">' . $timeresname . '</option>';
-	} else {
-		echo '<option  value="' . $key . '">' . $timeresname . '</option>';
-	}
+echo '<td class="td_right">';
+echo '<form action="#">';
+echo $this->data['post_res'];
+echo '<select onchange="submit();" name="res">';
+foreach ($this->data['available.timeres'] as $key => $timeresname) {
+    if ($key == $this->data['selected.timeres']) {
+        echo '<option selected="selected" value="' . $key . '">' . $timeresname . '</option>';
+    } else {
+        echo '<option  value="' . $key . '">' . $timeresname . '</option>';
+    }
 }
 echo '</select></form>';
 echo '</td>';
 
-
-echo '<td style="text-align: left">';
-echo '<form style="display: inline">';
-echo getBaseURL($this, 'post', 'time');
-echo '<select onChange="submit();" name="time">';
-foreach ($this->data['available.times'] AS $key => $timedescr) {
-	if ($key == $this->data['selected.time']) {
-		echo '<option selected="selected" value="' . $key . '">' . $timedescr . '</option>';
-	} else {
-		echo '<option  value="' . $key . '">' . $timedescr . '</option>';
-	}
+echo '<td class="td_left">';
+echo '<form action="#">';
+echo $this->data['post_time'];
+echo '<select onchange="submit();" name="time">';
+foreach ($this->data['available.times'] as $key => $timedescr) {
+    if ($key == $this->data['selected.time']) {
+        echo '<option selected="selected" value="' . $key . '">' . $timedescr . '</option>';
+    } else {
+        echo '<option  value="' . $key . '">' . $timedescr . '</option>';
+    }
 }
 echo '</select></form>';
 echo '</td>';
 
 if (isset($this->data['available.times.next'])) {
-	echo('<td style="text-align: right; padding-right: 4px"><a href="' . getBaseURL($this, 'get', 'time', $this->data['available.times.next']) . '">Next »</a></td>');
+    echo '<td class="td_right td_next_right"><a href="' . $this->data['get_times_next'] . '">Next »</a></td>';
 } else {
-	echo('<td style="color: #ccc; text-align: right; padding-right: 4px">Next »</td>');
+    echo '<td class="td_right selecttime_link_grey td_next_right">Next »</td>';
 }
 
-
-
-
 echo '</tr></table>';
 
 
-
-
-
-
-
 echo '<div id="tabdiv"><ul class="tabset_tabs">
    <li><a href="#graph">Graph</a></li>
    <li><a href="#table">Summary table</a></li>
@@ -210,27 +113,23 @@ echo '
 
 <div id="graph" class="tabset_content">';
 
+echo '<img src="' . htmlspecialchars($this->data['imgurl']) . '" alt="Graph" />';
 
-echo '<img src="' . htmlspecialchars($this->data['imgurl']) . '" />';
-
-
-echo '<form style="display: inline">';
-echo('<p style="text-align: right">Compare with total from this dataset ');
-echo getBaseURL($this, 'post', 'rule2');
-echo '<select onChange="submit();" name="rule2">';
+echo '<form action="#">';
+echo '<p class="p_right">Compare with total from this dataset ';
+echo $this->data['post_rule2'];
+echo '<select onchange="submit();" name="rule2">';
 echo '	<option value="_">None</option>';
-foreach ($this->data['available.rules'] AS $key => $rule) {
-	if ($key === $this->data['selected.rule2']) {
-		echo '<option selected="selected" value="' . $key . '">' . $rule['name'] . '</option>';
-	} else {
-		echo '<option value="' . $key . '">' . $rule['name'] . '</option>';
-	}
+foreach ($this->data['available.rules'] as $key => $rule) {
+    if ($key === $this->data['selected.rule2']) {
+        echo '<option selected="selected" value="' . $key . '">' . $rule['name'] . '</option>';
+    } else {
+        echo '<option value="' . $key . '">' . $rule['name'] . '</option>';
+    }
 }
-echo '</select></form>';
-
-
-echo '</div>'; # end graph content.
+echo '</select></p></form>';
 
+echo '</div>'; // end graph content.
 
 
 /**
@@ -240,20 +139,22 @@ $classint = array('odd', 'even'); $i = 0;
 echo '<div id="table" class="tabset_content">';
 
 if (isset($this->data['pieimgurl'])) {
-	echo('<img src="' . $this->data['pieimgurl'] . '" />');
+    echo '<img src="' . $this->data['pieimgurl'] . '" alt="Pie chart" />';
 }
-echo '<table class="tableview"><tr><th class="value">Value</th><th class="category">Data range</th>';
+echo '<table class="tableview"><tr><th class="value">Value</th><th class="category">Data range</th></tr>';
 
-foreach ( $this->data['summaryDataset'] as $key => $value ) {
-	$clint = $classint[$i++ % 2];
-	
-	$keyName = $key;
-	if(array_key_exists($key, $this->data['delimiterPresentation'])) $keyName = $this->data['delimiterPresentation'][$key];
+foreach ($this->data['summaryDataset'] as $key => $value) {
+    $clint = $classint[$i++ % 2];
 
-	if ($key === '_') {
-	    echo '<tr class="total '  . $clint . '"><td  class="value">' . $value . '</td><td class="category">' . $keyName . '</td></tr>';
+    $keyName = $key;
+    if (array_key_exists($key, $this->data['delimiterPresentation'])) {
+        $keyName = $this->data['delimiterPresentation'][$key];
+    }
+
+    if ($key === '_') {
+        echo '<tr class="total '  . $clint . '"><td  class="value">' . $value . '</td><td class="category">' . $keyName . '</td></tr>';
     } else {
-	    echo '<tr class="' . $clint . '"><td  class="value">' . $value . '</td><td class="category">' . $keyName . '</td></tr>';
+        echo '<tr class="' . $clint . '"><td  class="value">' . $value . '</td><td class="category">' . $keyName . '</td></tr>';
     }
 }
 
@@ -261,36 +162,34 @@ echo '</table></div>';
 //  - - - - - - - End table view - - - - - - - 
 
 echo '<div id="debug" >';
-echo '<table class="timeseries" style="">';
-echo('<tr><th>Time</th><th>Total</th>');
-foreach($this->data['topdelimiters'] AS $key) {
-	$keyName = $key;
-	if(array_key_exists($key, $this->data['delimiterPresentation'])) $keyName = $this->data['delimiterPresentation'][$key];
- 	echo('<th>' . $keyName . '</th>');
+echo '<table class="timeseries">';
+echo '<tr><th>Time</th><th>Total</th>';
+foreach ($this->data['topdelimiters'] as $key) {
+    $keyName = $key;
+    if (array_key_exists($key, $this->data['delimiterPresentation'])) {
+        $keyName = $this->data['delimiterPresentation'][$key];
+    }
+    echo'<th>' . $keyName . '</th>';
 }
-echo('</tr>');
+echo '</tr>';
 
 
 $i = 0;
-foreach ($this->data['debugdata'] AS $slot => $dd) {
-	echo('<tr class="' . ((++$i % 2) == 0 ? 'odd' : 'even') . '">');
-	echo('<td style="">' . $dd[0] . '</td>');	
-	echo('<td class="datacontent">' . $dd[1] . '</td>');
-
-	foreach($this->data['topdelimiters'] AS $key) {
-		echo('<td class="datacontent">' . 
-			(array_key_exists($key, $this->data['results'][$slot]) ? $this->data['results'][$slot][$key] : '&nbsp;') . 
-			'</td>');
-	}
-	echo('</tr>');
+foreach ($this->data['debugdata'] as $slot => $dd) {
+    echo '<tr class="' . ((++$i % 2) == 0 ? 'odd' : 'even') . '">';
+    echo '<td>' . $dd[0] . '</td>';
+    echo '<td class="datacontent">' . $dd[1] . '</td>';
+
+    foreach ($this->data['topdelimiters'] as $key) {
+        echo '<td class="datacontent">' . (array_key_exists($key, $this->data['results'][$slot]) ?
+            $this->data['results'][$slot][$key] : '&nbsp;') . '</td>';
+    }
+    echo '</tr>';
 }
 echo '</table>';
 
 
-echo '</div>'; # End debug tab content
-echo('</div>'); # End tab div
-
-
+echo '</div>'; // End debug tab content
+echo '</div>'; // End tab div
 
 $this->includeAtTemplateBase('includes/footer.php');
-
diff --git a/modules/statistics/templates/statmeta.tpl.php b/modules/statistics/templates/statmeta.tpl.php
index 1ad37d3c559b27a9410f0add0571ac9298060bc9..9af5a032ef3ce5b9caf74256de5bbb8c9ca6b885 100644
--- a/modules/statistics/templates/statmeta.tpl.php
+++ b/modules/statistics/templates/statmeta.tpl.php
@@ -1,56 +1,42 @@
 <?php
 $this->data['header'] = 'SimpleSAMLphp Statistics Metadata';
+$this->data['head'] = '<link rel="stylesheet" type="text/css" href="' . SimpleSAML\Module::getModuleURL("statistics/style.css") . '" />';
 $this->includeAtTemplateBase('includes/header.php');
 
-?>
+echo '<table id="statmeta">' ;
 
+if (isset($this->data['metadata'])) {
+    $metadata = $this->data['metadata'];
 
-<?php
+    if (isset($metadata['lastrun'])) {
+        echo '<tr><td>Aggregator last run at</td><td>' . $metadata['lastrun'] . '</td></tr>';
+    }
 
-echo('<table style="width: 100%">');
+    if (isset($metadata['notBefore'])) {
+        echo '<tr><td>Aggregated data until</td><td>' . $metadata['notBefore'] . '</td></tr>';
+    }
 
-if (isset($this->data['metadata'])) {
+    if (isset($metadata['memory'])) {
+        echo '<tr><td>Memory usage</td><td>' . $metadata['memory'] . ' MB' . '</td></tr>';
+    }
+
+    if (isset($metadata['time'])) {
+        echo '<tr><td>Execution time</td><td>' . $metadata['time'] . ' seconds' . '</td></tr>';
+    }
 
-	if (isset($this->data['metadata']['lastrun'] )) {
-		echo('<tr><td>Aggregator last run at</td><td>' . 
-			date('l jS \of F Y H:i:s', $this->data['metadata']['lastrun']) . 
-			'</td></tr>');
-	}
-
-	if (isset($this->data['metadata']['notBefore'] )) {
-		echo('<tr><td>Aggregated data until</td><td>' . 
-			date('l jS \of F Y H:i:s', $this->data['metadata']['notBefore']) . 
-			'</td></tr>');
-	}
-	
-	if (isset($this->data['metadata']['memory'] )) {
-		echo('<tr><td>Memory usage</td><td>' . 
-			number_format($this->data['metadata']['memory'] / (1024*1024), 2) . ' MB' . 
-			'</td></tr>');
-	}
-	if (isset($this->data['metadata']['time'] )) {
-		echo('<tr><td>Execution time</td><td>' . 
-			$this->data['metadata']['time'] . ' seconds' .
-			'</td></tr>');
-	}
-	if (isset($this->data['metadata']['lastlinehash'] )) {
-		echo('<tr><td>SHA1 of last processed logline</td><td>' . 
-			$this->data['metadata']['lastlinehash'] .
-			'</td></tr>');
-	}
-	if (isset($this->data['metadata']['lastline'] )) {
-		echo('<tr><td>Last processed logline</td><td>' . 
-			$this->data['metadata']['lastline'] .
-			'</td></tr>');
-	}
-	
-	
+    if (isset($metadata['lastlinehash'] )) {
+        echo '<tr><td>SHA1 of last processed logline</td><td>' . $metadata['lastlinehash'] . '</td></tr>';
+    }
+
+    if (isset($metadata['lastline'] )) {
+        echo '<tr><td>Last processed logline</td><td>' . $metadata['lastline'] . '</td></tr>';
+    }
 } else {
-	echo('<tr><td>No metadata found</td></tr>');
+    echo '<tr><td>No metadata found</td></tr>';
 }
 
-echo('</table>');
-
-echo('<p>[ <a href="showstats.php">Show statistics</a> ] </p>');
+echo '</table>';
+echo '<p>[ <a href="' . SimpleSAML\Module::getModuleURL("statistics/showstats.php") . '">Show statistics</a> ] </p>';
 
 $this->includeAtTemplateBase('includes/footer.php');
+
diff --git a/modules/statistics/www/javascript.js b/modules/statistics/www/javascript.js
new file mode 100644
index 0000000000000000000000000000000000000000..a5aa421a67fffda6c3c2f4527c8227e58b1b9cde
--- /dev/null
+++ b/modules/statistics/www/javascript.js
@@ -0,0 +1,4 @@
+$(document).ready(function() {
+        $("#tabdiv").tabs();
+});
+
diff --git a/modules/statistics/www/showstats.php b/modules/statistics/www/showstats.php
index d15edc7cd5743f5bb9ab291a89dc293777f2299a..051dc9aa55440fd3cd87059ce0a15fe6ab1dc302 100644
--- a/modules/statistics/www/showstats.php
+++ b/modules/statistics/www/showstats.php
@@ -4,25 +4,36 @@ $config = SimpleSAML_Configuration::getInstance();
 $statconfig = SimpleSAML_Configuration::getConfig('module_statistics.php');
 $session = SimpleSAML_Session::getSessionFromRequest();
 
-
 sspmod_statistics_AccessCheck::checkAccess($statconfig);
 
-
 /*
  * Check input parameters
  */
-$preferRule = NULL;
-$preferRule2 = NULL;
-$preferTime = NULL;
-$preferTimeRes = NULL;
-$delimiter = NULL;
-if(array_key_exists('rule', $_REQUEST)) $preferRule = $_REQUEST['rule'];
-if(array_key_exists('rule2', $_REQUEST)) $preferRule2 = $_REQUEST['rule2'];
-if(array_key_exists('time', $_REQUEST)) $preferTime = $_REQUEST['time'];
-if(array_key_exists('res', $_REQUEST)) $preferTimeRes = $_REQUEST['res'];
-if(array_key_exists('d', $_REQUEST)) $delimiter = $_REQUEST['d'];
-
-if ($preferRule2 === '_') $preferRule2 = NULL;
+$preferRule = null;
+$preferRule2 = null;
+$preferTime = null;
+$preferTimeRes = null;
+$delimiter = null;
+
+if (array_key_exists('rule', $_REQUEST)) {
+    $preferRule = $_REQUEST['rule'];
+}
+if (array_key_exists('rule2', $_REQUEST)) {
+    $preferRule2 = $_REQUEST['rule2'];
+}
+if (array_key_exists('time', $_REQUEST)) {
+    $preferTime = $_REQUEST['time'];
+}
+if (array_key_exists('res', $_REQUEST)) {
+    $preferTimeRes = $_REQUEST['res'];
+}
+if (array_key_exists('d', $_REQUEST)) {
+    $delimiter = $_REQUEST['d'];
+}
+
+if ($preferRule2 === '_') {
+    $preferRule2 = null;
+}
 
 /*
  * Create statistics data.
@@ -45,9 +56,6 @@ $timeNavigation = $statrule->getTimeNavigation($timeres, $preferTime);
 $dataset->aggregateSummary();
 $dataset->calculateMax();
 
-
-
-
 $piedata = $dataset->getPieData();
 
 $datasets = array();
@@ -59,49 +67,38 @@ $maxes = array();
 
 $maxes[] = $dataset->getMax();
 
-
 if (isset($preferRule2)) {
-	$statrule = $ruleset->getRule($preferRule2);
-	$dataset2 = $statrule->getDataset($preferTimeRes, $preferTime);
-	$dataset2->aggregateSummary();
-	$dataset2->calculateMax();
-	
-	$datasets[] = $dataset2->getPercentValues();
-	$maxes[] = $dataset2->getMax();
-}
-
-
-
-
-
-
+    $statrule = $ruleset->getRule($preferRule2);
+    $dataset2 = $statrule->getDataset($preferTimeRes, $preferTime);
+    $dataset2->aggregateSummary();
+    $dataset2->calculateMax();
 
+    $datasets[] = $dataset2->getPercentValues();
+    $maxes[] = $dataset2->getMax();
+}
 
 $dimx = $statconfig->getValue('dimension.x', 800);
 $dimy = $statconfig->getValue('dimension.y', 350);
 $grapher = new sspmod_statistics_Graph_GoogleCharts($dimx, $dimy);
 
 if (array_key_exists('output', $_REQUEST) && $_REQUEST['output'] === 'csv') {
-
-	header('Content-type: text/csv');
-	header('Content-Disposition: attachment; filename="simplesamlphp-data.csv"');
-	$data = $dataset->getDebugData();
-	foreach($data AS $de) {
-		if (isset($de[1])) {
-			echo('"' . $de[0] . '",' . $de[1] . "\n");
-		}
-	}
-	exit;
+    header('Content-type: text/csv');
+    header('Content-Disposition: attachment; filename="simplesamlphp-data.csv"');
+    $data = $dataset->getDebugData();
+    foreach ($data as $de) {
+        if (isset($de[1])) {
+            echo '"' . $de[0] . '",' . $de[1] . "\n";
+        }
+    }
+    exit;
 }
 
-
-
 $t = new SimpleSAML_XHTML_Template($config, 'statistics:statistics.tpl.php');
 $t->data['pageid'] = 'statistics';
 $t->data['header'] = 'stat';
 $t->data['imgurl'] = $grapher->show($axis['axis'], $axis['axispos'], $datasets, $maxes);
 if (isset($piedata)) {
-	$t->data['pieimgurl'] = $grapher->showPie( $dataset->getDelimiterPresentationPie(), $piedata);
+    $t->data['pieimgurl'] = $grapher->showPie( $dataset->getDelimiterPresentationPie(), $piedata);
 }
 $t->data['available.rules'] = $ruleset->availableRulesNames();
 $t->data['available.times'] = $statrule->availableFileSlots($timeres);
@@ -122,5 +119,47 @@ $t->data['topdelimiters'] = $dataset->getTopDelimiters();
 $t->data['availdelimiters'] = $dataset->availDelimiters();
 
 $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->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'],
+    );
+    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 (isset($key)) {
+        if (isset($vars[$key])) {
+            unset($vars[$key]);
+        }
+        if (isset($value)) {
+            $vars[$key] = $value;
+        }
+    }
+
+    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;
+    }
+}
diff --git a/modules/statistics/www/statmeta.php b/modules/statistics/www/statmeta.php
index 4b8a9d04b78695bf1c180801762e110f0c6fcb5a..0959bf1e501e986e5f53dfb687bca6a90b4ce468 100644
--- a/modules/statistics/www/statmeta.php
+++ b/modules/statistics/www/statmeta.php
@@ -9,8 +9,20 @@ $aggr = new sspmod_statistics_Aggregator();
 $aggr->loadMetadata();
 $metadata = $aggr->getMetadata();
 
-
 $t = new SimpleSAML_XHTML_Template($config, 'statistics:statmeta.tpl.php');
-$t->data['metadata'] =  $metadata;
+
+if ($metadata !== null) {
+    if (in_array('lastrun', $metadata)) {
+        $metadata['lastrun'] = date('l jS \of F Y H:i:s', $metadata['lastrun']);
+    }
+    if (in_array('notBefore', $metadata)) {
+        $metadata['notBefore'] = date('l jS \of F Y H:i:s', $metadata['notBefore']);
+    }
+    if (in_array('memory', $metadata)) {
+        $metadata['memory'] = number_format($metadata['memory'] / (1024 * 1024), 2);
+    }
+    $t->data['metadata'] = $metadata;
+}
+
 $t->show();
 
diff --git a/modules/statistics/www/style.css b/modules/statistics/www/style.css
new file mode 100644
index 0000000000000000000000000000000000000000..1485d07910f3e7bb23af42fc43546184c050338b
--- /dev/null
+++ b/modules/statistics/www/style.css
@@ -0,0 +1,95 @@
+@media all {
+    .ui-tabs-panel { padding: .5em }
+
+    div#content {
+        margin: .4em ! important;
+    }
+
+    .tableview {
+        border-collapse: collapse;
+        border: 1px solid #ccc;
+        margin: 1em;
+        width: 80%;
+    }
+
+    .tableview th, .tableview td {
+        border: 1px solid #ccc;
+        padding: 0px 5px;
+    }
+
+    .tableview th {
+        background: #e5e5e5;
+    }
+
+    .tableview tr.total td {
+        color: #500; font-weight: bold;
+    }
+
+    .tableview tr.even td {
+        background: #f5f5f5;
+        border-top: 1px solid #e0e0e0;
+        border-bottom: 1px solid #e0e0e0;
+    }
+
+    .tableview th.value, .tableview td.value {
+        text-align: right;
+    }
+
+    div.corner_t {
+        max-width: none ! important;
+    }
+
+    table.timeseries tr.odd td {
+        background-color: #f4f4f4;
+    }
+
+    table.timeseries td {
+        padding-right: 2em; border: 1px solid #ccc
+    }
+
+    td.datacontent {
+        text-align: right;
+    }
+
+    table.selecttime {
+        width: 100%;
+        border: 1px solid #ccc;
+        background: #eee;
+        margin: 1px 0px; padding: 0px;
+    }
+
+    td.selecttime_icon {
+        width: 50px;
+        padding: 0px;
+    }
+
+    td.selecttime_icon img {
+        margin: 0px;
+    }
+
+    td.selecttime_link_grey {
+        color: #ccc;
+    }
+
+    td.td_right {
+        text-align: right;
+    }
+    td.td_next_right {
+        padding-right: 4px;
+    }
+    td.td_left {
+        text-align: left;
+    }
+
+    p.p_right {
+        text-align: right;
+    }
+
+    form {
+        display: inline;
+    }
+
+    table#statmeta {
+        width: 100%;
+    }
+}
diff --git a/templates/_header.twig b/templates/_header.twig
index 704e340f69c295b42331b5957af347618ce662b1..461dfd25d1b2eacd5ea6d27d908728972f116b59 100644
--- a/templates/_header.twig
+++ b/templates/_header.twig
@@ -5,10 +5,18 @@
 {% if not hideLanguageBar %}
 <div id="languagebar">
     {% for lang in languageBar %}
-
-    {%- if not loop.first -%} | {% endif -%}{% if lang.url %}<a href="{{ lang.url }}">{{ lang.name }}</a>{% else %}{{ lang.name }}{% endif %}
+        {%- if not loop.first -%}|{% endif -%}
+        {% if lang.url %}
+            <a href="{{ lang.url -}}
+            {%- if queryParams %}&{% endif -%}
+            {%- for name, value in queryParams -%}
+                {%- if not loop.first %}&{% endif -%}
+                {%- if value %}{{ name }}={{ value }}{% else %}{{ name }}{% endif -%}
+            {%- endfor %}">{{ lang.name }}</a>
+        {% else %}
+            {{ lang.name }}
+        {% endif %}
 
     {% endfor %}
-
 </div>
 {% endif %}
diff --git a/templates/base.twig b/templates/base.twig
index 1edd9ca4bd7cb9c0bcdfd1eb78a592e585bca8b4..249cb2e23c1aaf14d11f43179e46f1fc7f8a1a7f 100644
--- a/templates/base.twig
+++ b/templates/base.twig
@@ -2,7 +2,7 @@
 <html>
 <head>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-    <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0">
+    <meta name="viewport" content="initial-scale=1.0">
     <title>{{ pagetitle }}</title>
     <link rel="stylesheet" type="text/css" href="/{{ baseurlpath }}resources/default.css">
     <link rel="icon" type="image/icon" href="/{{ baseurlpath }}resources/icons/favicon.ico">
diff --git a/templates/includes/header.php b/templates/includes/header.php
index 6f154c0ed24d443493c6970f262263b2f46e5dfa..2b3365128c0f7ce966e6b417af4a65e0e2442d89 100644
--- a/templates/includes/header.php
+++ b/templates/includes/header.php
@@ -43,7 +43,7 @@ header('X-Frame-Options: SAMEORIGIN');
 <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
 <head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
-<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0" />
+<meta name="viewport" content="initial-scale=1.0" />
 <script type="text/javascript" src="/<?php echo $this->data['baseurlpath']; ?>resources/script.js"></script>
 <title><?php
 if(array_key_exists('header', $this->data)) {
diff --git a/tests/lib/SimpleSAML/Store/RedisTest.php b/tests/lib/SimpleSAML/Store/RedisTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7eecdf310b86e757b1fabff755036954470d9a10
--- /dev/null
+++ b/tests/lib/SimpleSAML/Store/RedisTest.php
@@ -0,0 +1,174 @@
+<?php
+
+namespace SimpleSAML\Test\Store;
+
+use \SimpleSAML_Configuration as Configuration;
+use \SimpleSAML\Store;
+
+/**
+ * Tests for the Redis store.
+ *
+ * For the full copyright and license information, please view the LICENSE file that was distributed with this source
+ * code.
+ *
+ * @package simplesamlphp/simplesamlphp
+ */
+class RedisTest extends \PHPUnit_Framework_TestCase
+{
+    protected function setUp()
+    {
+        $this->config = array();
+
+        $this->mocked_redis = $this->getMockBuilder('Predis\Client')
+                                   ->setMethods(array('get', 'set', 'setex', 'del', 'disconnect'))
+                                   ->disableOriginalConstructor()
+                                   ->getMock();
+
+        $this->mocked_redis->method('get')
+                           ->will($this->returnCallback(array($this, 'getMocked')));
+
+        $this->mocked_redis->method('set')
+                           ->will($this->returnCallback(array($this, 'setMocked')));
+
+        $this->mocked_redis->method('setex')
+                           ->will($this->returnCallback(array($this, 'setexMocked')));
+
+        $this->mocked_redis->method('del')
+                           ->will($this->returnCallback(array($this, 'delMocked')));
+
+        $nop = function () {
+            return;
+        };
+
+        $this->mocked_redis->method('disconnect')
+                           ->will($this->returnCallback($nop));
+
+        $this->redis = new Store\Redis($this->mocked_redis);
+    }
+
+    public function getMocked($key)
+    {
+        return array_key_exists($key, $this->config) ? $this->config[$key] : false;
+    }
+
+    public function setMocked($key, $value)
+    {
+        $this->config[$key] = $value;
+    }
+
+    public function setexMocked($key, $expire, $value)
+    {
+        // Testing expiring data is more trouble than it's worth for now
+        $this->setMocked($key, $value);
+    }
+
+    public function delMocked($key)
+    {
+        unset($this->config[$key]);
+    }
+
+    /**
+     * @covers \SimpleSAML\Store::getInstance
+     * @covers \SimpleSAML\Store\Redis::__construct
+     * @test
+     */
+    public function testRedisInstance()
+    {
+        $config = Configuration::loadFromArray(array(
+            'store.type' => 'redis',
+            'store.redis.prefix' => 'phpunit_',
+        ), '[ARRAY]', 'simplesaml');
+
+        $store = Store::getInstance();
+
+        $this->assertInstanceOf('SimpleSAML\Store\Redis', $store);
+
+        $this->clearInstance($config, '\SimpleSAML_Configuration');
+        $this->clearInstance($store, '\SimpleSAML\Store');
+    }
+
+    /**
+     * @covers \SimpleSAML\Store\Redis::get
+     * @covers \SimpleSAML\Store\Redis::set
+     * @test
+     */
+    public function testInsertData()
+    {
+        $value = 'TEST';
+
+        $this->redis->set('test', 'key', $value);
+        $res = $this->redis->get('test', 'key');
+        $expected = $value;
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Store\Redis::get
+     * @covers \SimpleSAML\Store\Redis::set
+     * @test
+     */
+    public function testInsertExpiringData()
+    {
+        $value = 'TEST';
+
+        $this->redis->set('test', 'key', $value, $expire = 80808080);
+        $res = $this->redis->get('test', 'key');
+        $expected = $value;
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Store\Redis::get
+     * @test
+     */
+    public function testGetEmptyData()
+    {
+        $res = $this->redis->get('test', 'key');
+
+        $this->assertNull($res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Store\Redis::get
+     * @covers \SimpleSAML\Store\Redis::set
+     * @test
+     */
+    public function testOverwriteData()
+    {
+        $value1 = 'TEST1';
+        $value2 = 'TEST2';
+
+        $this->redis->set('test', 'key', $value1);
+        $this->redis->set('test', 'key', $value2);
+        $res = $this->redis->get('test', 'key');
+        $expected = $value2;
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Store\Redis::get
+     * @covers \SimpleSAML\Store\Redis::set
+     * @covers \SimpleSAML\Store\Redis::delete
+     * @test
+     */
+    public function testDeleteData()
+    {
+        $this->redis->set('test', 'key', 'TEST');
+        $this->redis->delete('test', 'key');
+        $res = $this->redis->get('test', 'key');
+
+        $this->assertNull($res);
+    }
+
+    protected function clearInstance($service, $className)
+    {
+        $reflectedClass = new \ReflectionClass($className);
+        $reflectedInstance = $reflectedClass->getProperty('instance');
+        $reflectedInstance->setAccessible(true);
+        $reflectedInstance->setValue($service, null);
+        $reflectedInstance->setAccessible(false);
+    }
+}
diff --git a/tests/lib/SimpleSAML/Utils/CryptoTest.php b/tests/lib/SimpleSAML/Utils/CryptoTest.php
index e0f67de19abd314d89615e09020921d9fb267555..cb31cdbae0b8afa9c18f0b74c9ca4921c6ba7d5e 100644
--- a/tests/lib/SimpleSAML/Utils/CryptoTest.php
+++ b/tests/lib/SimpleSAML/Utils/CryptoTest.php
@@ -3,12 +3,30 @@
 namespace SimpleSAML\Test\Utils;
 
 use SimpleSAML\Utils\Crypto;
+use \SimpleSAML_Configuration as Configuration;
+
+use \org\bovigo\vfs\vfsStream;
 
 /**
  * Tests for SimpleSAML\Utils\Crypto.
  */
 class CryptoTest extends \PHPUnit_Framework_TestCase
 {
+    const ROOTDIRNAME = 'testdir';
+    const DEFAULTCERTDIR = 'certdir';
+
+    public function setUp()
+    {
+        $this->root = vfsStream::setup(
+            self::ROOTDIRNAME,
+            null,
+            array(
+                self::DEFAULTCERTDIR => array(),
+            )
+        );
+        $this->root_directory = vfsStream::url(self::ROOTDIRNAME);
+        $this->certdir = $this->root_directory.DIRECTORY_SEPARATOR.self::DEFAULTCERTDIR;
+    }
 
     /**
      * Test invalid input provided to the aesDecrypt() method.
@@ -59,7 +77,7 @@ class CryptoTest extends \PHPUnit_Framework_TestCase
         $m->setAccessible(true);
 
         $plaintext = 'SUPER_SECRET_TEXT';
-        $ciphertext = 'NmRkODJlZGE2OTA3YTYwMm9En+KAReUk2z7Xi/b3c39kF/c1n6Vdj/zNARQt+UHU';
+        $ciphertext = 'uR2Yu0r4itInKx91D/l9y/08L5CIQyev9nAr27fh3Sshous4vbXRRcMcjqHDOrquD+2vqLyw7ygnbA9jA9TpB4hLZocvAWcTN8tyO82hiSY=';
         $this->assertEquals($plaintext, $m->invokeArgs(null, array(base64_decode($ciphertext), $secret)));
     }
 
@@ -135,4 +153,389 @@ pfajpJ9ZzdyLIo6dVjdQtl+S1rpFCx7ziVN8tCCX4fAVCqRqZJaG/UMLvguVqayb
 PHP;
         $this->assertEquals(trim($pem), trim(Crypto::der2pem(Crypto::pem2der($pem))));
     }
+
+    /**
+     * @covers \SimpleSAML\Utils\Crypto::pwHash
+     */
+    public function testGoodPwHash()
+    {
+        $pw = "password";
+        $algorithm = "SHA1";
+
+        $res = Crypto::pwHash($pw, $algorithm);
+
+        /*
+         * echo -n "password" | sha1sum | awk -F " " '{print $1}' | xxd -r -p | base64
+         * W6ph5Mm5Pz8GgiULbPgzG37mj9g=
+         */
+        $expected = "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=";
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\Crypto::pwHash
+     */
+    public function testGoodSaltedPwHash()
+    {
+        $pw = "password";
+        $algorithm = "SSHA1";
+        $salt = "salt";
+
+        $res = Crypto::pwHash($pw, $algorithm, $salt);
+
+        /*
+         * echo -n "password""salt" | sha1sum | awk -v salt=$(echo -n "salt" | xxd -u -p) -F " " '{print $1 salt}' | xxd -r -p | base64
+         * yI6cZwQadOA1e+/f+T+H3eCQQhRzYWx0
+         */
+        $expected = "{SSHA}yI6cZwQadOA1e+/f+T+H3eCQQhRzYWx0";
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @expectedException \SimpleSAML_Error_Exception
+     *
+     * @covers \SimpleSAML\Utils\Crypto::pwHash
+     */
+    public function testBadHashAlgorithm()
+    {
+        $pw = "password";
+        $algorithm = "wtf";
+
+        Crypto::pwHash($pw, $algorithm);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\Crypto::pwValid
+     */
+    public function testGoodPwValid()
+    {
+        $pw = "password";
+        $algorithm = "SHA1";
+
+        $hash = Crypto::pwHash($pw, $algorithm);
+        $res = Crypto::pwValid($hash, $pw);
+
+        $this->assertTrue($res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\Crypto::pwValid
+     */
+    public function testGoodSaltedPwValid()
+    {
+        $pw = "password";
+        $algorithm = "SSHA1";
+        $salt = "salt";
+
+        $hash = Crypto::pwHash($pw, $algorithm, $salt);
+        $res = Crypto::pwValid($hash, $pw);
+
+        $this->assertTrue($res);
+    }
+
+    /**
+     * @expectedException \SimpleSAML_Error_Exception
+     *
+     * @covers \SimpleSAML\Utils\Crypto::pwValid
+     */
+    public function testBadHashAlgorithmValid()
+    {
+        $pw = "password";
+        $algorithm = "wtf";
+        $hash = "{".$algorithm."}B64STRING";
+
+        Crypto::pwValid($hash, $algorithm);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\Crypto::secureCompare
+     */
+    public function testSecureCompareEqual()
+    {
+        $res = Crypto::secureCompare("string", "string");
+
+        $this->assertTrue($res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\Crypto::secureCompare
+     */
+    public function testSecureCompareNotEqual()
+    {
+        $res = Crypto::secureCompare("string1", "string2");
+
+        $this->assertFalse($res);
+    }
+
+    /**
+     * @expectedException \SimpleSAML_Error_Exception
+     *
+     * @covers \SimpleSAML\Utils\Crypto::loadPrivateKey
+     */
+    public function testLoadPrivateKeyRequiredMetadataMissing()
+    {
+        $config = new Configuration(array(), 'test');
+        $required = true;
+
+        Crypto::loadPrivateKey($config, $required);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\Crypto::loadPrivateKey
+     */
+    public function testLoadPrivateKeyNotRequiredMetadataMissing()
+    {
+        $config = new Configuration(array(), 'test');
+        $required = false;
+
+        $res = Crypto::loadPrivateKey($config, $required);
+
+        $this->assertNull($res);
+    }
+
+    /**
+     * @expectedException \SimpleSAML_Error_Exception
+     *
+     * @covers \SimpleSAML\Utils\Crypto::loadPrivateKey
+     */
+    public function testLoadPrivateKeyMissingFile()
+    {
+        $config = new Configuration(array('privatekey' => 'nonexistant'), 'test');
+
+        Crypto::loadPrivateKey($config, false, '', true);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\Crypto::loadPrivateKey
+     */
+    public function testLoadPrivateKeyBasic()
+    {
+        $filename = $this->certdir.DIRECTORY_SEPARATOR.'key';
+        $data = 'data';
+        $config = new Configuration(array('privatekey' => $filename), 'test');
+        $full_path = true;
+
+        file_put_contents($filename, $data);
+
+        $res = Crypto::loadPrivateKey($config, false, '', $full_path);
+        $expected = array('PEM' => $data);
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\Crypto::loadPrivateKey
+     */
+    public function testLoadPrivateKeyPassword()
+    {
+        $password = 'password';
+        $filename = $this->certdir.DIRECTORY_SEPARATOR.'key';
+        $data = 'data';
+        $config = new Configuration(
+            array(
+                'privatekey' => $filename,
+                'privatekey_pass' => $password,
+            ),
+            'test'
+        );
+        $full_path = true;
+
+        file_put_contents($filename, $data);
+
+        $res = Crypto::loadPrivateKey($config, false, '', $full_path);
+        $expected = array('PEM' => $data, 'password' => $password);
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\Crypto::loadPrivateKey
+     */
+    public function testLoadPrivateKeyPrefix()
+    {
+        $prefix = 'prefix';
+        $password = 'password';
+        $filename = $this->certdir.DIRECTORY_SEPARATOR.'key';
+        $data = 'data';
+        $config = new Configuration(
+            array(
+                $prefix.'privatekey' => $filename,
+                $prefix.'privatekey_pass' => $password,
+            ),
+            'test'
+        );
+        $full_path = true;
+
+        file_put_contents($filename, $data);
+
+        $res = Crypto::loadPrivateKey($config, false, $prefix, $full_path);
+        $expected = array('PEM' => $data, 'password' => $password);
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @expectedException \SimpleSAML_Error_Exception
+     *
+     * @covers \SimpleSAML\Utils\Crypto::loadPublicKey
+     */
+    public function testLoadPublicKeyRequiredMetadataMissing()
+    {
+        $config = new Configuration(array(), 'test');
+        $required = true;
+
+        Crypto::loadPublicKey($config, $required);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\Crypto::loadPublicKey
+     */
+    public function testLoadPublicKeyNotRequiredMetadataMissing()
+    {
+        $config = new Configuration(array(), 'test');
+        $required = false;
+
+        $res = Crypto::loadPublicKey($config, $required);
+
+        $this->assertNull($res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\Crypto::loadPublicKey
+     */
+    public function testLoadPublicKeyFingerprintBasicString()
+    {
+        $fingerprint = 'fingerprint';
+        $config = new Configuration(array('certFingerprint' => $fingerprint), 'test');
+
+        $res = Crypto::loadPublicKey($config);
+        $expected = array('certFingerprint' => array($fingerprint));
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\Crypto::loadPublicKey
+     */
+    public function testLoadPublicKeyFingerprintBasicArray()
+    {
+        $fingerprint1 = 'fingerprint1';
+        $fingerprint2 = 'fingerprint2';
+        $config = new Configuration(
+            array(
+                'certFingerprint' => array(
+                    $fingerprint1,
+                    $fingerprint2
+                ),
+            ),
+            'test'
+        );
+
+        $res = Crypto::loadPublicKey($config);
+        $expected = array('certFingerprint' => array($fingerprint1, $fingerprint2));
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\Crypto::loadPublicKey
+     */
+    public function testLoadPublicKeyFingerprintLowercase()
+    {
+        $fingerprint = 'FINGERPRINT';
+        $config = new Configuration(array('certFingerprint' => $fingerprint), 'test');
+
+        $res = Crypto::loadPublicKey($config);
+        $expected = array('certFingerprint' => array(strtolower($fingerprint)));
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\Crypto::loadPublicKey
+     */
+    public function testLoadPublicKeyFingerprintRemoveColons()
+    {
+        $fingerprint = 'f:i:n:g:e:r:p:r:i:n:t';
+        $config = new Configuration(array('certFingerprint' => $fingerprint), 'test');
+
+        $res = Crypto::loadPublicKey($config);
+        $expected = array('certFingerprint' => array(str_replace(':', '', $fingerprint)));
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\Crypto::loadPublicKey
+     */
+    public function testLoadPublicKeyNotX509Certificate()
+    {
+        $config = new Configuration(
+            array(
+                'keys' => array(
+                    array(
+                        'X509Certificate' => '',
+                        'type' => 'NotX509Certificate',
+                        'signing' => true
+                    ),
+                ),
+            ),
+            'test'
+        );
+
+        $res = Crypto::loadPublicKey($config);
+
+        $this->assertNull($res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\Crypto::loadPublicKey
+     */
+    public function testLoadPublicKeyNotSigning()
+    {
+        $config = new Configuration(
+            array(
+                'keys' => array(
+                    array(
+                        'X509Certificate' => '',
+                        'type' => 'X509Certificate',
+                        'signing' => false
+                    ),
+                ),
+            ),
+            'test'
+        );
+
+        $res = Crypto::loadPublicKey($config);
+
+        $this->assertNull($res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\Crypto::loadPublicKey
+     */
+    public function testLoadPublicKeyBasic()
+    {
+        $x509certificate = 'x509certificate';
+        $config = new Configuration(
+            array(
+                'keys' => array(
+                    array(
+                        'X509Certificate' => $x509certificate,
+                        'type' => 'X509Certificate',
+                        'signing' => true
+                    ),
+                ),
+            ),
+            'test'
+        );
+
+        $pubkey = Crypto::loadPublicKey($config);
+        $res = $pubkey['certData'];
+        $expected = $x509certificate;
+
+        $this->assertEquals($expected, $res);
+    }
 }
diff --git a/tests/lib/SimpleSAML/Utils/SystemTest.php b/tests/lib/SimpleSAML/Utils/SystemTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c749b798757df5ce3baad64aed59135a5fbb1b38
--- /dev/null
+++ b/tests/lib/SimpleSAML/Utils/SystemTest.php
@@ -0,0 +1,250 @@
+<?php
+
+namespace SimpleSAML\Test\Utils;
+
+use \SimpleSAML_Configuration as Configuration;
+use \SimpleSAML\Utils\System;
+
+use \org\bovigo\vfs\vfsStream;
+
+/**
+ * Tests for SimpleSAML\Utils\System.
+ */
+class SystemTest extends \PHPUnit_Framework_TestCase
+{
+    const ROOTDIRNAME = 'testdir';
+    const DEFAULTTEMPDIR = 'tempdir';
+
+    public function setUp()
+    {
+        $this->root = vfsStream::setup(
+            self::ROOTDIRNAME,
+            null,
+            array(
+                self::DEFAULTTEMPDIR => array(),
+            )
+        );
+        $this->root_directory = vfsStream::url(self::ROOTDIRNAME);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\System::getOS
+     * @test
+     */
+    public function testGetOSBasic()
+    {
+        $res = System::getOS();
+
+        $this->assertInternalType("int", $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\System::resolvePath
+     * @test
+     */
+    public function testResolvePathRemoveTrailingSlashes()
+    {
+        $base = "/base////";
+        $path = "test";
+
+        $res = System::resolvePath($path, $base);
+        $expected = "/base/test";
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\System::resolvePath
+     * @test
+     */
+    public function testResolvePathPreferAbsolutePathToBase()
+    {
+        $base = "/base/";
+        $path = "/test";
+
+        $res = System::resolvePath($path, $base);
+        $expected = "/test";
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\System::resolvePath
+     * @test
+     */
+    public function testResolvePathCurDirPath()
+    {
+        $base = "/base/";
+        $path = "/test/.";
+
+        $res = System::resolvePath($path, $base);
+        $expected = "/test";
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\System::resolvePath
+     * @test
+     */
+    public function testResolvePathParentPath()
+    {
+        $base = "/base/";
+        $path = "/test/child/..";
+
+        $res = System::resolvePath($path, $base);
+        $expected = "/test";
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\System::writeFile
+     * @test
+     */
+    public function testWriteFileInvalidArguments()
+    {
+        $this->setExpectedException('\InvalidArgumentException');
+        System::writeFile(null, null, null);
+    }
+
+    /**
+     * @requires PHP 5.4.0
+     * @covers \SimpleSAML\Utils\System::writeFile
+     * @test
+     */
+    public function testWriteFileBasic()
+    {
+        $tempdir = $this->root_directory . DIRECTORY_SEPARATOR . self::DEFAULTTEMPDIR;
+        $config = $this->setConfigurationTempDir($tempdir);
+
+        $filename = $this->root_directory . DIRECTORY_SEPARATOR . 'test';
+
+        System::writeFile($filename, '');
+
+        $this->assertFileExists($filename);
+
+        $this->clearInstance($config, '\SimpleSAML_Configuration');
+    }
+
+    /**
+     * @requires PHP 5.4.0
+     * @covers \SimpleSAML\Utils\System::writeFile
+     * @test
+     */
+    public function testWriteFileContents()
+    {
+        $tempdir = $this->root_directory . DIRECTORY_SEPARATOR . self::DEFAULTTEMPDIR;
+        $config = $this->setConfigurationTempDir($tempdir);
+
+        $filename = $this->root_directory . DIRECTORY_SEPARATOR . 'test';
+        $contents = 'TEST';
+
+        System::writeFile($filename, $contents);
+
+        $res = file_get_contents($filename);
+        $expected = $contents;
+
+        $this->assertEquals($expected, $res);
+
+        $this->clearInstance($config, '\SimpleSAML_Configuration');
+    }
+
+    /**
+     * @requires PHP 5.4.0
+     * @covers \SimpleSAML\Utils\System::writeFile
+     * @test
+     */
+    public function testWriteFileMode()
+    {
+        $tempdir = $this->root_directory . DIRECTORY_SEPARATOR . self::DEFAULTTEMPDIR;
+        $config = $this->setConfigurationTempDir($tempdir);
+
+        $filename = $this->root_directory . DIRECTORY_SEPARATOR . 'test';
+        $mode = 0666;
+
+        System::writeFile($filename, '', $mode);
+
+        $res = $this->root->getChild('test')->getPermissions();
+        $expected = $mode;
+
+        $this->assertEquals($expected, $res);
+
+        $this->clearInstance($config, '\SimpleSAML_Configuration');
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\System::getTempDir
+     * @test
+     */
+    public function testGetTempDirBasic()
+    {
+        $tempdir = $this->root_directory . DIRECTORY_SEPARATOR . self::DEFAULTTEMPDIR;
+        $config = $this->setConfigurationTempDir($tempdir);
+
+        $res = System::getTempDir();
+        $expected = $tempdir;
+
+        $this->assertEquals($expected, $res);
+        $this->assertFileExists($res);
+
+        $this->clearInstance($config, '\SimpleSAML_Configuration');
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\System::getTempDir
+     * @test
+     */
+    public function testGetTempDirNonExistant()
+    {
+        $tempdir = $this->root_directory . DIRECTORY_SEPARATOR . 'nonexistant';
+        $config = $this->setConfigurationTempDir($tempdir);
+
+        $res = System::getTempDir();
+        $expected = $tempdir;
+
+        $this->assertEquals($expected, $res);
+        $this->assertFileExists($res);
+
+        $this->clearInstance($config, '\SimpleSAML_Configuration');
+    }
+
+    /**
+     * @requires PHP 5.4.0
+     * @requires OS Linux
+     * @covers \SimpleSAML\Utils\System::getTempDir
+     * @test
+     */
+    public function testGetTempDirBadOwner()
+    {
+        $bad_uid = posix_getuid() + 1;
+
+        $tempdir = $this->root_directory . DIRECTORY_SEPARATOR . self::DEFAULTTEMPDIR;
+        $config = $this->setConfigurationTempDir($tempdir);
+
+        chown($tempdir, $bad_uid);
+
+        $this->setExpectedException('\SimpleSAML_Error_Exception');
+        $res = System::getTempDir();
+
+        $this->clearInstance($config, '\SimpleSAML_Configuration');
+    }
+
+    private function setConfigurationTempDir($directory)
+    {
+        $config = Configuration::loadFromArray(array(
+            'tempdir' => $directory,
+        ), '[ARRAY]', 'simplesaml');
+
+        return $config;
+    }
+
+    protected function clearInstance($service, $className)
+    {
+        $reflectedClass = new \ReflectionClass($className);
+        $reflectedInstance = $reflectedClass->getProperty('instance');
+        $reflectedInstance->setAccessible(true);
+        $reflectedInstance->setValue($service, null);
+        $reflectedInstance->setAccessible(false);
+    }
+}
diff --git a/tests/lib/SimpleSAML/Utils/XMLTest.php b/tests/lib/SimpleSAML/Utils/XMLTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..bd675b760c5682c9f8d71290b889d1f46cf020ce
--- /dev/null
+++ b/tests/lib/SimpleSAML/Utils/XMLTest.php
@@ -0,0 +1,379 @@
+<?php
+
+namespace SimpleSAML\Test\Utils;
+
+use \SimpleSAML_Configuration as Configuration;
+use \SimpleSAML\Utils\XML;
+
+/**
+ * Tests for SimpleSAML\Utils\XML.
+ */
+class XMLTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @covers \SimpleSAML\Utils\XML::isDOMNodeOfType
+     * @test
+     */
+    public function testIsDomNodeOfTypeBasic()
+    {
+        $name = 'name';
+        $namespace_uri = 'ns';
+        $element = new \DOMElement($name, 'value', $namespace_uri);
+
+        $res = XML::isDOMNodeOfType($element, $name, $namespace_uri);
+
+        $this->assertTrue($res);
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     *
+     * @covers \SimpleSAML\Utils\XML::isDOMNodeOfType
+     * @test
+     */
+    public function testIsDomNodeOfTypeMissingNamespace()
+    {
+        $name = 'name';
+        $namespace_uri = '@missing';
+        $element = new \DOMElement($name, 'value', $namespace_uri);
+
+        XML::isDOMNodeOfType($element, $name, $namespace_uri);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\XML::isDOMNodeOfType
+     * @test
+     */
+    public function testIsDomNodeOfTypeEmpty()
+    {
+        $name = 'name';
+        $namespace_uri = '';
+        $element = new \DOMElement($name);
+
+        $res = XML::isDOMNodeOfType($element, $name, $namespace_uri);
+
+        $this->assertFalse($res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\XML::isDOMNodeOfType
+     * @test
+     */
+    public function testIsDomNodeOfTypeShortcut()
+    {
+        $name = 'name';
+        $namespace_uri = 'urn:oasis:names:tc:SAML:2.0:metadata';
+        $short_namespace_uri = '@md';
+        $element = new \DOMElement($name, 'value', $namespace_uri);
+
+        $res = XML::isDOMNodeOfType($element, $name, $short_namespace_uri);
+
+        $this->assertTrue($res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\XML::isDOMNodeOfType
+     * @test
+     */
+    public function testIsDomNodeOfTypeIncorrectName()
+    {
+        $name = 'name';
+        $bad_name = 'bad name';
+        $namespace_uri = 'ns';
+        $element = new \DOMElement($name, 'value', $namespace_uri);
+
+        $res = XML::isDOMNodeOfType($element, $bad_name, $namespace_uri);
+
+        $this->assertFalse($res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\XML::isDOMNodeOfType
+     * @test
+     */
+    public function testIsDomNodeOfTypeIncorrectNamespace()
+    {
+        $name = 'name';
+        $namespace_uri = 'ns';
+        $bad_namespace_uri = 'bad name';
+        $element = new \DOMElement($name, 'value', $namespace_uri);
+
+        $res = XML::isDOMNodeOfType($element, $name, $bad_namespace_uri);
+
+        $this->assertFalse($res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\XML::getDOMText
+     * @test
+     */
+    public function testGetDomTextBasic()
+    {
+        $data = 'root value';
+        $dom = new \DOMDocument();
+        $element = $dom->appendChild(new \DOMElement('root'));
+        $element->appendChild(new \DOMText($data));
+
+        $res = XML::getDOMText($element);
+        $expected = $data;
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\XML::getDOMText
+     * @test
+     */
+    public function testGetDomTextMulti()
+    {
+        $data1 = 'root value 1';
+        $data2 = 'root value 2';
+        $dom = new \DOMDocument();
+        $element = $dom->appendChild(new \DOMElement('root'));
+        $element->appendChild(new \DOMText($data1));
+        $element->appendChild(new \DOMText($data2));
+
+        $res = XML::getDOMText($element);
+        $expected = $data1 . $data2 . $data1 . $data2;
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @expectedException \SimpleSAML_Error_Exception
+     *
+     * @covers \SimpleSAML\Utils\XML::getDOMText
+     * @test
+     */
+    public function testGetDomTextIncorrectType()
+    {
+        $dom = new \DOMDocument();
+        $element = $dom->appendChild(new \DOMElement('root'));
+        $comment = $element->appendChild(new \DOMComment(''));
+
+        XML::getDOMText($element);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\XML::getDOMChildren
+     * @test
+     */
+    public function testGetDomChildrenBasic()
+    {
+        $name = 'name';
+        $namespace_uri = 'ns';
+        $dom = new \DOMDocument();
+        $element = new \DOMElement($name, 'value', $namespace_uri);
+        $dom->appendChild($element);
+
+        $res = XML::getDOMChildren($dom, $name, $namespace_uri);
+        $expected = array($element);
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\XML::getDOMChildren
+     * @test
+     */
+    public function testGetDomChildrenIncorrectType()
+    {
+        $dom = new \DOMDocument();
+        $text = new \DOMText('text');
+        $comment = new \DOMComment('comment');
+        $dom->appendChild($text);
+        $dom->appendChild($comment);
+
+        $res = XML::getDOMChildren($dom, 'name', 'ns');
+
+        $this->assertEmpty($res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\XML::getDOMChildren
+     * @test
+     */
+    public function testGetDomChildrenIncorrectName()
+    {
+        $name = 'name';
+        $bad_name = 'bad name';
+        $namespace_uri = 'ns';
+        $dom = new \DOMDocument();
+        $element = new \DOMElement($name, 'value', $namespace_uri);
+        $dom->appendChild($element);
+
+        $res = XML::getDOMChildren($dom, $bad_name, $namespace_uri);
+
+        $this->assertEmpty($res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\XML::formatDOMElement
+     * @test
+     */
+    public function testFormatDomElementBasic()
+    {
+        $dom = new \DOMDocument();
+        $root = new \DOMElement('root');
+        $dom->appendChild($root);
+        $root->appendChild(new \DOMText('text'));
+
+        XML::formatDOMElement($root);
+        $res = $dom->saveXML();
+        $expected = <<<'NOWDOC'
+<?xml version="1.0"?>
+<root>text</root>
+
+NOWDOC;
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\XML::formatDOMElement
+     * @test
+     */
+    public function testFormatDomElementNested()
+    {
+        $dom = new \DOMDocument();
+        $root = new \DOMElement('root');
+        $nested = new \DOMElement('nested');
+        $dom->appendChild($root);
+        $root->appendChild($nested);
+        $nested->appendChild(new \DOMText('text'));
+
+        XML::formatDOMElement($root);
+        $res = $dom->saveXML();
+        $expected = <<<'NOWDOC'
+<?xml version="1.0"?>
+<root>
+  <nested>text</nested>
+</root>
+
+NOWDOC;
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\XML::formatDOMElement
+     * @test
+     */
+    public function testFormatDomElementIndentBase()
+    {
+        $indent_base = 'base';
+        $dom = new \DOMDocument();
+        $root = new \DOMElement('root');
+        $nested = new \DOMElement('nested');
+        $dom->appendChild($root);
+        $root->appendChild($nested);
+        $nested->appendChild(new \DOMText('text'));
+
+        XML::formatDOMElement($root, $indent_base);
+        $res = $dom->saveXML();
+        $expected = <<<HEREDOC
+<?xml version="1.0"?>
+<root>
+$indent_base  <nested>text</nested>
+$indent_base</root>
+
+HEREDOC;
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\XML::formatDOMElement
+     * @test
+     */
+    public function testFormatDomElementTextAndChild()
+    {
+        $dom = new \DOMDocument();
+        $root = new \DOMElement('root');
+        $dom->appendChild($root);
+        $root->appendChild(new \DOMText('text'));
+        $root->appendChild(new \DOMElement('child'));
+
+        XML::formatDOMElement($root);
+        $res = $dom->saveXML();
+        $expected = <<<HEREDOC
+<?xml version="1.0"?>
+<root>text<child/></root>
+
+HEREDOC;
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\XML::formatXMLString
+     * @test
+     */
+    public function testFormatXmlStringBasic()
+    {
+        $xml = '<root><nested>text</nested></root>';
+
+        $res = XML::formatXMLString($xml);
+        $expected = <<<'NOWDOC'
+<root>
+  <nested>text</nested>
+</root>
+NOWDOC;
+
+        $this->assertEquals($expected, $res);
+    }
+
+    /**
+     * @expectedException \DOMException
+     *
+     * @covers \SimpleSAML\Utils\XML::formatXMLString
+     * @test
+     */
+    public function testFormatXmlStringMalformedXml()
+    {
+        $xml = '<root><nested>text';
+
+        XML::formatXMLString($xml);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\XML::isValid
+     * @test
+     */
+    public function testIsValidMalformedXml()
+    {
+        $xml = '<root><nested>text';
+
+        $res = XML::isValid($xml, 'unused');
+        $expected = 'Failed to parse XML string for schema validation';
+
+        $this->assertContains($expected, $res);
+    }
+
+    /**
+     * @covers \SimpleSAML\Utils\XML::isValid
+     * @test
+     */
+    public function testIsValidMetadata()
+    {
+        $schema = 'saml-schema-metadata-2.0.xsd';
+
+        $dom = $this->getMockBuilder('\DOMDocument')
+            ->setMethods(array('schemaValidate'))
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        /*
+         * Unfortunately, we cannot actually test schemaValidate. To
+         * effectively unit test this function we'd have to enable LIBXML_NONET
+         * which disables network access when loading documents. PHP does not
+         * currently support enabling this flag.
+         */
+        $dom->method('schemaValidate')
+            ->willReturn(true);
+
+        $res = XML::isValid($dom, $schema);
+
+        $this->assertTrue($res);
+    }
+}
diff --git a/tests/lib/SimpleSAML/XML/ErrorsTest.php b/tests/lib/SimpleSAML/XML/ErrorsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c73580a500bd2f007fa558ea22af35867a915a16
--- /dev/null
+++ b/tests/lib/SimpleSAML/XML/ErrorsTest.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Tests for the SQL store.
+ *
+ * For the full copyright and license information, please view the LICENSE file that was distributed with this source
+ * code.
+ *
+ * @author Sergio Gómez <sergio@uco.es>
+ * @package simplesamlphp/simplesamlphp
+ */
+
+
+namespace SimpleSAML\Test\XML;
+
+use SimpleSAML\XML\Errors;
+
+class ErrorsTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @covers \SimpleSAML\XML\Errors::begin
+     * @covers \SimpleSAML\XML\Errors::addErrors
+     * @covers \SimpleSAML\XML\Errors::end
+     * @test
+     */
+    public function loggingErrors()
+    {
+        Errors::begin();
+        $xmlstr = "<Test>Test</test>";
+        simplexml_load_string($xmlstr);
+        $errors = Errors::end();
+        $errors = Errors::formatErrors($errors);
+
+        $this->assertEquals(
+            "level=3,code=76,line=1,col=18,msg=Opening and ending tag mismatch: Test line 1 and test\n",
+            $errors
+        );
+    }
+
+    /**
+     * @covers \SimpleSAML\XML\Errors::formatError
+     * @covers \SimpleSAML\XML\Errors::formatErrors
+     * @test
+     */
+    public function formatErrors()
+    {
+        $error = new \LibXMLError();
+        $error->level = 'level';
+        $error->code = 'code';
+        $error->line = 'line';
+        $error->column = 'col';
+        $error->message = ' msg ';
+
+        $errors = Errors::formatErrors(array($error, $error));
+
+        $this->assertEquals(
+            "level=level,code=code,line=line,col=col,msg=msg\nlevel=level,code=code,line=line,col=col,msg=msg\n",
+            $errors
+        );
+    }
+}
diff --git a/tests/lib/SimpleSAML/XML/ParserTest.php b/tests/lib/SimpleSAML/XML/ParserTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7860d9523285b4a6a2597107b60bb972ab04079f
--- /dev/null
+++ b/tests/lib/SimpleSAML/XML/ParserTest.php
@@ -0,0 +1,147 @@
+<?php
+/*
+ * This file is part of the sgomezsimplesamlphp.
+ *
+ * (c) Sergio Gómez <sergio@uco.es>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+
+namespace SimpleSAML\Test\XML;
+
+use SimpleSAML\XML\Parser;
+
+class ParserTest extends \PHPUnit_Framework_TestCase
+{
+    const XMLDOC = <<< XML
+<?xml version="1.0" encoding="UTF-8"?>
+<Root>
+  <Value>Hello, World!</Value>
+</Root>
+XML;
+
+    /** @var Parser */
+    private $xml;
+
+    protected function setUp()
+    {
+        $this->xml = new Parser(static::XMLDOC);
+    }
+
+    /**
+     * @covers \SimpleSAML\XML\Parser::getValue
+     * @covers \SimpleSAML\XML\Parser::__construct
+     * @test
+     */
+    public function getValue()
+    {
+        $result = $this->xml->getValue('/Root/Value', true);
+        $this->assertEquals(
+            'Hello, World!',
+            $result
+        );
+    }
+
+    /**
+     * @covers \SimpleSAML\XML\Parser::getValue
+     * @covers \SimpleSAML\XML\Parser::__construct
+     * @test
+     */
+    public function getEmptyValue()
+    {
+        $result = $this->xml->getValue('/Root/Foo', false);
+        $this->assertEquals(
+            null,
+            $result
+        );
+    }
+
+
+
+    /**
+     * @covers \SimpleSAML\XML\Parser::getValue
+     * @covers \SimpleSAML\XML\Parser::__construct
+     * @expectedException \Exception
+     * @test
+     */
+    public function getValueException()
+    {
+        $this->xml->getValue('/Root/Foo', true);
+    }
+
+    /**
+     * @covers \SimpleSAML\XML\Parser::getValueDefault
+     * @covers \SimpleSAML\XML\Parser::__construct
+     * @test
+     */
+    public function getDefaultValue()
+    {
+        $result = $this->xml->getValueDefault('/Root/Other', 'Hello');
+        $this->assertEquals(
+            'Hello',
+            $result
+        );
+    }
+
+
+    /**
+     * @covers \SimpleSAML\XML\Parser::getValueAlternatives
+     * @covers \SimpleSAML\XML\Parser::__construct
+     * @test
+     */
+    public function getValueAlternatives()
+    {
+        $result = $this
+            ->xml
+            ->getValueAlternatives(array(
+                '/Root/Other',
+                '/Root/Value'
+            ), true)
+        ;
+
+        $this->assertEquals(
+            'Hello, World!',
+            $result
+        );
+    }
+
+    /**
+     * @covers \SimpleSAML\XML\Parser::getValueAlternatives
+     * @covers \SimpleSAML\XML\Parser::__construct
+     * @test
+     */
+    public function getEmptyValueAlternatives()
+    {
+        $result = $this
+            ->xml
+            ->getValueAlternatives(array(
+                '/Root/Foo',
+                '/Root/Bar'
+            ), false)
+        ;
+
+        $this->assertEquals(
+            null,
+            $result
+        );
+    }
+
+    /**
+     * @covers \SimpleSAML\XML\Parser::getValueAlternatives
+     * @covers \SimpleSAML\XML\Parser::__construct
+     * @expectedException \Exception
+     * @test
+     */
+    public function getValueAlternativesException()
+    {
+        $this
+            ->xml
+            ->getValueAlternatives(array(
+                '/Root/Foo',
+                '/Root/Bar'
+            ), true)
+        ;
+    }
+}
diff --git a/tests/lib/SimpleSAML/XML/Shib13/AuthnResponseTest.php b/tests/lib/SimpleSAML/XML/Shib13/AuthnResponseTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f10423c0aaa6712a134ec86507deb102c2da5f94
--- /dev/null
+++ b/tests/lib/SimpleSAML/XML/Shib13/AuthnResponseTest.php
@@ -0,0 +1,121 @@
+<?php
+/*
+ * This file is part of the sgomezsimpleshibphp.
+ *
+ * (c) Sergio Gómez <sergio@uco.es>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+
+namespace SimpleSAML\Test\XML\Shib13;
+
+use SimpleSAML\XML\Shib13\AuthnResponse;
+
+class AuthnResponseTest extends \PHPUnit_Framework_TestCase
+{
+    const XMLDOC = <<< XML
+<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"
+        AssertionID="" IssueInstant=""
+        MajorVersion="1" MinorVersion="1"
+        Issuer="Issuer"
+    >
+        <AuthenticationStatement AuthenticationInstant="" AuthenticationMethod="">
+            <Subject>
+                <NameIdentifier Format="urn:mace:shibboleth:1.0:nameIdentifier">NameIdentifier</NameIdentifier>
+            </Subject>
+        </AuthenticationStatement>
+    </Assertion>
+</Response>
+XML;
+
+    const BADXMLDOC = <<< XML
+<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"
+        AssertionID="" IssueInstant=""
+        MajorVersion="1" MinorVersion="1"
+    >
+        <AuthenticationStatement AuthenticationInstant="" AuthenticationMethod="">
+            <Subject>
+                <NameIdentifier Format="urn:mace:shibboleth:1.0:nameIdentifier">NameIdentifier</NameIdentifier>
+            </Subject>
+        </AuthenticationStatement>
+    </Assertion>
+</Response>
+XML;
+
+    /**
+     * @var AuthnResponse
+     */
+    private $xml;
+
+    protected function setUp()
+    {
+        $this->xml = new AuthnResponse();
+        $this->xml->setXML(static::XMLDOC);
+    }
+
+    /**
+     * @covers \SimpleSAML\XML\Shib13\AuthnResponse::setXML
+     * @test
+     */
+    public function setXML()
+    {
+        $this->xml = new AuthnResponse();
+        $this->xml->setXML(static::XMLDOC);
+    }
+
+    /**
+     * @covers \SimpleSAML\XML\Shib13\AuthnResponse::doXPathQuery
+     * @covers \SimpleSAML\XML\Shib13\AuthnResponse::getIssuer
+     * @covers \SimpleSAML\XML\Shib13\AuthnResponse::setXML
+     * @test
+     */
+    public function getIssuer()
+    {
+        $result = $this->xml->getIssuer();
+
+        $this->assertEquals(
+            'Issuer',
+            $result
+        );
+    }
+
+    /**
+     * @covers \SimpleSAML\XML\Shib13\AuthnResponse::getIssuer
+     * @covers \SimpleSAML\XML\Shib13\AuthnResponse::setXML
+     * @expectedException \Exception
+     * @test
+     */
+    public function getIssuerException()
+    {
+        $xml = new AuthnResponse();
+        $xml->setXML(static::BADXMLDOC);
+
+        $xml->getIssuer();
+    }
+
+    /**
+     * @covers \SimpleSAML\XML\Shib13\AuthnResponse::getNameID
+     * @covers \SimpleSAML\XML\Shib13\AuthnResponse::setXML
+     * @test
+     */
+    public function getNameID()
+    {
+        $result = $this->xml->getNameID();
+
+        $this->assertEquals(
+            array(
+                'Value' => 'NameIdentifier',
+                'Format' => 'urn:mace:shibboleth:1.0:nameIdentifier',
+            ),
+            $result
+        );
+    }
+}
diff --git a/tests/lib/SimpleSAML/XML/SignerTest.php b/tests/lib/SimpleSAML/XML/SignerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..359836b8b8d6b6a2dfec24751a011621f011c68e
--- /dev/null
+++ b/tests/lib/SimpleSAML/XML/SignerTest.php
@@ -0,0 +1,239 @@
+<?php
+
+namespace SimpleSAML\Test\Utils;
+
+use \SimpleSAML_Configuration as Configuration;
+use \SimpleSAML\XML\Signer;
+
+use \org\bovigo\vfs\vfsStream;
+
+/**
+ * Tests for SimpleSAML\XML\Signer.
+ */
+class SignerTest extends \PHPUnit_Framework_TestCase
+{
+    // openssl genrsa -out private.pem 2048
+    private $private_key = <<<'NOWDOC'
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA5LoQYYPfKdHnSnuXI+SiHfUd648Ub0sn2YO81rmnwJ168Ol/
+FZODrGpm8tsRUTz5R9uXXSnwhnWwVJW4ckiZORcp1bEUGI0zXYR387yF3Ih87UFV
+KdqodrDXNN6Id7Xrw65AVa4gjwLN2DNBF3JnjbH7zKtnqhb7u2Qer7Lidhvw4WxY
+lC9t8c+Kv3xoJOgDvlG1gRaYTZv7pxTpBA7W1YnJpOj3xiXetVmAxRcGyB0Jc8aB
+nc1WoUBGudSvjvuc01kJ+rurjgklGEFjVP9AjPfcVkdcFTXc+ECets++AmZc/kk4
+Y6RKCn3fOJlL5L0RxVSJ8obnBcS7H4rZYordfwIDAQABAoIBAH364cTkPompPIyw
+0AmMB6MafFVfZHD8Y0GSJvPaJESaOLny0fWPX4oavQNsl/g37lGe6Jr+26Ujs3CT
+WplP1V01new+cYQoWa9bpDoSj2RtpOmE/6Ri9EETnCVZoK7W+7m3A2Zt1y8N61T2
+vhZtBA5uhvMvQZTUvehz99bsX4GPTUilYHCPEq4IPkfhCMGigv/c0lWtFQhOoNUF
+BjZHezH4Z/qQolIaHpzFZT0K0e7VD4gomBegGsIqPuEJ0gProCjULqA0O5QT4gQX
+IT52pUJuU0061d4JOfDcgDI3NT2SmBBMfig71n/R88eMn0azWKN4rn4/3QjxRW3q
+tdjL0UECgYEA/ynTXtuL7G5zOezKirakuSlSbHu/3TJ+tdG5p7WOLqWADUzgqss+
+k7rxxFUxw40dBpC0LfYP5YMhXi4cBiNoT5EWhT53x/UxCilXHuz5uYcrt/Wyaqa0
+mZuyIPYuw/yTASEBUE/sE1DU82PD3IlkPmqfgEyW6j8CVyLqo/LxMWECgYEA5XoM
+aVB5jhYk8jxy0APWn4jSTm2zpTBZpzHmqTPL19B4Es18XoU+ehWA8rWGQFFwbl1f
+TTUBE1hlS9MgMMI8MK6S1Qrhi7mVrHuMaMbp0ilwDBjv+4DSqlDGDoCSLCLrDkkl
+c0uDLLFGHkfDjNmk3uiSxPZvrUiVVuwJYLGNGt8CgYEAyvjWbsptz7E8b4Nwyk7n
+UXMRYcI+qRIVwUQHTuUZKPn1lp7kyHfMW2+GCgtK/qctw58v9K+bjZJ15JkBKdDY
+lRJwu6UpWyIr1E12Q9919qMTn84OEtBxMQ+s7pNmN/ieZ3N9vAkXXXYbL1DY6IFS
+AGSIZGKIWeWtUusvgyMpwYECgYEArGDIHfxTs0YzLrv1ywh3GpQe1sdVYUs2rX+w
+s32zLETvTcCKIj6ZNgAdQzTUyk/i0yTUyBx+2FdYkGLiFX5y1Gbu6ZYo41rfchfE
+25hAYJy8DHpXG2gj18ihXpd6NilsxOhxd3BL8zCfaXOjE5USYlf2mHo+Xb7eX9Mj
+ID1/r6UCgYBos8plM27v5BzI8gghUlkFAFLmmccJXQHCUlUhT1+d8FTMEhTZGjZk
+94a7cc/ps+6UCp6hOqJ2d6w+cfteWZWP0zMcoxr2JAO9lYekIlUafoZ+mhJCCqoC
+ENg4/K7BqpAlRzCf28gUiL53wOut2CadGIoSvj0UR/Mh2eM64jTgSQ==
+-----END RSA PRIVATE KEY-----
+NOWDOC;
+
+    // openssl req -new -x509 -key private.pem -out public1.pem -days 3650
+    private $certificate1 = <<<'NOWDOC'
+-----BEGIN CERTIFICATE-----
+MIIDXTCCAkWgAwIBAgIJAIonjtIRUcfJMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTcwNjE1MTcyMTI4WhcNMjcwNjEzMTcyMTI4WjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA5LoQYYPfKdHnSnuXI+SiHfUd648Ub0sn2YO81rmnwJ168Ol/FZODrGpm
+8tsRUTz5R9uXXSnwhnWwVJW4ckiZORcp1bEUGI0zXYR387yF3Ih87UFVKdqodrDX
+NN6Id7Xrw65AVa4gjwLN2DNBF3JnjbH7zKtnqhb7u2Qer7Lidhvw4WxYlC9t8c+K
+v3xoJOgDvlG1gRaYTZv7pxTpBA7W1YnJpOj3xiXetVmAxRcGyB0Jc8aBnc1WoUBG
+udSvjvuc01kJ+rurjgklGEFjVP9AjPfcVkdcFTXc+ECets++AmZc/kk4Y6RKCn3f
+OJlL5L0RxVSJ8obnBcS7H4rZYordfwIDAQABo1AwTjAdBgNVHQ4EFgQUZHjC+k2X
+pMchyKojQngj5zOsZacwHwYDVR0jBBgwFoAUZHjC+k2XpMchyKojQngj5zOsZacw
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAETjO0RltSYxFdxmIqVIg
+7N6yKptUr46YkWY877HWmCLExHwFLTvewUvbgx7ASYA0YMErnAaVrT9IqCDbOUF+
+RCBovVuiAwwKcvag0C8nKg7rfx7KDr2E8vVV+2WzSpDECtLrpTmrPaje8TlFv8NW
+hMk80osVxnGmI7UewiMzfpRuA4tEKFxHhoQG5LVinWRTMKw6EYmrSKGLdQt/27zj
+xDe0oOS2DDIYbU/oWCqLtlTlzVqrNM7ig9HKcT0Xxgf5rwTDDzNf/dpM/Nt8DWFY
+YmLDnUolf8d/M/kglX1x5IRSN+GxTCgV8i6dIF9EPtBW/AfMz99ojmW+WOgfOLnm
+vg==
+-----END CERTIFICATE-----
+NOWDOC;
+
+    // openssl req -new -x509 -key private.pem -out public2.pem -days 3650
+    private $certificate2 = <<<'NOWDOC'
+-----BEGIN CERTIFICATE-----
+MIIDXTCCAkWgAwIBAgIJAJ6gIIeYjdQSMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTcwNjE1MTcyMTM0WhcNMjcwNjEzMTcyMTM0WjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA5LoQYYPfKdHnSnuXI+SiHfUd648Ub0sn2YO81rmnwJ168Ol/FZODrGpm
+8tsRUTz5R9uXXSnwhnWwVJW4ckiZORcp1bEUGI0zXYR387yF3Ih87UFVKdqodrDX
+NN6Id7Xrw65AVa4gjwLN2DNBF3JnjbH7zKtnqhb7u2Qer7Lidhvw4WxYlC9t8c+K
+v3xoJOgDvlG1gRaYTZv7pxTpBA7W1YnJpOj3xiXetVmAxRcGyB0Jc8aBnc1WoUBG
+udSvjvuc01kJ+rurjgklGEFjVP9AjPfcVkdcFTXc+ECets++AmZc/kk4Y6RKCn3f
+OJlL5L0RxVSJ8obnBcS7H4rZYordfwIDAQABo1AwTjAdBgNVHQ4EFgQUZHjC+k2X
+pMchyKojQngj5zOsZacwHwYDVR0jBBgwFoAUZHjC+k2XpMchyKojQngj5zOsZacw
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEA1CqpKLeYLkgRym2qeMhU
+5lKlXAYX5b0eM2SOCCjfpEnRqp2PTU/E83H0MOY6i47OfHp3LKNUj4Kze2DD+S6A
+llpmLfuLXZ/CB19sByzMrcEyUQo4mfqvKyzLhUTgygGczyocwRRZgnw1e+VwMtpf
+mgXnldomDT8CUsM2v3Xb52+JPGSCs16lRYZkgDCQEpHU4+VQxwGAGpj13NM+sidR
+ymj443jgpF6XUviaGiaS292rXMO/tW7veA1UZ2/eTKu5PF9RqDmYLiGatY1qp4tr
+QjBeEjMtDCs9Rqaety/UIaL4ZfOKffLKsKb2mjM/ew+QTwTLDg9RVv5vv2jbZrw7
+Nw==
+-----END CERTIFICATE-----
+NOWDOC;
+
+    const ROOTDIRNAME = 'testdir';
+    const DEFAULTCERTDIR = 'certdir';
+    const PRIVATEKEY = 'privatekey.pem';
+    const CERTIFICATE1 = 'certificate1.pem';
+    const CERTIFICATE2 = 'certificate2.pem';
+
+    public function setUp()
+    {
+        $this->root = vfsStream::setup(
+            self::ROOTDIRNAME,
+            null,
+            array(
+                self::DEFAULTCERTDIR => array(
+                    self::PRIVATEKEY => $this->private_key,
+                    self::CERTIFICATE1 => $this->certificate1,
+                    self::CERTIFICATE2 => $this->certificate2,
+                ),
+            )
+        );
+        $this->root_directory = vfsStream::url(self::ROOTDIRNAME);
+
+        $this->certdir = $this->root_directory.DIRECTORY_SEPARATOR.self::DEFAULTCERTDIR;
+        $this->privatekey_file = $this->certdir.DIRECTORY_SEPARATOR.self::PRIVATEKEY;
+        $this->certificate_file1 = $this->certdir.DIRECTORY_SEPARATOR.self::CERTIFICATE1;
+        $this->certificate_file2 = $this->certdir.DIRECTORY_SEPARATOR.self::CERTIFICATE2;
+
+        $this->config = Configuration::loadFromArray(array(
+            'certdir' => $this->certdir,
+        ), '[ARRAY]', 'simplesaml');
+    }
+
+    public function tearDown()
+    {
+        $this->clearInstance($this->config, '\SimpleSAML_Configuration', array());
+    }
+
+    public function testSignerBasic()
+    {
+        $res = new Signer(array());
+
+        $this->assertNotNull($res);
+    }
+
+    public function testSignBasic()
+    {
+        $node = new \DOMDocument();
+        $node->loadXML('<?xml version="1.0"?><node>value</node>');
+        $element = $node->getElementsByTagName("node")->item(0);
+
+        $doc = new \DOMDocument();
+        $insertInto = $doc->appendChild(new \DOMElement('insert'));
+
+        $signer = new Signer(array());
+        $signer->loadPrivateKey($this->privatekey_file, null, true);
+        $signer->sign($element, $insertInto);
+
+        $res = $doc->saveXML();
+
+        $this->assertContains('DigestValue', $res);
+        $this->assertContains('SignatureValue', $res);
+    }
+
+    private static function getCertificateValue($certificate)
+    {
+        $replacements = array(
+            "-----BEGIN CERTIFICATE-----",
+            "-----END CERTIFICATE-----",
+            "\n",
+        );
+
+        return str_replace($replacements, "", $certificate);
+    }
+
+    public function testSignWithCertificate()
+    {
+        $node = new \DOMDocument();
+        $node->loadXML('<?xml version="1.0"?><node>value</node>');
+        $element = $node->getElementsByTagName("node")->item(0);
+
+        $doc = new \DOMDocument();
+        $insertInto = $doc->appendChild(new \DOMElement('insert'));
+
+        $signer = new Signer(array());
+        $signer->loadPrivateKey($this->privatekey_file, null, true);
+        $signer->loadCertificate($this->certificate_file1, true);
+        $signer->sign($element, $insertInto);
+
+        $res = $doc->saveXML();
+
+        $expected = self::getCertificateValue($this->certificate1);
+
+        $this->assertContains('X509Certificate', $res);
+        $this->assertContains($expected, $res);
+    }
+
+    public function testSignWithMultiCertificate()
+    {
+        $node = new \DOMDocument();
+        $node->loadXML('<?xml version="1.0"?><node>value</node>');
+        $element = $node->getElementsByTagName("node")->item(0);
+
+        $doc = new \DOMDocument();
+        $insertInto = $doc->appendChild(new \DOMElement('insert'));
+
+        $signer = new Signer(array());
+        $signer->loadPrivateKey($this->privatekey_file, null, true);
+        $signer->loadCertificate($this->certificate_file1, true);
+        $signer->addCertificate($this->certificate_file2, true);
+        $signer->sign($element, $insertInto);
+
+        $res = $doc->saveXML();
+
+        $expected1 = self::getCertificateValue($this->certificate1);
+        $expected2 = self::getCertificateValue($this->certificate2);
+
+        $this->assertContains('X509Certificate', $res);
+        $this->assertContains($expected1, $res);
+        $this->assertContains($expected2, $res);
+    }
+
+    public function testSignMissingPrivateKey()
+    {
+        $node = new \DOMDocument();
+        $node->loadXML('<?xml version="1.0"?><node>value</node>');
+        $element = $node->getElementsByTagName("node")->item(0);
+
+        $doc = new \DOMDocument();
+        $insertInto = $doc->appendChild(new \DOMElement('insert'));
+
+        $signer = new Signer(array());
+
+        $this->setExpectedException('\Exception');
+        $signer->sign($element, $insertInto);
+    }
+
+    protected function clearInstance($service, $className, $value = null)
+    {
+        $reflectedClass = new \ReflectionClass($className);
+        $reflectedInstance = $reflectedClass->getProperty('instance');
+        $reflectedInstance->setAccessible(true);
+        $reflectedInstance->setValue($service, $value);
+        $reflectedInstance->setAccessible(false);
+    }
+}
diff --git a/tests/modules/consent/lib/Auth/Process/ConsentTest.php b/tests/modules/consent/lib/Auth/Process/ConsentTest.php
index ce864179fa61a549feccf4d54f9b0962a99ce0ef..e83659d22e054db22ba453e60a5372ac2af2f56b 100644
--- a/tests/modules/consent/lib/Auth/Process/ConsentTest.php
+++ b/tests/modules/consent/lib/Auth/Process/ConsentTest.php
@@ -8,8 +8,14 @@
 
 namespace SimpleSAML\Test\Module\consent\Auth\Process;
 
+use \SimpleSAML_Configuration as Configuration;
+
 class ConsentTest extends \PHPUnit_Framework_TestCase
 {
+    public function setUp()
+    {
+        $this->config = Configuration::loadFromArray(array(), '[ARRAY]', 'simplesaml');
+    }
 
     /**
      * Helper function to run the filter with a given configuration.
diff --git a/tests/modules/core/lib/Auth/Process/AttributeCopyTest.php b/tests/modules/core/lib/Auth/Process/AttributeCopyTest.php
index 29e696400417a31d7e0b4631e3fb946c9db80ce8..599df57bb3fa8f16110ea5065b65224268268df2 100644
--- a/tests/modules/core/lib/Auth/Process/AttributeCopyTest.php
+++ b/tests/modules/core/lib/Auth/Process/AttributeCopyTest.php
@@ -38,6 +38,26 @@ class Test_Core_Auth_Process_AttributeCopy extends PHPUnit_Framework_TestCase
         $this->assertEquals($attributes['testnew'], array('AAP'));
     }
 
+    /**
+     * Test the most basic functionality.
+     */
+    public function testArray()
+    {
+        $config = array(
+            'test' => array('new1','new2'),
+        );
+        $request = array(
+            'Attributes' => array('test' => array('AAP')),
+        );
+        $result = self::processFilter($config, $request);
+        $attributes = $result['Attributes'];
+        $this->assertArrayHasKey('test', $attributes);
+        $this->assertArrayHasKey('new1', $attributes);
+        $this->assertArrayHasKey('new2', $attributes);
+        $this->assertEquals($attributes['new1'], array('AAP'));
+        $this->assertEquals($attributes['new2'], array('AAP'));
+    }
+
     /**
      * Test that existing attributes are left unmodified.
      */
@@ -128,7 +148,7 @@ class Test_Core_Auth_Process_AttributeCopy extends PHPUnit_Framework_TestCase
     public function testWrongAttributeValue()
     {
         $config = array(
-            'test' => array('test2'),
+            'test' => 100,
         );
         $request = array(
             'Attributes' => array(
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 c88def979a42701ebf2d72897652af67edad1058..bb3d065db979d757aa9a474c55706ade050578c3 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,8 @@
 
 namespace SimpleSAML\Test\Module\saml\Auth\Source;
 
+use \SimpleSAML_Configuration as Configuration;
+
 /**
  * Custom Exception to throw to terminate a TestCase.
  */
@@ -121,6 +123,8 @@ class SP_Test extends \PHPUnit_Framework_TestCase
                 ),
             ),
         );
+
+        $this->config = Configuration::loadFromArray(array(), '[ARRAY]', 'simplesaml');
     }
 
 
diff --git a/www/resources/default.css b/www/resources/default.css
index 192b8cf0970de4f26a285ba9cda37906b9af1885..3c0371bb5cffa5add05af9b70e4fa132be57d426 100644
--- a/www/resources/default.css
+++ b/www/resources/default.css
@@ -58,7 +58,7 @@ a:link, a:visited {
 }
 a:visited {
 	color: #999;
-} 
+}
 
 a:hover, a:active {
 	color: #069;
@@ -91,11 +91,11 @@ dt {
 
 #wrap {
 	background: #fff;
-	
+
 	border: 1px solid #fff;
 	position: relative;
 	text-align: left;
-	
+
 	margin: 20px 75px 2em 75px;
 	max-width: 950px;
 }
@@ -114,7 +114,7 @@ dt {
 	text-decoration: none;
 	color: #333;
 	border-bottom: 1px solid #333;
-	
+
 }
 
 #header {
@@ -284,15 +284,15 @@ th.rowtitle {
 }
 
 .metadatabox {
-	overflow: scroll; 
-	border: 1px solid #eee; 
+	overflow: scroll;
+	border: 1px solid #eee;
 	padding: 0.5em;
 	border-radius: 3px;
 }
 div.preferredidp {
 	border: 1px dashed #ccc;
 	background: #eee;
-	padding: 2px 2em 2px 2em;	
+	padding: 2px 2em 2px 2em;
 }
 
 table.modules {
@@ -322,14 +322,14 @@ table.attributes tr.even td {
 }
 
 table.attributes tr td {
-	border-bottom: 1px solid #bbb; 
+	border-bottom: 1px solid #bbb;
 	border-left: 0px;
-	border-right: 0px;	
+	border-right: 0px;
 	background: #fff;
 	padding-top: 5px;
 	padding-left: 1em;
 	padding-right: 1em;
-	
+
 	vertical-align: top;
 }
 
@@ -374,12 +374,12 @@ caption {
 	float: left;
 }
 
-#mobilesubmit, #mobile_remember_username, #mobile_remember_me {
+#mobile_remember_username, #mobile_remember_me {
 	display: none;
 }
 
 @media handheld, only screen and (max-width: 480px), only screen and (max-device-width: 480px) {
-	#header, #languagebar, #footer, .erroricon, #loginicon, .logintext, #regularsubmit,
+	#header, #languagebar, #footer, .erroricon, .loginicon, .logintext,
 	#regular_remember_username, #regular_remember_me {
 		display: none;
 	}
@@ -392,7 +392,7 @@ caption {
 	h1,h2,h3,h4 {
 		font-size: 110%;
 	}
-		
+
 	#content {
 		margin-bottom: 10px;
 		padding: 0;