diff --git a/composer.json b/composer.json
index 7da1e1e9492c16eb20dd1d4b123e0b3ee6010975..7b394c41ac88ce5c232b840cd30c503d5002e939 100644
--- a/composer.json
+++ b/composer.json
@@ -15,13 +15,15 @@
     },
     "require": {
         "php": ">=7.0.8",
-        "aferrandini/phpqrcode": "1.0.1",
-        "jasig/phpcas": "1.3.5",
-        "cap60552/php-sip2": "1.0.0",
         "ahand/mobileesp": "dev-master",
+        "cap60552/php-sip2": "1.0.0",
+        "endroid/qr-code": "2.5.0",
+        "ghislainf/zf2-whoops": "dev-master#2649cf7caf400409942ddc3f8fe15b89381fc74e",
+        "jasig/phpcas": "1.3.5",
         "matthiasmullie/minify": "1.3.60",
         "ocramius/proxy-manager": "2.0.4",
         "oyejorge/less.php": "1.7.0.14",
+        "pear/archive_tar": "^1.4",
         "pear/file_marc": "1.2.0",
         "pear/http_request2": "2.3.0",
         "pear/validate_ispn": "dev-master",
@@ -68,9 +70,7 @@
         "zendframework/zendrest": "2.0.2",
         "zendframework/zendservice-amazon": "2.3.0",
         "zendframework/zendservice-recaptcha": "3.1.0",
-        "zf-commons/zfc-rbac": "2.6.3",
-        "ghislainf/zf2-whoops": "dev-master#2649cf7caf400409942ddc3f8fe15b89381fc74e",
-        "pear/archive_tar": "^1.4"
+        "zf-commons/zfc-rbac": "2.6.3"
     },
     "require-dev": {
         "behat/mink": "1.7.1",
diff --git a/composer.lock b/composer.lock
index 96fd7b853186d4caebd54d2a329242749f51bc82..3cc4d6945ba748d2986fe40899db55b8558282a4 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,53 +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": "9706d8ee125df82ee07e87ac799d82a1",
+    "content-hash": "f6c0185abbc60228a15ead1b186270f6",
     "packages": [
-        {
-            "name": "aferrandini/phpqrcode",
-            "version": "1.0.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/aferrandini/PHPQRCode.git",
-                "reference": "3c1c0454d43710ab5bbe19a51ad4cb41c22e3d46"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/aferrandini/PHPQRCode/zipball/3c1c0454d43710ab5bbe19a51ad4cb41c22e3d46",
-                "reference": "3c1c0454d43710ab5bbe19a51ad4cb41c22e3d46",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.0"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-0": {
-                    "PHPQRCode": "lib/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Ariel Ferrandini",
-                    "email": "arielferrandini@gmail.com",
-                    "homepage": "http://www.ferrandini.com/",
-                    "role": "Developer"
-                }
-            ],
-            "description": "PHPQRCode porting and changed for PHP 5.3 compatibility",
-            "homepage": "https://github.com/aferrandini/PHPQRCode",
-            "keywords": [
-                "barcode",
-                "php",
-                "qrcode"
-            ],
-            "abandoned": "endroid/qr-code",
-            "time": "2013-07-08T09:39:08+00:00"
-        },
         {
             "name": "ahand/mobileesp",
             "version": "dev-master",
@@ -105,6 +60,52 @@
             ],
             "time": "2017-06-06T22:20:56+00:00"
         },
+        {
+            "name": "bacon/bacon-qr-code",
+            "version": "1.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Bacon/BaconQrCode.git",
+                "reference": "5a91b62b9d37cee635bbf8d553f4546057250bee"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/5a91b62b9d37cee635bbf8d553f4546057250bee",
+                "reference": "5a91b62b9d37cee635bbf8d553f4546057250bee",
+                "shasum": ""
+            },
+            "require": {
+                "ext-iconv": "*",
+                "php": "^5.4|^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8"
+            },
+            "suggest": {
+                "ext-gd": "to generate QR code images"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "BaconQrCode": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-2-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Ben Scholzen 'DASPRiD'",
+                    "email": "mail@dasprids.de",
+                    "homepage": "http://www.dasprids.de",
+                    "role": "Developer"
+                }
+            ],
+            "description": "BaconQrCode is a QR code generator for PHP.",
+            "homepage": "https://github.com/Bacon/BaconQrCode",
+            "time": "2017-10-17T09:59:25+00:00"
+        },
         {
             "name": "cap60552/php-sip2",
             "version": "v1.0.0",
@@ -171,6 +172,75 @@
             "homepage": "https://github.com/container-interop/container-interop",
             "time": "2017-02-14T19:40:03+00:00"
         },
+        {
+            "name": "endroid/qr-code",
+            "version": "2.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/endroid/qr-code.git",
+                "reference": "a9a57ab57ac75928fcdcfb2a71179963ff6fe573"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/endroid/qr-code/zipball/a9a57ab57ac75928fcdcfb2a71179963ff6fe573",
+                "reference": "a9a57ab57ac75928fcdcfb2a71179963ff6fe573",
+                "shasum": ""
+            },
+            "require": {
+                "bacon/bacon-qr-code": "^1.0.3",
+                "ext-gd": "*",
+                "khanamiryan/qrcode-detector-decoder": "^1.0",
+                "myclabs/php-enum": "^1.5",
+                "php": ">=5.6",
+                "symfony/options-resolver": ">=2.7",
+                "symfony/property-access": ">=2.7"
+            },
+            "require-dev": {
+                "phpunit/phpunit": ">=5.7",
+                "symfony/asset": ">=2.7",
+                "symfony/browser-kit": ">=2.7",
+                "symfony/finder": ">=2.7",
+                "symfony/framework-bundle": ">=2.7",
+                "symfony/http-kernel": ">=2.7",
+                "symfony/templating": ">=2.7",
+                "symfony/twig-bundle": ">=2.7",
+                "symfony/yaml": ">=2.7"
+            },
+            "type": "symfony-bundle",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Endroid\\QrCode\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jeroen van den Enden",
+                    "email": "info@endroid.nl",
+                    "homepage": "http://endroid.nl/"
+                }
+            ],
+            "description": "Endroid QR Code",
+            "homepage": "https://github.com/endroid/QrCode",
+            "keywords": [
+                "bundle",
+                "code",
+                "endroid",
+                "flex",
+                "qr",
+                "qrcode",
+                "symfony"
+            ],
+            "time": "2017-10-22T18:56:00+00:00"
+        },
         {
             "name": "filp/whoops",
             "version": "2.1.14",
@@ -344,6 +414,56 @@
             ],
             "time": "2017-04-10T19:12:45+00:00"
         },
+        {
+            "name": "khanamiryan/qrcode-detector-decoder",
+            "version": "1.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/khanamiryan/php-qrcode-detector-decoder.git",
+                "reference": "a75482d3bc804e3f6702332bfda6cccbb0dfaa76"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/khanamiryan/php-qrcode-detector-decoder/zipball/a75482d3bc804e3f6702332bfda6cccbb0dfaa76",
+                "reference": "a75482d3bc804e3f6702332bfda6cccbb0dfaa76",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.6|^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^5.7"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Zxing\\": "lib/"
+                },
+                "files": [
+                    "lib/Common/customFunctions.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ashot Khanamiryan",
+                    "email": "a.khanamiryan@gmail.com",
+                    "homepage": "https://github.com/khanamiryan",
+                    "role": "Developer"
+                }
+            ],
+            "description": "QR code decoder / reader",
+            "homepage": "https://github.com/khanamiryan/php-qrcode-detector-decoder/",
+            "keywords": [
+                "barcode",
+                "qr",
+                "zxing"
+            ],
+            "time": "2018-04-26T11:41:33+00:00"
+        },
         {
             "name": "matthiasmullie/minify",
             "version": "1.3.60",
@@ -453,6 +573,50 @@
             ],
             "time": "2018-02-02T11:30:10+00:00"
         },
+        {
+            "name": "myclabs/php-enum",
+            "version": "1.6.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/myclabs/php-enum.git",
+                "reference": "8c5649e4ed99acd53a40eada270154dcac089f7e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/myclabs/php-enum/zipball/8c5649e4ed99acd53a40eada270154dcac089f7e",
+                "reference": "8c5649e4ed99acd53a40eada270154dcac089f7e",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35|^5.7|^6.0",
+                "squizlabs/php_codesniffer": "1.*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "MyCLabs\\Enum\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP Enum contributors",
+                    "homepage": "https://github.com/myclabs/php-enum/graphs/contributors"
+                }
+            ],
+            "description": "PHP Enum implementation",
+            "homepage": "http://github.com/myclabs/php-enum",
+            "keywords": [
+                "enum"
+            ],
+            "time": "2018-05-09T08:09:15+00:00"
+        },
         {
             "name": "ocramius/package-versions",
             "version": "1.2.0",
@@ -1535,6 +1699,118 @@
             ],
             "time": "2017-01-05T08:57:09+00:00"
         },
+        {
+            "name": "symfony/inflector",
+            "version": "v3.4.10",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/inflector.git",
+                "reference": "bc27e8f793a571313132923b5ad10fdf2843e26c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/inflector/zipball/bc27e8f793a571313132923b5ad10fdf2843e26c",
+                "reference": "bc27e8f793a571313132923b5ad10fdf2843e26c",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.5.9|>=7.0.8",
+                "symfony/polyfill-ctype": "~1.8"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.4-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Inflector\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@gmail.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Inflector Component",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "inflection",
+                "pluralize",
+                "singularize",
+                "string",
+                "symfony",
+                "words"
+            ],
+            "time": "2018-05-01T22:53:27+00:00"
+        },
+        {
+            "name": "symfony/options-resolver",
+            "version": "v3.4.10",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/options-resolver.git",
+                "reference": "f3109a6aedd20e35c3a33190e932c2b063b7b50e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/options-resolver/zipball/f3109a6aedd20e35c3a33190e932c2b063b7b50e",
+                "reference": "f3109a6aedd20e35c3a33190e932c2b063b7b50e",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.5.9|>=7.0.8"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.4-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\OptionsResolver\\": ""
+                },
+                "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 OptionsResolver Component",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "config",
+                "configuration",
+                "options"
+            ],
+            "time": "2018-01-11T07:56:07+00:00"
+        },
         {
             "name": "symfony/polyfill-ctype",
             "version": "v1.8.0",
@@ -1590,6 +1866,133 @@
             ],
             "time": "2018-04-30T19:57:29+00:00"
         },
+        {
+            "name": "symfony/polyfill-php70",
+            "version": "v1.8.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php70.git",
+                "reference": "77454693d8f10dd23bb24955cffd2d82db1007a6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/77454693d8f10dd23bb24955cffd2d82db1007a6",
+                "reference": "77454693d8f10dd23bb24955cffd2d82db1007a6",
+                "shasum": ""
+            },
+            "require": {
+                "paragonie/random_compat": "~1.0|~2.0",
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.8-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php70\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ],
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2018-04-26T10:06:28+00:00"
+        },
+        {
+            "name": "symfony/property-access",
+            "version": "v3.4.10",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/property-access.git",
+                "reference": "a6e8c778b220dfd5cd5f5dcb09f87ee232dd608a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/property-access/zipball/a6e8c778b220dfd5cd5f5dcb09f87ee232dd608a",
+                "reference": "a6e8c778b220dfd5cd5f5dcb09f87ee232dd608a",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.5.9|>=7.0.8",
+                "symfony/inflector": "~3.1|~4.0",
+                "symfony/polyfill-php70": "~1.0"
+            },
+            "require-dev": {
+                "symfony/cache": "~3.1|~4.0"
+            },
+            "suggest": {
+                "psr/cache-implementation": "To cache access methods."
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.4-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\PropertyAccess\\": ""
+                },
+                "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 PropertyAccess Component",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "access",
+                "array",
+                "extraction",
+                "index",
+                "injection",
+                "object",
+                "property",
+                "property path",
+                "reflection"
+            ],
+            "time": "2018-01-03T07:37:34+00:00"
+        },
         {
             "name": "symfony/yaml",
             "version": "v3.4.10",
@@ -7029,60 +7432,6 @@
             "homepage": "https://symfony.com",
             "time": "2018-05-16T08:49:21+00:00"
         },
-        {
-            "name": "symfony/options-resolver",
-            "version": "v3.4.10",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/options-resolver.git",
-                "reference": "f3109a6aedd20e35c3a33190e932c2b063b7b50e"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/options-resolver/zipball/f3109a6aedd20e35c3a33190e932c2b063b7b50e",
-                "reference": "f3109a6aedd20e35c3a33190e932c2b063b7b50e",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^5.5.9|>=7.0.8"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "3.4-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\OptionsResolver\\": ""
-                },
-                "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 OptionsResolver Component",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "config",
-                "configuration",
-                "options"
-            ],
-            "time": "2018-01-11T07:56:07+00:00"
-        },
         {
             "name": "symfony/polyfill-mbstring",
             "version": "v1.8.0",
@@ -7142,65 +7491,6 @@
             ],
             "time": "2018-04-26T10:06:28+00:00"
         },
-        {
-            "name": "symfony/polyfill-php70",
-            "version": "v1.8.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-php70.git",
-                "reference": "77454693d8f10dd23bb24955cffd2d82db1007a6"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/77454693d8f10dd23bb24955cffd2d82db1007a6",
-                "reference": "77454693d8f10dd23bb24955cffd2d82db1007a6",
-                "shasum": ""
-            },
-            "require": {
-                "paragonie/random_compat": "~1.0|~2.0",
-                "php": ">=5.3.3"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.8-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Polyfill\\Php70\\": ""
-                },
-                "files": [
-                    "bootstrap.php"
-                ],
-                "classmap": [
-                    "Resources/stubs"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "time": "2018-04-26T10:06:28+00:00"
-        },
         {
             "name": "symfony/polyfill-php72",
             "version": "v1.8.0",
@@ -7489,8 +7779,8 @@
     "minimum-stability": "stable",
     "stability-flags": {
         "ahand/mobileesp": 20,
-        "pear/validate_ispn": 20,
-        "ghislainf/zf2-whoops": 20
+        "ghislainf/zf2-whoops": 20,
+        "pear/validate_ispn": 20
     },
     "prefer-stable": false,
     "prefer-lowest": false,
diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php
index e6e0f2ffd9e92b5c2505ed409a84de22820c34a5..06f3fecc0eab27563bf653fec14ba32f361fc568 100644
--- a/module/VuFind/config/module.config.php
+++ b/module/VuFind/config/module.config.php
@@ -138,7 +138,7 @@ $config = [
             'VuFind\Controller\Pazpar2Controller' => 'VuFind\Controller\AbstractBaseFactory',
             'VuFind\Controller\PrimoController' => 'VuFind\Controller\AbstractBaseFactory',
             'VuFind\Controller\PrimorecordController' => 'VuFind\Controller\AbstractBaseFactory',
-            'VuFind\Controller\QRCodeController' => 'VuFind\Controller\AbstractBaseFactory',
+            'VuFind\Controller\QRCodeController' => 'VuFind\Controller\QRCodeControllerFactory',
             'VuFind\Controller\RecordController' => 'VuFind\Controller\AbstractBaseWithConfigFactory',
             'VuFind\Controller\RecordsController' => 'VuFind\Controller\AbstractBaseFactory',
             'VuFind\Controller\RelaisController' => 'VuFind\Controller\AbstractBaseFactory',
@@ -332,6 +332,7 @@ $config = [
             'VuFind\Log\Logger' => 'VuFind\Log\LoggerFactory',
             'VuFind\Mailer\Mailer' => 'VuFind\Mailer\Factory',
             'VuFind\Net\IpAddressUtils' => 'Zend\ServiceManager\Factory\InvokableFactory',
+            'VuFind\QRCode\Loader' => 'VuFind\QRCode\LoaderFactory',
             'VuFind\Recommend\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory',
             'VuFind\Record\Cache' => 'VuFind\Record\CacheFactory',
             'VuFind\Record\Loader' => 'VuFind\Record\LoaderFactory',
diff --git a/module/VuFind/src/VuFind/Controller/QRCodeController.php b/module/VuFind/src/VuFind/Controller/QRCodeController.php
index ad1714d36d55d28cad78007e4a8630028c22e3b5..81766dab4e9cf45a9502be0fbe8768c156c9a4bb 100644
--- a/module/VuFind/src/VuFind/Controller/QRCodeController.php
+++ b/module/VuFind/src/VuFind/Controller/QRCodeController.php
@@ -29,6 +29,7 @@
 namespace VuFind\Controller;
 
 use VuFind\QRCode\Loader;
+use VuFind\Session\Settings as SessionSettings;
 
 /**
  * Generates qrcodes
@@ -40,7 +41,7 @@ use VuFind\QRCode\Loader;
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     https://vufind.org Main Page
  */
-class QRCodeController extends AbstractBase
+class QRCodeController extends \Zend\Mvc\Controller\AbstractActionController
 {
     /**
      * QR Code loader
@@ -50,22 +51,22 @@ class QRCodeController extends AbstractBase
     protected $loader = false;
 
     /**
-     * Get the cover loader object
+     * Session settings
      *
-     * @return Loader
+     * @var SessionSettings
      */
-    protected function getLoader()
+    protected $sessionSettings = null;
+
+    /**
+     * Constructor
+     *
+     * @param Loader          $loader QR Code Loader
+     * @param SessionSettings $ss     Session settings
+     */
+    public function __construct(Loader $loader, SessionSettings $ss)
     {
-        // Construct object for QRCodes if it does not already exist:
-        if (!$this->loader) {
-            $this->loader = new Loader(
-                $this->getConfig(),
-                $this->serviceLocator->get('VuFindTheme\ThemeInfo')
-            );
-            $initializer = new \VuFind\ServiceManager\ServiceInitializer();
-            $initializer($this->serviceLocator, $this->loader);
-        }
-        return $this->loader;
+        $this->loader = $loader;
+        $this->sessionSettings = $ss;
     }
 
     /**
@@ -75,16 +76,13 @@ class QRCodeController extends AbstractBase
      */
     public function showAction()
     {
-        $this->disableSessionWrites();  // avoid session write timing bug
+        $this->sessionSettings->disableWrite(); // avoid session write timing bug
 
-        $this->getLoader()->loadQRCode(
-            $this->params()->fromQuery('text'),
-            [
-                'level' => $this->params()->fromQuery('level', "L"),
-                'size' => $this->params()->fromQuery('size', "3"),
-                'margin' => $this->params()->fromQuery('margin', "4"),
-            ]
-        );
+        $params = [];
+        foreach ($this->loader->getDefaults() as $param => $default) {
+            $params[$param] = $this->params()->fromQuery($param, $default);
+        }
+        $this->loader->loadQRCode($this->params()->fromQuery('text'), $params);
         return $this->displayQRCode();
     }
 
@@ -95,8 +93,8 @@ class QRCodeController extends AbstractBase
      */
     public function unavailableAction()
     {
-        $this->disableSessionWrites();  // avoid session write timing bug
-        $this->getLoader()->loadUnavailable();
+        $this->sessionSettings->disableWrite(); // avoid session write timing bug
+        $this->loader->loadUnavailable();
         return $this->displayQRCode();
     }
 
@@ -109,11 +107,10 @@ class QRCodeController extends AbstractBase
     protected function displayQRCode()
     {
         $response = $this->getResponse();
-        $headers = $response->getHeaders();
-        $headers->addHeaderLine(
-            'Content-type', $this->getLoader()->getContentType()
+        $response->getHeaders()->addHeaderLine(
+            'Content-type', $this->loader->getContentType()
         );
-        $response->setContent($this->getLoader()->getImage());
+        $response->setContent($this->loader->getImage());
         return $response;
     }
 }
diff --git a/module/VuFind/src/VuFind/Controller/QRCodeControllerFactory.php b/module/VuFind/src/VuFind/Controller/QRCodeControllerFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..dc3051698f5fd6defc126c1a7e0cd4ff759b98e2
--- /dev/null
+++ b/module/VuFind/src/VuFind/Controller/QRCodeControllerFactory.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * QRCode controller factory.
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 2018.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\Controller;
+
+use Interop\Container\ContainerInterface;
+use Zend\ServiceManager\Factory\FactoryInterface;
+
+/**
+ * QRCode controller factory.
+ *
+ * @category VuFind
+ * @package  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class QRCodeControllerFactory implements FactoryInterface
+{
+    /**
+     * Create an object
+     *
+     * @param ContainerInterface $container     Service manager
+     * @param string             $requestedName Service being created
+     * @param null|array         $options       Extra options (optional)
+     *
+     * @return object
+     *
+     * @throws ServiceNotFoundException if unable to resolve the service.
+     * @throws ServiceNotCreatedException if an exception is raised when
+     * creating a service.
+     * @throws ContainerException if any other error occurs
+     */
+    public function __invoke(ContainerInterface $container, $requestedName,
+        array $options = null
+    ) {
+        if (!empty($options)) {
+            throw new \Exception('Unexpected options sent to factory.');
+        }
+        return new $requestedName(
+            $container->get('VuFind\QRCode\Loader'),
+            $container->get('VuFind\Session\Settings')
+        );
+    }
+}
diff --git a/module/VuFind/src/VuFind/QRCode/Loader.php b/module/VuFind/src/VuFind/QRCode/Loader.php
index b358a635aad8384783e6856b7647864c09d50550..e9ec2385471c7c1a1495dfc228dc554de71911f7 100644
--- a/module/VuFind/src/VuFind/QRCode/Loader.php
+++ b/module/VuFind/src/VuFind/QRCode/Loader.php
@@ -29,7 +29,8 @@
  */
 namespace VuFind\QRCode;
 
-use PHPQRCode;
+use Endroid\QrCode\ErrorCorrectionLevel;
+use Endroid\QrCode\QrCode;
 
 /**
  * QR Code Generator
@@ -45,27 +46,11 @@ use PHPQRCode;
 class Loader extends \VuFind\ImageLoader
 {
     /**
-     * VuFind configuration settings
-     *
-     * @var \Zend\Config\Config
-     */
-    protected $config;
-
-    /**
-     * The text used to generate the QRCode
-     *
-     * @var string
-     */
-    protected $text = null;
-
-    /**
-     * The params used to generate the QRCode
+     * The default params used to generate the QRCode
      *
      * @var string
      */
-    protected $params = [
-        'level' => "L", 'size' => "3", 'margin' => "4"
-    ];
+    protected $defaultParams = ['level' => 'L', 'size' => '3', 'margin' => '4'];
 
     /**
      * Constructor
@@ -75,48 +60,123 @@ class Loader extends \VuFind\ImageLoader
      */
     public function __construct($config, \VuFindTheme\ThemeInfo $theme)
     {
-        $this->config = $config;
         $this->setThemeInfo($theme);
         $this->configuredFailImage
-            = isset($this->config->QRCode->noQRCodeAvailableImage)
-            ? $this->config->QRCode->noQRCodeAvailableImage : null;
+            = $this->config->QRCode->noQRCodeAvailableImage ?? null;
         $this->defaultFailImage = 'images/noQRCode.gif';
     }
 
+    /**
+     * Get default parameters.
+     *
+     * @return array
+     */
+    public function getDefaults()
+    {
+        return $this->defaultParams;
+    }
+
     /**
      * Set up a QR code image
      *
-     * @param string $text   The QR code text
-     * @param array  $params QR code parameters (level/size/margin)
+     * @param string $text      The QR code text
+     * @param array  $rawParams QR code parameters (level/size/margin)
      *
      * @return void
      */
-    public function loadQRCode($text,
-        $params = ['level' => "L", 'size' => "3", 'margin' => "4"]
-    ) {
+    public function loadQRCode($text, $rawParams = [])
+    {
+        // Fill in defaults:
+        $params = $rawParams + $this->defaultParams;
+
+        // Normalize parameters; when the size setting is less than 30 pixels,
+        // do some math to try to map old PHPQRCode-style settings to new
+        // Endroid\QrCode equivalents. When the size setting is 30 or higher,
+        // treat 'size' and 'margin' as literal pixel sizes.
+        $margin = $params['margin'];
+        $level = $this->mapErrorLevel($params['level']);
+        if ($params['size'] < 30) {
+            // In the old system, the margin was multiplied by the size....
+            $margin *= $params['size'];
+
+            // Do some magic math to adjust the QR code size to accommodate the
+            // length of the text and the quality level. This is probably not the
+            // smartest way to do this, but it seems good enough for VuFind's
+            // limited needs.
+            $sizeIncrement = ceil(ceil(sqrt(strlen($text))) / 10);
+            if ($level === ErrorCorrectionLevel::HIGH) {
+                $sizeIncrement *= 38;
+            } elseif ($level === ErrorCorrectionLevel::QUARTILE) {
+                $sizeIncrement *= 34;
+            } else {
+                $sizeIncrement *= 30;
+            }
+
+            // Put it all together:
+            $size = $params['size'] * $sizeIncrement - $params['margin'];
+        } else {
+            $size = $params['size'];
+        }
+
         // Sanitize parameters:
-        $this->text = $text;
-        $this->params = $params;
-        if (!$this->fetchQRCode()) {
+        if (!$this->fetchQRCode($text, $size, $margin, $level)) {
             $this->loadUnavailable();
         }
     }
 
+    /**
+     * Map an incoming error correction level parameter to a valid constant.
+     *
+     * @param string $level Error correction level parameter
+     *
+     * @return string
+     */
+    protected function mapErrorLevel($level)
+    {
+        switch (strtoupper(substr($level, 0, 1))) {
+        case '3':
+        case 'H':
+            return ErrorCorrectionLevel::HIGH;
+        case '2':
+        case 'Q':
+            return ErrorCorrectionLevel::QUARTILE;
+        case '1':
+        case 'M':
+            return ErrorCorrectionLevel::MEDIUM;
+        case '0':
+        case 'L':
+        default:
+            return ErrorCorrectionLevel::LOW;
+        }
+    }
+
     /**
      * Generate a QR code image
      *
+     * @param string $text   The QR code text
+     * @param int    $size   QR code width / height (in pixels)
+     * @param int    $margin QR code margin (in pixels)
+     * @param string $level  Error correction level constant
+     *
      * @return bool True if image displayed, false on failure.
      */
-    protected function fetchQRCode()
+    protected function fetchQRCode($text, $size, $margin, $level)
     {
-        if (empty($this->text)) {
+        if (strlen(trim($text)) == 0) {
             return false;
         }
-        $this->contentType = 'image/png';
-        $this->image = PHPQRCode\QRcode::PNG(
-            $this->text, false,
-            $this->params['level'], $this->params['size'], $this->params['margin']
-        );
+
+        // Build the code:
+        $code = new QrCode($text);
+        $code->setWriterByName('png');
+        $code->setMargin($margin);
+        $code->setErrorCorrectionLevel($level);
+        $code->setSize($size);
+        $code->setEncoding('UTF-8');
+
+        // Save the values.
+        $this->contentType = $code->getContentType();
+        $this->image = $code->writeString();
         return true;
     }
 }
diff --git a/module/VuFind/src/VuFind/QRCode/LoaderFactory.php b/module/VuFind/src/VuFind/QRCode/LoaderFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..67d5db90f84b7016fd88f6a0da36adb24524365f
--- /dev/null
+++ b/module/VuFind/src/VuFind/QRCode/LoaderFactory.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Factory for QR Code Generator
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 2018.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  QRCode_Generator
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\QRCode;
+
+use Interop\Container\ContainerInterface;
+use Zend\ServiceManager\Factory\FactoryInterface;
+
+/**
+ * Factory for QR Code Generator
+ *
+ * @category VuFind
+ * @package  QRCode_Generator
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class LoaderFactory implements FactoryInterface
+{
+    /**
+     * Create an object
+     *
+     * @param ContainerInterface $container     Service manager
+     * @param string             $requestedName Service being created
+     * @param null|array         $options       Extra options (optional)
+     *
+     * @return object
+     *
+     * @throws ServiceNotFoundException if unable to resolve the service.
+     * @throws ServiceNotCreatedException if an exception is raised when
+     * creating a service.
+     * @throws ContainerException if any other error occurs
+     */
+    public function __invoke(ContainerInterface $container, $requestedName,
+        array $options = null
+    ) {
+        if (!empty($options)) {
+            throw new \Exception('Unexpected options passed to factory.');
+        }
+        return new $requestedName(
+            $container->get('VuFind\Config\PluginManager')->get('config'),
+            $container->get('VuFindTheme\ThemeInfo')
+        );
+    }
+}