From 23236508633c15daea82e6be2ea7cb32f1be7af4 Mon Sep 17 00:00:00 2001 From: Zoey Date: Sat, 20 Apr 2024 18:38:03 +0200 Subject: [PATCH] init PHP rewrite --- .github/workflows/docker.yml | 10 +- .github/workflows/update-and-lint.yml | 17 +- COPYING | 661 +++ Dockerfile | 58 +- compose.override.yaml | 14 - compose.yaml | 15 +- renovate.json | 2 +- rootfs/etc/{tls => }/dhparam | 0 rootfs/etc/tls/certbot.ini | 15 - rootfs/html/404/bootstrap.min.css | 6 - rootfs/html/404/index.html | 26 - rootfs/html/default/bootstrap.min.css | 6 - rootfs/html/default/index.html | 29 - rootfs/usr/local/bin/healthcheck.sh | 2 +- rootfs/usr/local/bin/launch.sh | 13 +- rootfs/usr/local/bin/start.sh | 98 +- .../conf/conf.d/include/tls-ciphers.conf | 2 +- rootfs/usr/local/nginx/conf/conf.d/npm.conf | 36 - ...-name.conf => npmplus-no-server-name.conf} | 0 .../usr/local/nginx/conf/conf.d/npmplus.conf | 27 + src/composer.json | 37 + src/composer.lock | 259 + .../public/.well-known/security.txt | 0 src/public/status/index.php | 2 + src/vendor/autoload.php | 25 + .../chillerlan/php-qrcode/LICENSE-ASL-2.0 | 202 + src/vendor/chillerlan/php-qrcode/LICENSE-MIT | 21 + src/vendor/chillerlan/php-qrcode/NOTICE | 40 + src/vendor/chillerlan/php-qrcode/README.md | 168 + .../chillerlan/php-qrcode/composer.json | 79 + .../php-qrcode/src/Common/BitBuffer.php | 180 + .../php-qrcode/src/Common/ECICharset.php | 125 + .../php-qrcode/src/Common/EccLevel.php | 223 + .../src/Common/GDLuminanceSource.php | 97 + .../php-qrcode/src/Common/GF256.php | 154 + .../php-qrcode/src/Common/GenericGFPoly.php | 263 + .../src/Common/IMagickLuminanceSource.php | 78 + .../src/Common/LuminanceSourceAbstract.php | 104 + .../src/Common/LuminanceSourceInterface.php | 61 + .../php-qrcode/src/Common/MaskPattern.php | 329 ++ .../chillerlan/php-qrcode/src/Common/Mode.php | 96 + .../php-qrcode/src/Common/Version.php | 287 + .../php-qrcode/src/Data/AlphaNum.php | 137 + .../chillerlan/php-qrcode/src/Data/Byte.php | 85 + .../chillerlan/php-qrcode/src/Data/ECI.php | 155 + .../chillerlan/php-qrcode/src/Data/Hanzi.php | 205 + .../chillerlan/php-qrcode/src/Data/Kanji.php | 191 + .../chillerlan/php-qrcode/src/Data/Number.php | 182 + .../src/Data/QRCodeDataException.php | 20 + .../chillerlan/php-qrcode/src/Data/QRData.php | 263 + .../src/Data/QRDataModeAbstract.php | 61 + .../src/Data/QRDataModeInterface.php | 63 + .../php-qrcode/src/Data/QRMatrix.php | 812 +++ .../src/Data/ReedSolomonEncoder.php | 127 + .../php-qrcode/src/Decoder/Binarizer.php | 361 ++ .../php-qrcode/src/Decoder/BitMatrix.php | 430 ++ .../php-qrcode/src/Decoder/Decoder.php | 173 + .../php-qrcode/src/Decoder/DecoderResult.php | 99 + .../src/Decoder/QRCodeDecoderException.php | 20 + .../src/Decoder/ReedSolomonDecoder.php | 313 + .../src/Detector/AlignmentPattern.php | 34 + .../src/Detector/AlignmentPatternFinder.php | 283 + .../php-qrcode/src/Detector/Detector.php | 350 ++ .../php-qrcode/src/Detector/FinderPattern.php | 92 + .../src/Detector/FinderPatternFinder.php | 770 +++ .../php-qrcode/src/Detector/GridSampler.php | 181 + .../src/Detector/PerspectiveTransform.php | 182 + .../src/Detector/QRCodeDetectorException.php | 20 + .../php-qrcode/src/Detector/ResultPoint.php | 73 + .../src/Output/QRCodeOutputException.php | 20 + .../php-qrcode/src/Output/QREps.php | 173 + .../php-qrcode/src/Output/QRFpdf.php | 177 + .../php-qrcode/src/Output/QRGdImage.php | 400 ++ .../php-qrcode/src/Output/QRGdImageBMP.php | 33 + .../php-qrcode/src/Output/QRGdImageGIF.php | 33 + .../php-qrcode/src/Output/QRGdImageJPEG.php | 40 + .../php-qrcode/src/Output/QRGdImagePNG.php | 33 + .../php-qrcode/src/Output/QRGdImageWEBP.php | 33 + .../php-qrcode/src/Output/QRImage.php | 19 + .../php-qrcode/src/Output/QRImagick.php | 235 + .../php-qrcode/src/Output/QRMarkup.php | 94 + .../php-qrcode/src/Output/QRMarkupHTML.php | 51 + .../php-qrcode/src/Output/QRMarkupSVG.php | 200 + .../src/Output/QROutputAbstract.php | 261 + .../src/Output/QROutputInterface.php | 226 + .../php-qrcode/src/Output/QRString.php | 111 + .../php-qrcode/src/Output/QRStringJSON.php | 67 + .../php-qrcode/src/Output/QRStringText.php | 76 + .../chillerlan/php-qrcode/src/QRCode.php | 488 ++ .../php-qrcode/src/QRCodeException.php | 20 + .../chillerlan/php-qrcode/src/QROptions.php | 20 + .../php-qrcode/src/QROptionsTrait.php | 729 +++ .../chillerlan/php-settings-container/LICENSE | 21 + .../php-settings-container/README.md | 164 + .../php-settings-container/composer.json | 51 + .../src/SettingsContainerAbstract.php | 240 + .../src/SettingsContainerInterface.php | 78 + src/vendor/composer/ClassLoader.php | 579 ++ src/vendor/composer/InstalledVersions.php | 359 ++ LICENSE => src/vendor/composer/LICENSE | 12 +- src/vendor/composer/autoload_classmap.php | 10 + src/vendor/composer/autoload_namespaces.php | 9 + src/vendor/composer/autoload_psr4.php | 13 + src/vendor/composer/autoload_real.php | 38 + src/vendor/composer/autoload_static.php | 57 + src/vendor/composer/installed.json | 255 + src/vendor/composer/installed.php | 50 + src/vendor/composer/platform_check.php | 26 + src/vendor/phpmailer/phpmailer/.editorconfig | 15 + src/vendor/phpmailer/phpmailer/COMMITMENT | 46 + src/vendor/phpmailer/phpmailer/LICENSE | 502 ++ src/vendor/phpmailer/phpmailer/README.md | 231 + src/vendor/phpmailer/phpmailer/SECURITY.md | 37 + src/vendor/phpmailer/phpmailer/VERSION | 1 + src/vendor/phpmailer/phpmailer/composer.json | 79 + .../phpmailer/phpmailer/get_oauth_token.php | 182 + .../phpmailer/language/phpmailer.lang-af.php | 26 + .../phpmailer/language/phpmailer.lang-ar.php | 27 + .../phpmailer/language/phpmailer.lang-as.php | 35 + .../phpmailer/language/phpmailer.lang-az.php | 27 + .../phpmailer/language/phpmailer.lang-ba.php | 27 + .../phpmailer/language/phpmailer.lang-be.php | 27 + .../phpmailer/language/phpmailer.lang-bg.php | 27 + .../phpmailer/language/phpmailer.lang-bn.php | 35 + .../phpmailer/language/phpmailer.lang-ca.php | 27 + .../phpmailer/language/phpmailer.lang-cs.php | 28 + .../phpmailer/language/phpmailer.lang-da.php | 36 + .../phpmailer/language/phpmailer.lang-de.php | 28 + .../phpmailer/language/phpmailer.lang-el.php | 33 + .../phpmailer/language/phpmailer.lang-eo.php | 26 + .../phpmailer/language/phpmailer.lang-es.php | 31 + .../phpmailer/language/phpmailer.lang-et.php | 28 + .../phpmailer/language/phpmailer.lang-fa.php | 28 + .../phpmailer/language/phpmailer.lang-fi.php | 27 + .../phpmailer/language/phpmailer.lang-fo.php | 27 + .../phpmailer/language/phpmailer.lang-fr.php | 37 + .../phpmailer/language/phpmailer.lang-gl.php | 27 + .../phpmailer/language/phpmailer.lang-he.php | 27 + .../phpmailer/language/phpmailer.lang-hi.php | 35 + .../phpmailer/language/phpmailer.lang-hr.php | 27 + .../phpmailer/language/phpmailer.lang-hu.php | 27 + .../phpmailer/language/phpmailer.lang-hy.php | 27 + .../phpmailer/language/phpmailer.lang-id.php | 31 + .../phpmailer/language/phpmailer.lang-it.php | 28 + .../phpmailer/language/phpmailer.lang-ja.php | 29 + .../phpmailer/language/phpmailer.lang-ka.php | 27 + .../phpmailer/language/phpmailer.lang-ko.php | 27 + .../phpmailer/language/phpmailer.lang-lt.php | 27 + .../phpmailer/language/phpmailer.lang-lv.php | 27 + .../phpmailer/language/phpmailer.lang-mg.php | 27 + .../phpmailer/language/phpmailer.lang-mn.php | 27 + .../phpmailer/language/phpmailer.lang-ms.php | 27 + .../phpmailer/language/phpmailer.lang-nb.php | 33 + .../phpmailer/language/phpmailer.lang-nl.php | 34 + .../phpmailer/language/phpmailer.lang-pl.php | 33 + .../phpmailer/language/phpmailer.lang-pt.php | 27 + .../language/phpmailer.lang-pt_br.php | 38 + .../phpmailer/language/phpmailer.lang-ro.php | 33 + .../phpmailer/language/phpmailer.lang-ru.php | 28 + .../phpmailer/language/phpmailer.lang-si.php | 34 + .../phpmailer/language/phpmailer.lang-sk.php | 30 + .../phpmailer/language/phpmailer.lang-sl.php | 36 + .../phpmailer/language/phpmailer.lang-sr.php | 28 + .../language/phpmailer.lang-sr_latn.php | 28 + .../phpmailer/language/phpmailer.lang-sv.php | 27 + .../phpmailer/language/phpmailer.lang-tl.php | 28 + .../phpmailer/language/phpmailer.lang-tr.php | 31 + .../phpmailer/language/phpmailer.lang-uk.php | 28 + .../phpmailer/language/phpmailer.lang-vi.php | 27 + .../phpmailer/language/phpmailer.lang-zh.php | 29 + .../language/phpmailer.lang-zh_cn.php | 36 + .../phpmailer/src/DSNConfigurator.php | 245 + .../phpmailer/phpmailer/src/Exception.php | 40 + src/vendor/phpmailer/phpmailer/src/OAuth.php | 139 + .../phpmailer/src/OAuthTokenProvider.php | 44 + .../phpmailer/phpmailer/src/PHPMailer.php | 5252 +++++++++++++++++ src/vendor/phpmailer/phpmailer/src/POP3.php | 467 ++ src/vendor/phpmailer/phpmailer/src/SMTP.php | 1497 +++++ 178 files changed, 25163 insertions(+), 286 deletions(-) create mode 100644 COPYING delete mode 100644 compose.override.yaml rename rootfs/etc/{tls => }/dhparam (100%) delete mode 100644 rootfs/etc/tls/certbot.ini delete mode 100644 rootfs/html/404/bootstrap.min.css delete mode 100644 rootfs/html/404/index.html delete mode 100644 rootfs/html/default/bootstrap.min.css delete mode 100644 rootfs/html/default/index.html delete mode 100644 rootfs/usr/local/nginx/conf/conf.d/npm.conf rename rootfs/usr/local/nginx/conf/conf.d/{npm-no-server-name.conf => npmplus-no-server-name.conf} (100%) create mode 100644 rootfs/usr/local/nginx/conf/conf.d/npmplus.conf create mode 100644 src/composer.json create mode 100644 src/composer.lock rename security.txt => src/public/.well-known/security.txt (100%) create mode 100644 src/public/status/index.php create mode 100644 src/vendor/autoload.php create mode 100644 src/vendor/chillerlan/php-qrcode/LICENSE-ASL-2.0 create mode 100644 src/vendor/chillerlan/php-qrcode/LICENSE-MIT create mode 100644 src/vendor/chillerlan/php-qrcode/NOTICE create mode 100644 src/vendor/chillerlan/php-qrcode/README.md create mode 100644 src/vendor/chillerlan/php-qrcode/composer.json create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/BitBuffer.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/ECICharset.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/EccLevel.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/GDLuminanceSource.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/GF256.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/IMagickLuminanceSource.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceAbstract.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceInterface.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/MaskPattern.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/Mode.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/Version.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/Byte.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/ECI.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/Hanzi.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/Kanji.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/Number.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/QRData.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/QRDataModeAbstract.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/QRDataModeInterface.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/ReedSolomonEncoder.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Decoder/Binarizer.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Decoder/BitMatrix.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Decoder/Decoder.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Decoder/QRCodeDecoderException.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Decoder/ReedSolomonDecoder.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Detector/Detector.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QREps.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRImage.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRString.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRStringText.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/QRCode.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/QRCodeException.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/QROptions.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/QROptionsTrait.php create mode 100644 src/vendor/chillerlan/php-settings-container/LICENSE create mode 100644 src/vendor/chillerlan/php-settings-container/README.md create mode 100644 src/vendor/chillerlan/php-settings-container/composer.json create mode 100644 src/vendor/chillerlan/php-settings-container/src/SettingsContainerAbstract.php create mode 100644 src/vendor/chillerlan/php-settings-container/src/SettingsContainerInterface.php create mode 100644 src/vendor/composer/ClassLoader.php create mode 100644 src/vendor/composer/InstalledVersions.php rename LICENSE => src/vendor/composer/LICENSE (87%) create mode 100644 src/vendor/composer/autoload_classmap.php create mode 100644 src/vendor/composer/autoload_namespaces.php create mode 100644 src/vendor/composer/autoload_psr4.php create mode 100644 src/vendor/composer/autoload_real.php create mode 100644 src/vendor/composer/autoload_static.php create mode 100644 src/vendor/composer/installed.json create mode 100644 src/vendor/composer/installed.php create mode 100644 src/vendor/composer/platform_check.php create mode 100644 src/vendor/phpmailer/phpmailer/.editorconfig create mode 100644 src/vendor/phpmailer/phpmailer/COMMITMENT create mode 100644 src/vendor/phpmailer/phpmailer/LICENSE create mode 100644 src/vendor/phpmailer/phpmailer/README.md create mode 100644 src/vendor/phpmailer/phpmailer/SECURITY.md create mode 100644 src/vendor/phpmailer/phpmailer/VERSION create mode 100644 src/vendor/phpmailer/phpmailer/composer.json create mode 100644 src/vendor/phpmailer/phpmailer/get_oauth_token.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ar.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ba.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-da.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-el.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-eo.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fo.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-gl.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hy.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ms.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-nl.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-uk.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php create mode 100644 src/vendor/phpmailer/phpmailer/src/DSNConfigurator.php create mode 100644 src/vendor/phpmailer/phpmailer/src/Exception.php create mode 100644 src/vendor/phpmailer/phpmailer/src/OAuth.php create mode 100644 src/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php create mode 100644 src/vendor/phpmailer/phpmailer/src/PHPMailer.php create mode 100644 src/vendor/phpmailer/phpmailer/src/POP3.php create mode 100644 src/vendor/phpmailer/phpmailer/src/SMTP.php diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index e03d40ef..a7cc3e93 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -6,18 +6,12 @@ on: paths: - .github/workflows/docker.yml - Dockerfile - - frontend/** - - backend/** - - global/** - rootfs/** - src/** pull_request: paths: - .github/workflows/docker.yml - Dockerfile - - frontend/** - - backend/** - - global/** - rootfs/** - src/** workflow_dispatch: @@ -56,9 +50,7 @@ jobs: - name: version run: | version="$(cat .version)+$(git rev-parse --short HEAD)" - sed -i "s|\"0.0.0\"|\"$version\"|g" frontend/js/i18n/messages.json - sed -i "s|\"0.0.0\"|\"$version\"|g" frontend/package.json - sed -i "s|\"0.0.0\"|\"$version\"|g" backend/package.json + #sed -i "s|\"0.0.0\"|\"$version\"|g" src/ - name: Build uses: docker/build-push-action@v5 if: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/update-and-lint.yml b/.github/workflows/update-and-lint.yml index 7de0d0f8..e4b21f60 100644 --- a/.github/workflows/update-and-lint.yml +++ b/.github/workflows/update-and-lint.yml @@ -3,6 +3,7 @@ on: push: branches: - develop + - php schedule: - cron: "0 */6 * * *" workflow_dispatch: @@ -13,21 +14,9 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 21 - - name: eslint + - name: lint run: | - cd backend - yarn install --no-lockfile - yarn eslint . --fix - - name: update - run: | - curl -L https://unpkg.com/xregexp/xregexp-all.js -o rootfs/nftd/xregexp-all.js - curl -L https://unpkg.com/showdown/dist/showdown.min.js -o rootfs/nftd/showdown.min.js - curl -L https://code.jquery.com/jquery-"$(git ls-remote --tags https://github.com/jquery/jquery | cut -d/ -f3 | sort -V | tail -1)".min.js -o rootfs/nftd/jquery.min.js - curl -L https://cdn.jsdelivr.net/npm/bootstrap@"$(git ls-remote --tags https://github.com/twbs/bootstrap v3.3.* | cut -d/ -f3 | sort -V | tail -1)"/dist/css/bootstrap.min.css -o rootfs/html/404/bootstrap.min.css - curl -L https://cdn.jsdelivr.net/npm/bootstrap@"$(git ls-remote --tags https://github.com/twbs/bootstrap v3.3.* | cut -d/ -f3 | sort -V | tail -1)"/dist/css/bootstrap.min.css -o rootfs/html/default/bootstrap.min.css + cd src - name: nginxbeautifier run: | yarn global add nginxbeautifier diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..0ad25db4 --- /dev/null +++ b/COPYING @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/Dockerfile b/Dockerfile index bc4e23a4..aa7bdc13 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,40 +1,3 @@ -# syntax=docker/dockerfile:labs -FROM --platform="$BUILDPLATFORM" alpine:3.19.1 as frontend -COPY frontend /build/frontend -COPY global/certbot-dns-plugins.json /build/frontend/certbot-dns-plugins.json -ARG NODE_ENV=production \ - NODE_OPTIONS=--openssl-legacy-provider -WORKDIR /build/frontend -RUN apk upgrade --no-cache -a && \ - apk add --no-cache ca-certificates nodejs yarn git python3 build-base && \ - yarn global add clean-modules && \ - yarn --no-lockfile install && \ - clean-modules --yes && \ - yarn --no-lockfile build && \ - yarn cache clean --all -COPY darkmode.css /build/frontend/dist/css/darkmode.css -COPY security.txt /build/frontend/dist/.well-known/security.txt - - -FROM --platform="$BUILDPLATFORM" alpine:3.19.1 as backend -SHELL ["/bin/ash", "-eo", "pipefail", "-c"] -COPY backend /build/backend -COPY global/certbot-dns-plugins.json /build/backend/certbot-dns-plugins.json -ARG NODE_ENV=production \ - TARGETARCH -WORKDIR /build/backend -RUN apk upgrade --no-cache -a && \ - apk add --no-cache ca-certificates nodejs-current yarn && \ - yarn global add clean-modules && \ - if [ "$TARGETARCH" = "amd64" ]; then \ - npm_config_target_platform=linux npm_config_target_arch=x64 yarn install --no-lockfile; \ - elif [ "$TARGETARCH" = "arm64" ]; then \ - npm_config_target_platform=linux npm_config_target_arch=arm64 yarn install --no-lockfile; \ - fi && \ - clean-modules --yes && \ - yarn cache clean --all - - FROM --platform="$BUILDPLATFORM" alpine:3.19.1 as crowdsec SHELL ["/bin/ash", "-eo", "pipefail", "-c"] @@ -64,18 +27,19 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] ARG CRS_VER=v4.1.0 COPY rootfs / -COPY --from=zoeyvid/certbot-docker:34 /usr/local /usr/local +COPY src /html/app COPY --from=zoeyvid/curl-quic:380 /usr/local/bin/curl /usr/local/bin/curl RUN apk upgrade --no-cache -a && \ apk add --no-cache ca-certificates tzdata tini \ bash nano \ - nodejs-current \ openssl apache2-utils \ lua5.1-lzlib lua5.1-socket \ coreutils grep findutils jq shadow su-exec \ - luarocks5.1 lua5.1-dev lua5.1-sec build-base git yarn && \ + luarocks5.1 lua5.1-dev lua5.1-sec build-base git \ + fcgi php83-fpm && \ curl https://raw.githubusercontent.com/acmesh-official/acme.sh/master/acme.sh | sh -s -- --install-online --home /usr/local/acme.sh --nocron && \ + ln -s /usr/local/acme.sh/acme.sh /usr/local/bin/acme.sh && \ git clone https://github.com/coreruleset/coreruleset --branch "$CRS_VER" /tmp/coreruleset && \ mkdir -v /usr/local/nginx/conf/conf.d/include/coreruleset && \ mv -v /tmp/coreruleset/crs-setup.conf.example /usr/local/nginx/conf/conf.d/include/coreruleset/crs-setup.conf.example && \ @@ -86,11 +50,8 @@ RUN apk upgrade --no-cache -a && \ luarocks-5.1 install lua-resty-http && \ luarocks-5.1 install lua-resty-string && \ luarocks-5.1 install lua-resty-openssl && \ - yarn global add nginxbeautifier && \ - apk del --no-cache luarocks5.1 lua5.1-dev lua5.1-sec build-base git yarn + apk del --no-cache luarocks5.1 lua5.1-dev lua5.1-sec build-base git -COPY --from=backend /build/backend /app -COPY --from=frontend /build/frontend/dist /html/frontend COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/lib/plugins /usr/local/nginx/lib/lua/plugins COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/lib/crowdsec.lua /usr/local/nginx/lib/lua/crowdsec.lua COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/templates/ban.html /usr/local/nginx/conf/conf.d/include/ban.html @@ -98,18 +59,12 @@ COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/templates/captcha.html COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf /usr/local/nginx/conf/conf.d/include/crowdsec.conf COPY --from=crowdsec /src/crowdsec-nginx-bouncer/nginx/crowdsec_nginx.conf /usr/local/nginx/conf/conf.d/include/crowdsec_nginx.conf -RUN ln -s /usr/local/acme.sh/acme.sh /usr/local/bin/acme.sh && \ - ln -s /app/password-reset.js /usr/local/bin/password-reset.js && \ - ln -s /app/sqlite-vaccum.js /usr/local/bin/sqlite-vaccum.js && \ - ln -s /app/index.js /usr/local/bin/index.js - ENV NODE_ENV=production \ NODE_CONFIG_DIR=/data/etc/npm \ DB_SQLITE_FILE=/data/etc/npm/database.sqlite ENV PUID=0 \ PGID=0 \ - NIBEP=48693 \ GOAIWSP=48683 \ NPM_PORT=81 \ GOA_PORT=91 \ @@ -141,8 +96,7 @@ ENV PUID=0 \ GOA=false \ GOACLA="--agent-list --real-os --double-decode --anonymize-ip --anonymize-level=1 --keep-last=30 --with-output-resolver --no-query-string" \ PHP81=false \ - PHP82=false \ - PHP83=false + PHP82=false WORKDIR /app ENTRYPOINT ["tini", "--", "entrypoint.sh"] diff --git a/compose.override.yaml b/compose.override.yaml deleted file mode 100644 index 709aaea1..00000000 --- a/compose.override.yaml +++ /dev/null @@ -1,14 +0,0 @@ -services: - npmplus-caddy: - container_name: npmplus-caddy - image: zoeyvid/npmplus:caddy - restart: always - network_mode: bridge - ports: - - "80:80" - environment: - - "TZ=Europe/Berlin" - - npmplus: - environment: - - "DISABLE_HTTP=true" # disables nginx to listen on port 80, default false diff --git a/compose.yaml b/compose.yaml index cb8b32ed..b2bd31db 100644 --- a/compose.yaml +++ b/compose.yaml @@ -12,7 +12,6 @@ services: - "TZ=Europe/Berlin" # set timezone, required # - "PUID=1000" # set group id, default 0 (root) # - "PGID=1000" # set user id, default 0 (root) -# - "NIBEP=48694" # internal port of the NOMplus API, always bound to 127.0.0.1, default 48693, you need to change it, if you want to run multiple npm instances in network mode host # - "GOAIWSP=48684" # internal port of goaccess, always bound to 127.0.0.1, default 48683, you need to change it, if you want to run multiple npm with goaccess instances in network mode host # - "NPM_PORT=82" # Port the NPM UI should be bound to, default 81, you need to change it, if you want to run multiple npm instances in network mode host # - "NPM_PORT=92" # Port the goaccess should be bound to, default 91, you need to change it, if you want to run multiple npm with goaccess instances in network mode host @@ -42,10 +41,20 @@ services: # - "IPRT=3" # Set how many hours should be between updating ip ranges from aws and cloudflare, default 1, ignored when SKIP_IP_RANGES is true # - "GOA=true" # Enables goaccess, overrides LOGROTATE, default false --- if you download the GeoLite2-Country.mmdb, GeoLite2-City.mmdb AND GeoLite2-ASN.mmdb file from MaxMind and place them in /opt/npm/etc/goaccess/geoip it will automatically enable GeoIP in goaccess after restarting NPMplus (no need to change GOACLA below), you may also use the compose.geoip.yaml # - "GOACLA=--agent-list --real-os --double-decode --anonymize-ip --anonymize-level=2 --keep-last=7 --with-output-resolver --no-query-string" # Arguments that should be passed to goaccess, default: https://github.com/ZoeyVid/NPMplus/blob/develop/rootfs/usr/local/bin/launch.sh#L50 and: --agent-list --real-os --double-decode --anonymize-ip --anonymize-level=1 --keep-last=30 --with-output-resolver --no-query-string +# - "PHP_APKS=php-pecl-apcu php-pecl-redis" # Add php extensions, see available packages here: https://pkgs.alpinelinux.org/packages?branch=v3.19&repo=community&arch=x86_64&name=php-*, default none # - "PHP81=true" # Activate PHP81, default false # - "PHP81_APKS=php81-curl php81-openssl" # Add php extensions, see available packages here: https://pkgs.alpinelinux.org/packages?branch=v3.19&repo=community&arch=x86_64&name=php81-*, default none # - "PHP82=true" # Activate PHP82, default false # - "PHP82_APKS=php82-curl php-82-openssl" # Add php extensions, see available packages here: https://pkgs.alpinelinux.org/packages?branch=v3.19&repo=community&arch=x86_64&name=php82-*, default none -# - "PHP83=true" # Activate PHP83, default false # - "PHP83_APKS=php83-curl php83-openssl" # Add php extensions, see available packages here: https://pkgs.alpinelinux.org/packages?branch=v3.19&repo=community&arch=x86_64&name=php83-*, default none -# - "PHP_APKS=php-pecl-apcu php-pecl-redis" # Add php extensions, see available packages here: https://pkgs.alpinelinux.org/packages?branch=v3.19&repo=community&arch=x86_64&name=php-*, default none + +# This can be used with DISABLE_HTTP=true, to force HTTPS redirects for every host +# npmplus-caddy: +# container_name: npmplus-caddy +# image: zoeyvid/npmplus:caddy +# restart: always +# network_mode: bridge +# ports: +# - "80:80" +# environment: +# - "TZ=Europe/Berlin" diff --git a/renovate.json b/renovate.json index 0897d9b3..24277269 100644 --- a/renovate.json +++ b/renovate.json @@ -2,7 +2,7 @@ "extends": [ "config:base" ], - "baseBranches": [], + "baseBranches": ["develop", "php"], "includeForks": true, "automerge": false, "branchPrefix": "renovate-deps-update-", diff --git a/rootfs/etc/tls/dhparam b/rootfs/etc/dhparam similarity index 100% rename from rootfs/etc/tls/dhparam rename to rootfs/etc/dhparam diff --git a/rootfs/etc/tls/certbot.ini b/rootfs/etc/tls/certbot.ini deleted file mode 100644 index a9a0f909..00000000 --- a/rootfs/etc/tls/certbot.ini +++ /dev/null @@ -1,15 +0,0 @@ -agree-tos = true -non-interactive = true -webroot-path = /tmp/acme-challenge - -new-key= true -key-type = ecdsa -must-staple = true -no-reuse-key = true -rsa-key-size = 4096 -elliptic-curve = secp384r1 - -# An example of using an alternate ACME server that uses EAB credentials -# server = https://dv.acme-v02.api.pki.goog/directory -# eab-kid = somestringofstuffwithoutquotes -# eab-hmac-key = yaddayaddahexhexnotquoted diff --git a/rootfs/html/404/bootstrap.min.css b/rootfs/html/404/bootstrap.min.css deleted file mode 100644 index ed3905e0..00000000 --- a/rootfs/html/404/bootstrap.min.css +++ /dev/null @@ -1,6 +0,0 @@ -/*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/rootfs/html/404/index.html b/rootfs/html/404/index.html deleted file mode 100644 index 377251c1..00000000 --- a/rootfs/html/404/index.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - 404 Not Found - - - - -
-
-

404 Not Found

-
-

- Powered by NPMplus - -

-
- - diff --git a/rootfs/html/default/bootstrap.min.css b/rootfs/html/default/bootstrap.min.css deleted file mode 100644 index ed3905e0..00000000 --- a/rootfs/html/default/bootstrap.min.css +++ /dev/null @@ -1,6 +0,0 @@ -/*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/rootfs/html/default/index.html b/rootfs/html/default/index.html deleted file mode 100644 index 64f6a6a2..00000000 --- a/rootfs/html/default/index.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - Default Site - - - - -
-
-

Congratulations!

-

You've successfully started NPMplus.

-

If you're seeing this site then you're trying to access a host that isn't set up yet.

-

Log in to the Admin panel to get started.

-
-

- Powered by NPMplus - -

-
- - diff --git a/rootfs/usr/local/bin/healthcheck.sh b/rootfs/usr/local/bin/healthcheck.sh index c234aa79..c168b060 100755 --- a/rootfs/usr/local/bin/healthcheck.sh +++ b/rootfs/usr/local/bin/healthcheck.sh @@ -31,7 +31,7 @@ if [ "$GOA_IPV6_BINDING" != "[::]" ] && [ "$GOA_IPV4_BINDING" != "0.0.0.0" ]; th fi fi -if (if [ "$GOA" = "true" ]; then [ -f /tmp/goa/index.html ] && nc -z "$HCGOA_IP" "$GOA_PORT"; fi && if [ "$PHP81" = true ]; then cgi-fcgi -bind -connect /run/php81.sock > /dev/null 2>&1; fi && if [ "$PHP82" = true ]; then cgi-fcgi -bind -connect /run/php82.sock > /dev/null 2>&1; fi && if [ "$PHP83" = true ]; then cgi-fcgi -bind -connect /run/php83.sock > /dev/null 2>&1; fi && [ "$(curl -sk https://"$HCNPM_IP":"$NPM_PORT"/api/ | jq --raw-output .status)" = "OK" ]); then +if (if [ "$GOA" = "true" ]; then [ -f /tmp/goa/index.html ] && nc -z "$HCGOA_IP" "$GOA_PORT"; fi && if [ "$PHP81" = true ]; then cgi-fcgi -bind -connect /run/php81.sock > /dev/null 2>&1; fi && if [ "$PHP82" = true ]; then cgi-fcgi -bind -connect /run/php82.sock > /dev/null 2>&1; fi && cgi-fcgi -bind -connect /run/php83.sock > /dev/null 2>&1 && [ "$(curl -sk https://"$HCNPM_IP":"$NPM_PORT"/status | jq -r .status)" = "ok" ]); then echo "OK" exit 0 else diff --git a/rootfs/usr/local/bin/launch.sh b/rootfs/usr/local/bin/launch.sh index 3294bc57..851544e0 100755 --- a/rootfs/usr/local/bin/launch.sh +++ b/rootfs/usr/local/bin/launch.sh @@ -36,19 +36,18 @@ if [ "$PHP82" = "true" ]; then fi fi -if [ "$PHP83" = "true" ]; then - if ! PHP_INI_SCAN_DIR=/data/php/83/conf.d php-fpm83 -c /data/php/83 -y /data/php/83/php-fpm.conf -FORt > /dev/null 2>&1; then - PHP_INI_SCAN_DIR=/data/php/83/conf.d php-fpm83 -c /data/php/83 -y /data/php/83/php-fpm.conf -FORt - sleep inf - fi +if ! PHP_INI_SCAN_DIR=/data/php/83/conf.d php-fpm83 -c /data/php/83 -y /data/php/83/php-fpm.conf -FORt > /dev/null 2>&1; then + PHP_INI_SCAN_DIR=/data/php/83/conf.d php-fpm83 -c /data/php/83 -y /data/php/83/php-fpm.conf -FORt + sleep inf fi +nginx -e stderr & if [ "$PHP81" = "true" ]; then PHP_INI_SCAN_DIR=/data/php/81/conf.d php-fpm81 -c /data/php/81 -y /data/php/81/php-fpm.conf -FOR; fi & if [ "$PHP82" = "true" ]; then PHP_INI_SCAN_DIR=/data/php/82/conf.d php-fpm82 -c /data/php/82 -y /data/php/82/php-fpm.conf -FOR; fi & -if [ "$PHP83" = "true" ]; then PHP_INI_SCAN_DIR=/data/php/83/conf.d php-fpm83 -c /data/php/83 -y /data/php/83/php-fpm.conf -FOR; fi & +PHP_INI_SCAN_DIR=/data/php/83/conf.d php-fpm83 -c /data/php/83 -y /data/php/83/php-fpm.conf -FOR & if [ "$LOGROTATE" = "true" ]; then while true; do logrotate --verbose --state /data/etc/logrotate.status /etc/logrotate; sleep 25h; done; fi & # shellcheck disable=SC2086 if [ "$GOA" = "true" ]; then while true; do goaccess --no-global-config --num-tests=0 --tz="$TZ" --date-format="%d/%b/%Y" --time-format="%H:%M:%S" --log-format='[%d:%t %^] %v %h %T "%r" %s %b %b %R %u' --no-ip-validation --addr=127.0.0.1 --port="$GOAIWSP" \ -f /data/nginx/access.log --real-time-html -o /tmp/goa/index.html --persist --restore --db-path=/data/etc/goaccess/data -b /etc/goaccess/browsers.list -b /etc/goaccess/podcast.list $GOACLA; done; fi & -aio.sh & +#aio.sh & index.js diff --git a/rootfs/usr/local/bin/start.sh b/rootfs/usr/local/bin/start.sh index 8c5dcb82..8b799446 100755 --- a/rootfs/usr/local/bin/start.sh +++ b/rootfs/usr/local/bin/start.sh @@ -51,11 +51,6 @@ if ! echo "$PGID" | grep -q "^[0-9]\+$"; then sleep inf fi -if ! echo "$NIBEP" | grep -q "^[0-9]\+$"; then - echo "NIBEP needs to be a number." - sleep inf -fi - if ! echo "$GOAIWSP" | grep -q "^[0-9]\+$"; then echo "GOAIWSP needs to be a number." sleep inf @@ -221,6 +216,11 @@ if [ -n "$PHP82_APKS" ] && ! echo "$PHP82_APKS" | grep -q "^[a-z0-9 _-]\+$"; the sleep inf fi +if [ -n "$PHP83_APKS" ] && ! echo "$PHP83_APKS" | grep -q "^[a-z0-9 _-]\+$"; then + echo "PHP83_APKS can consist of lower letters a-z, numbers 0-9, spaces, underscores and hyphens." + sleep inf +fi + if [ -n "$NC_AIO" ] && ! echo "$NC_AIO" | grep -q "^true$\|^false$"; then echo "NC_AIO needs to be true or false." @@ -263,28 +263,21 @@ if [ -s /data/etc/goaccess/geoip/GeoLite2-Country.mmdb ] && [ -s /data/etc/goacc fi -if [ "$PHP81" = "true" ] || [ "$PHP82" = "true" ] || [ "$PHP83" = "true" ]; then +# From https://github.com/nextcloud/all-in-one/pull/1377/files +if [ -n "$PHP_APKS" ]; then + for apk in $(echo "$PHP_APKS" | tr " " "\n"); do + if ! echo "$apk" | grep -q "^php-.*$"; then + echo "$apk is a non allowed value." + echo "It needs to start with \"php-\"." + echo "It is set to \"$apk\"." + sleep inf + fi - apk add --no-cache fcgi - - # From https://github.com/nextcloud/all-in-one/pull/1377/files - if [ -n "$PHP_APKS" ]; then - for apk in $(echo "$PHP_APKS" | tr " " "\n"); do - - if ! echo "$apk" | grep -q "^php-.*$"; then - echo "$apk is a non allowed value." - echo "It needs to start with \"php-\"." - echo "It is set to \"$apk\"." - sleep inf - fi - - echo "Installing $apk via apk..." - if ! apk add --no-cache "$apk" > /dev/null 2>&1; then - echo "The apk \"$apk\" was not installed!" - fi - - done - fi + echo "Installing $apk via apk..." + if ! apk add --no-cache "$apk" > /dev/null 2>&1; then + echo "The apk \"$apk\" was not installed!" + fi + done fi if [ "$PHP81" = "true" ]; then @@ -294,7 +287,6 @@ if [ "$PHP81" = "true" ]; then # From https://github.com/nextcloud/all-in-one/pull/1377/files if [ -n "$PHP81_APKS" ]; then for apk in $(echo "$PHP81_APKS" | tr " " "\n"); do - if ! echo "$apk" | grep -q "^php81-.*$"; then echo "$apk is a non allowed value." echo "It needs to start with \"php81-\"." @@ -306,7 +298,6 @@ if [ "$PHP81" = "true" ]; then if ! apk add --no-cache "$apk" > /dev/null 2>&1; then echo "The apk \"$apk\" was not installed!" fi - done fi @@ -327,7 +318,6 @@ if [ "$PHP82" = "true" ]; then # From https://github.com/nextcloud/all-in-one/pull/1377/files if [ -n "$PHP82_APKS" ]; then for apk in $(echo "$PHP82_APKS" | tr " " "\n"); do - if ! echo "$apk" | grep -q "^php82-.*$"; then echo "$apk is a non allowed value." echo "It needs to start with \"php82-\"." @@ -339,7 +329,6 @@ if [ "$PHP82" = "true" ]; then if ! apk add --no-cache "$apk" > /dev/null 2>&1; then echo "The apk \"$apk\" was not installed!" fi - done fi @@ -353,38 +342,27 @@ elif [ "$FULLCLEAN" = "true" ]; then rm -vrf /data/php/82 fi -if [ "$PHP83" = "true" ]; then +# From https://github.com/nextcloud/all-in-one/pull/1377/files +if [ -n "$PHP83_APKS" ]; then + for apk in $(echo "$PHP83_APKS" | tr " " "\n"); do + if ! echo "$apk" | grep -q "^php83-.*$"; then + echo "$apk is a non allowed value." + echo "It needs to start with \"php83-\"." + echo "It is set to \"$apk\"." + sleep inf + fi - apk add --no-cache php83-fpm - - # From https://github.com/nextcloud/all-in-one/pull/1377/files - if [ -n "$PHP83_APKS" ]; then - for apk in $(echo "$PHP83_APKS" | tr " " "\n"); do - - if ! echo "$apk" | grep -q "^php83-.*$"; then - echo "$apk is a non allowed value." - echo "It needs to start with \"php83-\"." - echo "It is set to \"$apk\"." - sleep inf - fi - - echo "Installing $apk via apk..." - if ! apk add --no-cache "$apk" > /dev/null 2>&1; then - echo "The apk \"$apk\" was not installed!" - fi - - done - fi - - mkdir -vp /data/php - cp -varnT /etc/php83 /data/php/83 - sed -i "s|listen =.*|listen = /run/php83.sock|" /data/php/83/php-fpm.d/www.conf - sed -i "s|;error_log =.*|error_log = /proc/self/fd/2|g" /data/php/83/php-fpm.conf - sed -i "s|include=.*|include=/data/php/83/php-fpm.d/*.conf|g" /data/php/83/php-fpm.conf - -elif [ "$FULLCLEAN" = "true" ]; then - rm -vrf /data/php/83 + echo "Installing $apk via apk..." + if ! apk add --no-cache "$apk" > /dev/null 2>&1; then + echo "The apk \"$apk\" was not installed!" + fi + done fi +mkdir -vp /data/php +cp -varnT /etc/php83 /data/php/83 +sed -i "s|listen =.*|listen = /run/php83.sock|" /data/php/83/php-fpm.d/www.conf +sed -i "s|;error_log =.*|error_log = /proc/self/fd/2|g" /data/php/83/php-fpm.conf +sed -i "s|include=.*|include=/data/php/83/php-fpm.d/*.conf|g" /data/php/83/php-fpm.conf if [ "$LOGROTATE" = "true" ]; then apk add --no-cache logrotate diff --git a/rootfs/usr/local/nginx/conf/conf.d/include/tls-ciphers.conf b/rootfs/usr/local/nginx/conf/conf.d/include/tls-ciphers.conf index 698e2cce..49236ed7 100644 --- a/rootfs/usr/local/nginx/conf/conf.d/include/tls-ciphers.conf +++ b/rootfs/usr/local/nginx/conf/conf.d/include/tls-ciphers.conf @@ -6,7 +6,7 @@ ssl_stapling_verify on; ssl_session_timeout 1d; ssl_session_cache shared:MozSSL:10m; # about 40000 sessions ssl_session_tickets off; -ssl_dhparam /etc/tls/dhparam; +ssl_dhparam /etc/dhparam; # intermediate configuration. tweak to your needs. ssl_protocols TLSv1.2 TLSv1.3; diff --git a/rootfs/usr/local/nginx/conf/conf.d/npm.conf b/rootfs/usr/local/nginx/conf/conf.d/npm.conf deleted file mode 100644 index 1898765b..00000000 --- a/rootfs/usr/local/nginx/conf/conf.d/npm.conf +++ /dev/null @@ -1,36 +0,0 @@ -server { - http3 off; - listen 81 ssl default_server; - listen [::]:81 ssl default_server; - - server_name _; - include conf.d/include/brotli.conf; - include conf.d/include/force-tls.conf; - include conf.d/include/tls-ciphers.conf; - include conf.d/include/block-exploits.conf; - - modsecurity on; - modsecurity_rules_file /usr/local/nginx/conf/conf.d/include/modsecurity.conf; - - #ssl_certificate ; - #ssl_certificate_key ; - #ssl_trusted_certificate ; - - location /api { - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - - include conf.d/include/proxy-location.conf; - - rewrite ^/api(/.*)$ $1 break; - proxy_pass http://127.0.0.1:48693; - } - - location / { - root /html/frontend; - if ($request_uri ~ ^/(.*)\.html$) { - return 302 /$1; - } - try_files $uri $uri.html $uri/ /index.html; - } -} diff --git a/rootfs/usr/local/nginx/conf/conf.d/npm-no-server-name.conf b/rootfs/usr/local/nginx/conf/conf.d/npmplus-no-server-name.conf similarity index 100% rename from rootfs/usr/local/nginx/conf/conf.d/npm-no-server-name.conf rename to rootfs/usr/local/nginx/conf/conf.d/npmplus-no-server-name.conf diff --git a/rootfs/usr/local/nginx/conf/conf.d/npmplus.conf b/rootfs/usr/local/nginx/conf/conf.d/npmplus.conf new file mode 100644 index 00000000..ed5cd4ce --- /dev/null +++ b/rootfs/usr/local/nginx/conf/conf.d/npmplus.conf @@ -0,0 +1,27 @@ +server { + http3 off; + listen 81 ssl default_server; + listen [::]:81 ssl default_server; + + server_name _; + include conf.d/include/brotli.conf; + include conf.d/include/force-tls.conf; + include conf.d/include/tls-ciphers.conf; + include conf.d/include/block-exploits.conf; + + #ssl_certificate ; + #ssl_certificate_key ; + #ssl_trusted_certificate ; + + location / { + alias /html/app/public/; + + location ~ [^/]\.php(/|$) { + fastcgi_pass php83; + fastcgi_split_path_info ^(.+?\.php)(/.*)$; + if (!-f $document_root$fastcgi_script_name) { + return 404; + } + } + } +} diff --git a/src/composer.json b/src/composer.json new file mode 100644 index 00000000..c185d5b0 --- /dev/null +++ b/src/composer.json @@ -0,0 +1,37 @@ +{ + "name": "zoeyvid/npmplus", + "description": "WebUI for NPMplus, which manages nginx and acme.sh", + "type": "project", + "require": { + "phpmailer/phpmailer": "6.9.1", + "chillerlan/php-qrcode": "5.0.2" + }, + "license": "AGPL-3.0", + "version": "v0.0.1-alpha", + "authors": [ + { + "name": "Zoey", + "email": "zoey@z0ey.de", + "homepage": "https://z0ey.de" + }, + { + "name": "David", + "email": "david@davidcraft.de", + "homepage": "https://davidcraft.de" + }, + { + "name": "ZoeyVid", + "email": "zoeyvid@zvcdn.de", + "homepage": "https://zoeyvid.de" + } + ], + "minimum-stability": "alpha", + "support": { + "email": "zoey@z0ey.de", + "issues": "https://github.com/ZoeyVid/booking/issues", + "forum": "https://github.com/ZoeyVid/booking/discussions", + "wiki": "https://github.com/ZoeyVid/booking", + "source": "https://github.com/ZoeyVid/booking", + "docs": "https://github.com/ZoeyVid/booking" + } +} diff --git a/src/composer.lock b/src/composer.lock new file mode 100644 index 00000000..446d65d6 --- /dev/null +++ b/src/composer.lock @@ -0,0 +1,259 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "0e5e06a8da6fe6041233a057fffa913a", + "packages": [ + { + "name": "chillerlan/php-qrcode", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/chillerlan/php-qrcode.git", + "reference": "da5bdb82c8755f54de112b271b402aaa8df53269" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/da5bdb82c8755f54de112b271b402aaa8df53269", + "reference": "da5bdb82c8755f54de112b271b402aaa8df53269", + "shasum": "" + }, + "require": { + "chillerlan/php-settings-container": "^2.1.4 || ^3.1", + "ext-mbstring": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "chillerlan/php-authenticator": "^4.1 || ^5.1", + "phan/phan": "^5.4", + "phpmd/phpmd": "^2.15", + "phpunit/phpunit": "^9.6", + "setasign/fpdf": "^1.8.2", + "squizlabs/php_codesniffer": "^3.8" + }, + "suggest": { + "chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.", + "setasign/fpdf": "Required to use the QR FPDF output.", + "simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code" + }, + "type": "library", + "autoload": { + "psr-4": { + "chillerlan\\QRCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT", + "Apache-2.0" + ], + "authors": [ + { + "name": "Kazuhiko Arase", + "homepage": "https://github.com/kazuhikoarase/qrcode-generator" + }, + { + "name": "ZXing Authors", + "homepage": "https://github.com/zxing/zxing" + }, + { + "name": "Ashot Khanamiryan", + "homepage": "https://github.com/khanamiryan/php-qrcode-detector-decoder" + }, + { + "name": "Smiley", + "email": "smiley@chillerlan.net", + "homepage": "https://github.com/codemasher" + }, + { + "name": "Contributors", + "homepage": "https://github.com/chillerlan/php-qrcode/graphs/contributors" + } + ], + "description": "A QR code generator and reader with a user friendly API. PHP 7.4+", + "homepage": "https://github.com/chillerlan/php-qrcode", + "keywords": [ + "phpqrcode", + "qr", + "qr code", + "qr-reader", + "qrcode", + "qrcode-generator", + "qrcode-reader" + ], + "support": { + "docs": "https://php-qrcode.readthedocs.io", + "issues": "https://github.com/chillerlan/php-qrcode/issues", + "source": "https://github.com/chillerlan/php-qrcode" + }, + "funding": [ + { + "url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4", + "type": "custom" + }, + { + "url": "https://ko-fi.com/codemasher", + "type": "ko_fi" + } + ], + "time": "2024-02-27T14:37:26+00:00" + }, + { + "name": "chillerlan/php-settings-container", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/chillerlan/php-settings-container.git", + "reference": "8f93648fac8e6bacac8e00a8d325eba4950295e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/8f93648fac8e6bacac8e00a8d325eba4950295e6", + "reference": "8f93648fac8e6bacac8e00a8d325eba4950295e6", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^8.1" + }, + "require-dev": { + "phan/phan": "^5.4", + "phpmd/phpmd": "^2.15", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^3.9" + }, + "type": "library", + "autoload": { + "psr-4": { + "chillerlan\\Settings\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Smiley", + "email": "smiley@chillerlan.net", + "homepage": "https://github.com/codemasher" + } + ], + "description": "A container class for immutable settings objects. Not a DI container.", + "homepage": "https://github.com/chillerlan/php-settings-container", + "keywords": [ + "Settings", + "configuration", + "container", + "helper" + ], + "support": { + "issues": "https://github.com/chillerlan/php-settings-container/issues", + "source": "https://github.com/chillerlan/php-settings-container" + }, + "funding": [ + { + "url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4", + "type": "custom" + }, + { + "url": "https://ko-fi.com/codemasher", + "type": "ko_fi" + } + ], + "time": "2024-03-02T20:07:15+00:00" + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.9.1", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "039de174cd9c17a8389754d3b877a2ed22743e18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18", + "reference": "039de174cd9c17a8389754d3b877a2ed22743e18", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.1" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2023-11-25T22:23:28+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "alpha", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/security.txt b/src/public/.well-known/security.txt similarity index 100% rename from security.txt rename to src/public/.well-known/security.txt diff --git a/src/public/status/index.php b/src/public/status/index.php new file mode 100644 index 00000000..5e34bd39 --- /dev/null +++ b/src/public/status/index.php @@ -0,0 +1,2 @@ + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/vendor/chillerlan/php-qrcode/NOTICE b/src/vendor/chillerlan/php-qrcode/NOTICE new file mode 100644 index 00000000..596130bf --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/NOTICE @@ -0,0 +1,40 @@ +Parts of this code are ported to php from the ZXing project +and licensed under the Apache License, Version 2.0. + +Copyright 2007 ZXing authors (https://github.com/zxing/zxing), +Copyright (c) Ashot Khanamiryan (https://github.com/khanamiryan/php-qrcode-detector-decoder) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +List of affected files: + +src/Common/ECICharset.php +src/Common/GenericGFPoly.php +src/Common/GF256.php +src/Common/LuminanceSourceAbstract.php +src/Common/MaskPattern.php +src/Decoder/Binarizer.php +src/Decoder/BitMatrix.php +src/Decoder/Decoder.php +src/Decoder/DecoderResult.php +src/Decoder/ReedSolomonDecoder.php +src/Detector/AlignmentPattern.php +src/Detector/AlignmentPatternFinder.php +src/Detector/Detector.php +src/Detector/FinderPattern.php +src/Detector/FinderPatternFinder.php +src/Detector/GridSampler.php +src/Detector/PerspectiveTransform.php +src/Detector/ResultPoint.php +tests/Common/MaskPatternTest.php diff --git a/src/vendor/chillerlan/php-qrcode/README.md b/src/vendor/chillerlan/php-qrcode/README.md new file mode 100644 index 00000000..9aaaaea3 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/README.md @@ -0,0 +1,168 @@ +# chillerlan/php-qrcode + +A PHP QR Code generator based on the [implementation by Kazuhiko Arase](https://github.com/kazuhikoarase/qrcode-generator), namespaced, cleaned up, improved and other stuff.
+It also features a QR Code reader based on a [PHP port](https://github.com/khanamiryan/php-qrcode-detector-decoder) of the [ZXing library](https://github.com/zxing/zxing). + +**Attention:** there is now also a javascript port: [chillerlan/js-qrcode](https://github.com/chillerlan/js-qrcode). + +[![PHP Version Support][php-badge]][php] +[![Packagist version][packagist-badge]][packagist] +[![Continuous Integration][gh-action-badge]][gh-action] +[![CodeCov][coverage-badge]][coverage] +[![Codacy][codacy-badge]][codacy] +[![Packagist downloads][downloads-badge]][downloads] +[![Documentation][readthedocs-badge]][readthedocs] + +[php-badge]: https://img.shields.io/packagist/php-v/chillerlan/php-qrcode?logo=php&color=8892BF +[php]: https://www.php.net/supported-versions.php +[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-qrcode.svg?logo=packagist +[packagist]: https://packagist.org/packages/chillerlan/php-qrcode +[gh-action-badge]: https://img.shields.io/github/actions/workflow/status/chillerlan/php-qrcode/ci.yml?branch=v5.0.x&logo=github +[gh-action]: https://github.com/chillerlan/php-qrcode/actions/workflows/ci.yml?query=branch%3Amain +[coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-qrcode/v5.0.x?logo=codecov +[coverage]: https://app.codecov.io/gh/chillerlan/php-qrcode/tree/v5.0.x +[codacy-badge]: https://img.shields.io/codacy/grade/edccfc4fe5a34b74b1c53ee03f097b8d/v5.0.x?logo=codacy +[codacy]: https://app.codacy.com/gh/chillerlan/php-qrcode/dashboard?branch=v5.0.x +[downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-qrcode?logo=packagist +[downloads]: https://packagist.org/packages/chillerlan/php-qrcode/stats +[readthedocs-badge]: https://img.shields.io/readthedocs/php-qrcode/v5.0.x?logo=readthedocs +[readthedocs]: https://php-qrcode.readthedocs.io/en/v5.0.x/ + +## Overview + +### Features + +- Creation of [Model 2 QR Codes](https://www.qrcode.com/en/codes/model12.html), [Version 1 to 40](https://www.qrcode.com/en/about/version.html) +- [ECC Levels](https://www.qrcode.com/en/about/error_correction.html) L/M/Q/H supported +- Mixed mode support (encoding modes can be combined within a QR symbol). Supported modes: + - numeric + - alphanumeric + - 8-bit binary + - [ECI support](https://en.wikipedia.org/wiki/Extended_Channel_Interpretation) + - 13-bit double-byte: + - kanji (Japanese, Shift-JIS) + - hanzi (simplified Chinese, GB2312/GB18030) as [defined in GBT18284-2000](https://www.chinesestandard.net/PDF/English.aspx/GBT18284-2000) +- Flexible, easily extensible output modules, built-in support for the following output formats: + - [GdImage](https://www.php.net/manual/book.image) (raster graphics: bmp, gif, jpeg, png, webp) + - [ImageMagick](https://www.php.net/manual/book.imagick) ([multiple supported image formats](https://imagemagick.org/script/formats.php)) + - Markup types: SVG, HTML, etc. + - String types: JSON, plain text, etc. + - Encapsulated Postscript (EPS) + - PDF via [FPDF](https://github.com/setasign/fpdf) +- QR Code reader (via GD and ImageMagick) + + +### Requirements + +- PHP 7.4+ + - [`ext-mbstring`](https://www.php.net/manual/book.mbstring.php) + - optional: + - [`ext-gd`](https://www.php.net/manual/book.image) + - [`ext-imagick`](https://github.com/Imagick/imagick) with [ImageMagick](https://imagemagick.org) installed + - [`ext-fileinfo`](https://www.php.net/manual/book.fileinfo.php) (required by `QRImagick` output) + - [`setasign/fpdf`](https://github.com/setasign/fpdf) for the PDF output module + +For the QRCode reader, either `ext-gd` or `ext-imagick` is required! + + +## Documentation + +- The user manual is at https://php-qrcode.readthedocs.io/ ([sources](https://github.com/chillerlan/php-qrcode/tree/v5.0.x/docs)) +- An API documentation created with [phpDocumentor](https://www.phpdoc.org/) can be found at https://chillerlan.github.io/php-qrcode/ +- The documentation for the `QROptions` container can be found here: [chillerlan/php-settings-container](https://github.com/chillerlan/php-settings-container#readme) + + +## Installation with [composer](https://getcomposer.org) + +See [the installation guide](https://php-qrcode.readthedocs.io/en/v5.0.x/Usage/Installation.html) for more info! + + +### Terminal + +``` +composer require chillerlan/php-qrcode +``` + + +### composer.json + +```json +{ + "require": { + "php": "^7.4 || ^8.0", + "chillerlan/php-qrcode": "v5.0.x-dev#" + } +} +``` + +Note: replace `v5.0.x-dev` with a [version constraint](https://getcomposer.org/doc/articles/versions.md#writing-version-constraints), e.g. `^4.3` - see [releases](https://github.com/chillerlan/php-qrcode/releases) for valid versions. + + +## Quickstart + +We want to encode this URI for a mobile authenticator into a QRcode image: + +```php +$data = 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net'; + +// quick and simple: +echo 'QR Code'; +``` + +Wait, what was that? Please again, slower! See [Advanced usage](https://php-qrcode.readthedocs.io/en/v5.0.x/Usage/Advanced-usage.html) in the manual. +Also, have a look [in the examples folder](https://github.com/chillerlan/php-qrcode/tree/v5.0.x/examples) for some more usage examples. + +

+ QR codes are awesome! +

+ + +### Reading QR Codes + +Using the built-in QR Code reader is pretty straight-forward: + +```php +// it's generally a good idea to wrap the reader in a try/catch block because it WILL throw eventually +try{ + $result = (new QRCode)->readFromFile('path/to/file.png'); // -> DecoderResult + + // you can now use the result instance... + $content = $result->data; + $matrix = $result->getMatrix(); // -> QRMatrix + + // ...or simply cast it to string to get the content: + $content = (string)$result; +} +catch(Throwable $e){ + // oopsies! +} +``` + + +## Shameless advertising + +Hi, please check out some of my other projects that are way cooler than qrcodes! + +- [js-qrcode](https://github.com/chillerlan/js-qrcode) - a javascript port of this library +- [php-authenticator](https://github.com/chillerlan/php-authenticator) - a Google Authenticator implementation (see [authenticator example](https://github.com/chillerlan/php-qrcode/blob/v5.0.x/examples/authenticator.php)) +- [php-httpinterface](https://github.com/chillerlan/php-httpinterface) - a PSR-7/15/17/18 implemetation +- [php-oauth-core](https://github.com/chillerlan/php-oauth-core) - an OAuth 1/2 client library along with a bunch of [providers](https://github.com/chillerlan/php-oauth-providers) +- [php-database](https://github.com/chillerlan/php-database) - a database client & querybuilder for MySQL, Postgres, SQLite, MSSQL, Firebird +- [php-tootbot](https://github.com/php-tootbot/tootbot-template) - a Mastodon bot library (see [@dwil](https://github.com/php-tootbot/dwil)) + + +## Disclaimer! + +I don't take responsibility for molten CPUs, misled applications, failed log-ins etc.. Use at your own risk! + + +### License notice + +- Parts of this code are [ported to PHP](https://github.com/codemasher/php-qrcode-decoder) from the [ZXing project](https://github.com/zxing/zxing) and licensed under the [Apache License, Version 2.0](./NOTICE). +- [The documentation](https://github.com/chillerlan/php-qrcode/tree/v5.0.x/docs) is licensed under the [Creative Commons Attribution 4.0 International (CC BY 4.0) License](https://creativecommons.org/licenses/by/4.0/). + + +### Trademark Notice + +The word "QR Code" is a registered trademark of *DENSO WAVE INCORPORATED*
+https://www.qrcode.com/en/faq.html#patentH2Title diff --git a/src/vendor/chillerlan/php-qrcode/composer.json b/src/vendor/chillerlan/php-qrcode/composer.json new file mode 100644 index 00000000..7e74be55 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/composer.json @@ -0,0 +1,79 @@ +{ + "name": "chillerlan/php-qrcode", + "description": "A QR code generator and reader with a user friendly API. PHP 7.4+", + "homepage": "https://github.com/chillerlan/php-qrcode", + "license": [ + "MIT", "Apache-2.0" + ], + "type": "library", + "keywords": [ + "QR code", "qrcode", "qr", "qrcode-generator", "phpqrcode", "qrcode-reader", "qr-reader" + ], + "authors": [ + { + "name": "Kazuhiko Arase", + "homepage": "https://github.com/kazuhikoarase/qrcode-generator" + }, + { + "name":"ZXing Authors", + "homepage": "https://github.com/zxing/zxing" + }, + { + "name": "Ashot Khanamiryan", + "homepage": "https://github.com/khanamiryan/php-qrcode-detector-decoder" + }, + { + "name": "Smiley", + "email": "smiley@chillerlan.net", + "homepage": "https://github.com/codemasher" + }, + { + "name": "Contributors", + "homepage":"https://github.com/chillerlan/php-qrcode/graphs/contributors" + } + ], + "support": { + "docs": "https://php-qrcode.readthedocs.io", + "issues": "https://github.com/chillerlan/php-qrcode/issues", + "source": "https://github.com/chillerlan/php-qrcode" + }, + "minimum-stability": "stable", + "prefer-stable": true, + "require": { + "php": "^7.4 || ^8.0", + "ext-mbstring": "*", + "chillerlan/php-settings-container": "^2.1.4 || ^3.1" + }, + "require-dev": { + "chillerlan/php-authenticator": "^4.1 || ^5.1", + "phan/phan": "^5.4", + "phpunit/phpunit": "^9.6", + "phpmd/phpmd": "^2.15", + "setasign/fpdf": "^1.8.2", + "squizlabs/php_codesniffer": "^3.8" + }, + "suggest": { + "chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.", + "setasign/fpdf": "Required to use the QR FPDF output.", + "simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code" + }, + "autoload": { + "psr-4": { + "chillerlan\\QRCode\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "chillerlan\\QRCodeTest\\": "tests/" + } + }, + "scripts": { + "phpunit": "@php vendor/bin/phpunit", + "phan": "@php vendor/bin/phan" + }, + "config": { + "lock": false, + "sort-packages": true, + "platform-check": true + } +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/BitBuffer.php b/src/vendor/chillerlan/php-qrcode/src/Common/BitBuffer.php new file mode 100644 index 00000000..4a59f2b5 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/BitBuffer.php @@ -0,0 +1,180 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\QRCodeException; +use function count, floor, min; + +/** + * Holds the raw binary data + */ +final class BitBuffer{ + + /** + * The buffer content + * + * @var int[] + */ + private array $buffer; + + /** + * Length of the content (bits) + */ + private int $length; + + /** + * Read count (bytes) + */ + private int $bytesRead = 0; + + /** + * Read count (bits) + */ + private int $bitsRead = 0; + + /** + * BitBuffer constructor. + * + * @param int[] $bytes + */ + public function __construct(array $bytes = []){ + $this->buffer = $bytes; + $this->length = count($this->buffer); + } + + /** + * appends a sequence of bits + */ + public function put(int $bits, int $length):self{ + + for($i = 0; $i < $length; $i++){ + $this->putBit((($bits >> ($length - $i - 1)) & 1) === 1); + } + + return $this; + } + + /** + * appends a single bit + */ + public function putBit(bool $bit):self{ + $bufIndex = (int)floor($this->length / 8); + + if(count($this->buffer) <= $bufIndex){ + $this->buffer[] = 0; + } + + if($bit === true){ + $this->buffer[$bufIndex] |= (0x80 >> ($this->length % 8)); + } + + $this->length++; + + return $this; + } + + /** + * returns the current buffer length + */ + public function getLength():int{ + return $this->length; + } + + /** + * returns the buffer content + * + * to debug: array_map(fn($v) => sprintf('%08b', $v), $bitBuffer->getBuffer()) + */ + public function getBuffer():array{ + return $this->buffer; + } + + /** + * @return int number of bits that can be read successfully + */ + public function available():int{ + return ((8 * ($this->length - $this->bytesRead)) - $this->bitsRead); + } + + /** + * @author Sean Owen, ZXing + * + * @param int $numBits number of bits to read + * + * @return int representing the bits read. The bits will appear as the least-significant bits of the int + * @throws \chillerlan\QRCode\QRCodeException if numBits isn't in [1,32] or more than is available + */ + public function read(int $numBits):int{ + + if($numBits < 1 || $numBits > $this->available()){ + throw new QRCodeException('invalid $numBits: '.$numBits); + } + + $result = 0; + + // First, read remainder from current byte + if($this->bitsRead > 0){ + $bitsLeft = (8 - $this->bitsRead); + $toRead = min($numBits, $bitsLeft); + $bitsToNotRead = ($bitsLeft - $toRead); + $mask = ((0xff >> (8 - $toRead)) << $bitsToNotRead); + $result = (($this->buffer[$this->bytesRead] & $mask) >> $bitsToNotRead); + $numBits -= $toRead; + $this->bitsRead += $toRead; + + if($this->bitsRead === 8){ + $this->bitsRead = 0; + $this->bytesRead++; + } + } + + // Next read whole bytes + if($numBits > 0){ + + while($numBits >= 8){ + $result = (($result << 8) | ($this->buffer[$this->bytesRead] & 0xff)); + $this->bytesRead++; + $numBits -= 8; + } + + // Finally read a partial byte + if($numBits > 0){ + $bitsToNotRead = (8 - $numBits); + $mask = ((0xff >> $bitsToNotRead) << $bitsToNotRead); + $result = (($result << $numBits) | (($this->buffer[$this->bytesRead] & $mask) >> $bitsToNotRead)); + $this->bitsRead += $numBits; + } + } + + return $result; + } + + /** + * Clears the buffer and resets the stats + */ + public function clear():self{ + $this->buffer = []; + $this->length = 0; + + return $this->rewind(); + } + + /** + * Resets the read-counters + */ + public function rewind():self{ + $this->bytesRead = 0; + $this->bitsRead = 0; + + return $this; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/ECICharset.php b/src/vendor/chillerlan/php-qrcode/src/Common/ECICharset.php new file mode 100644 index 00000000..0c98e36d --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/ECICharset.php @@ -0,0 +1,125 @@ + + * @copyright 2021 smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\QRCodeException; +use function sprintf; + +/** + * ISO/IEC 18004:2000 - 8.4.1 Extended Channel Interpretation (ECI) Mode + */ +final class ECICharset{ + + public const CP437 = 0; // Code page 437, DOS Latin US + public const ISO_IEC_8859_1_GLI = 1; // GLI encoding with characters 0 to 127 identical to ISO/IEC 646 and characters 128 to 255 identical to ISO 8859-1 + public const CP437_WO_GLI = 2; // An equivalent code table to CP437, without the return-to-GLI 0 logic + public const ISO_IEC_8859_1 = 3; // Latin-1 (Default) + public const ISO_IEC_8859_2 = 4; // Latin-2 + public const ISO_IEC_8859_3 = 5; // Latin-3 + public const ISO_IEC_8859_4 = 6; // Latin-4 + public const ISO_IEC_8859_5 = 7; // Latin/Cyrillic + public const ISO_IEC_8859_6 = 8; // Latin/Arabic + public const ISO_IEC_8859_7 = 9; // Latin/Greek + public const ISO_IEC_8859_8 = 10; // Latin/Hebrew + public const ISO_IEC_8859_9 = 11; // Latin-5 + public const ISO_IEC_8859_10 = 12; // Latin-6 + public const ISO_IEC_8859_11 = 13; // Latin/Thai + // 14 reserved + public const ISO_IEC_8859_13 = 15; // Latin-7 (Baltic Rim) + public const ISO_IEC_8859_14 = 16; // Latin-8 (Celtic) + public const ISO_IEC_8859_15 = 17; // Latin-9 + public const ISO_IEC_8859_16 = 18; // Latin-10 + // 19 reserved + public const SHIFT_JIS = 20; // JIS X 0208 Annex 1 + JIS X 0201 + public const WINDOWS_1250_LATIN_2 = 21; // Superset of Latin-2, Central Europe + public const WINDOWS_1251_CYRILLIC = 22; // Latin/Cyrillic + public const WINDOWS_1252_LATIN_1 = 23; // Superset of Latin-1 + public const WINDOWS_1256_ARABIC = 24; + public const ISO_IEC_10646_UCS_2 = 25; // High order byte first (UTF-16BE) + public const ISO_IEC_10646_UTF_8 = 26; // UTF-8 + public const ISO_IEC_646_1991 = 27; // International Reference Version of ISO 7-bit coded character set (US-ASCII) + public const BIG5 = 28; // Big 5 (Taiwan) Chinese Character Set + public const GB18030 = 29; // GB (PRC) Chinese Character Set + public const EUC_KR = 30; // Korean Character Set + + /** + * map of charset id -> name + * + * @see \mb_list_encodings() + */ + public const MB_ENCODINGS = [ + self::CP437 => null, + self::ISO_IEC_8859_1_GLI => null, + self::CP437_WO_GLI => null, + self::ISO_IEC_8859_1 => 'ISO-8859-1', + self::ISO_IEC_8859_2 => 'ISO-8859-2', + self::ISO_IEC_8859_3 => 'ISO-8859-3', + self::ISO_IEC_8859_4 => 'ISO-8859-4', + self::ISO_IEC_8859_5 => 'ISO-8859-5', + self::ISO_IEC_8859_6 => 'ISO-8859-6', + self::ISO_IEC_8859_7 => 'ISO-8859-7', + self::ISO_IEC_8859_8 => 'ISO-8859-8', + self::ISO_IEC_8859_9 => 'ISO-8859-9', + self::ISO_IEC_8859_10 => 'ISO-8859-10', + self::ISO_IEC_8859_11 => null, + self::ISO_IEC_8859_13 => 'ISO-8859-13', + self::ISO_IEC_8859_14 => 'ISO-8859-14', + self::ISO_IEC_8859_15 => 'ISO-8859-15', + self::ISO_IEC_8859_16 => 'ISO-8859-16', + self::SHIFT_JIS => 'SJIS', + self::WINDOWS_1250_LATIN_2 => null, // @see https://www.php.net/manual/en/function.mb-convert-encoding.php#112547 + self::WINDOWS_1251_CYRILLIC => 'Windows-1251', + self::WINDOWS_1252_LATIN_1 => 'Windows-1252', + self::WINDOWS_1256_ARABIC => null, // @see https://stackoverflow.com/a/8592995 + self::ISO_IEC_10646_UCS_2 => 'UTF-16BE', + self::ISO_IEC_10646_UTF_8 => 'UTF-8', + self::ISO_IEC_646_1991 => 'ASCII', + self::BIG5 => 'BIG-5', + self::GB18030 => 'GB18030', + self::EUC_KR => 'EUC-KR', + ]; + + /** + * The current ECI character set ID + */ + private int $charsetID; + + /** + * @throws \chillerlan\QRCode\QRCodeException + */ + public function __construct(int $charsetID){ + + if($charsetID < 0 || $charsetID > 999999){ + throw new QRCodeException(sprintf('invalid charset id: "%s"', $charsetID)); + } + + $this->charsetID = $charsetID; + } + + /** + * Returns the current character set ID + */ + public function getID():int{ + return $this->charsetID; + } + + /** + * Returns the name of the current character set or null if no name is available + * + * @see \mb_convert_encoding() + * @see \iconv() + */ + public function getName():?string{ + return (self::MB_ENCODINGS[$this->charsetID] ?? null); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/EccLevel.php b/src/vendor/chillerlan/php-qrcode/src/Common/EccLevel.php new file mode 100644 index 00000000..789d7f79 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/EccLevel.php @@ -0,0 +1,223 @@ + + * @copyright 2020 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\QRCodeException; +use function array_column; + +/** + * This class encapsulates the four error correction levels defined by the QR code standard. + */ +final class EccLevel{ + + // ISO/IEC 18004:2000 Tables 12, 25 + + /** @var int */ + public const L = 0b01; // 7%. + /** @var int */ + public const M = 0b00; // 15%. + /** @var int */ + public const Q = 0b11; // 25%. + /** @var int */ + public const H = 0b10; // 30%. + + /** + * ISO/IEC 18004:2000 Tables 7-11 - Number of symbol characters and input data capacity for versions 1 to 40 + * + * @var int[][] + */ + private const MAX_BITS = [ + // [ L, M, Q, H] // v => modules + [ 0, 0, 0, 0], // 0 => will be ignored, index starts at 1 + [ 152, 128, 104, 72], // 1 => 21 + [ 272, 224, 176, 128], // 2 => 25 + [ 440, 352, 272, 208], // 3 => 29 + [ 640, 512, 384, 288], // 4 => 33 + [ 864, 688, 496, 368], // 5 => 37 + [ 1088, 864, 608, 480], // 6 => 41 + [ 1248, 992, 704, 528], // 7 => 45 + [ 1552, 1232, 880, 688], // 8 => 49 + [ 1856, 1456, 1056, 800], // 9 => 53 + [ 2192, 1728, 1232, 976], // 10 => 57 + [ 2592, 2032, 1440, 1120], // 11 => 61 + [ 2960, 2320, 1648, 1264], // 12 => 65 + [ 3424, 2672, 1952, 1440], // 13 => 69 NICE! + [ 3688, 2920, 2088, 1576], // 14 => 73 + [ 4184, 3320, 2360, 1784], // 15 => 77 + [ 4712, 3624, 2600, 2024], // 16 => 81 + [ 5176, 4056, 2936, 2264], // 17 => 85 + [ 5768, 4504, 3176, 2504], // 18 => 89 + [ 6360, 5016, 3560, 2728], // 19 => 93 + [ 6888, 5352, 3880, 3080], // 20 => 97 + [ 7456, 5712, 4096, 3248], // 21 => 101 + [ 8048, 6256, 4544, 3536], // 22 => 105 + [ 8752, 6880, 4912, 3712], // 23 => 109 + [ 9392, 7312, 5312, 4112], // 24 => 113 + [10208, 8000, 5744, 4304], // 25 => 117 + [10960, 8496, 6032, 4768], // 26 => 121 + [11744, 9024, 6464, 5024], // 27 => 125 + [12248, 9544, 6968, 5288], // 28 => 129 + [13048, 10136, 7288, 5608], // 29 => 133 + [13880, 10984, 7880, 5960], // 30 => 137 + [14744, 11640, 8264, 6344], // 31 => 141 + [15640, 12328, 8920, 6760], // 32 => 145 + [16568, 13048, 9368, 7208], // 33 => 149 + [17528, 13800, 9848, 7688], // 34 => 153 + [18448, 14496, 10288, 7888], // 35 => 157 + [19472, 15312, 10832, 8432], // 36 => 161 + [20528, 15936, 11408, 8768], // 37 => 165 + [21616, 16816, 12016, 9136], // 38 => 169 + [22496, 17728, 12656, 9776], // 39 => 173 + [23648, 18672, 13328, 10208], // 40 => 177 + ]; + + /** + * ISO/IEC 18004:2000 Section 8.9 - Format Information + * + * ECC level -> mask pattern + * + * @var int[][] + */ + private const FORMAT_PATTERN = [ + [ // L + 0b111011111000100, + 0b111001011110011, + 0b111110110101010, + 0b111100010011101, + 0b110011000101111, + 0b110001100011000, + 0b110110001000001, + 0b110100101110110, + ], + [ // M + 0b101010000010010, + 0b101000100100101, + 0b101111001111100, + 0b101101101001011, + 0b100010111111001, + 0b100000011001110, + 0b100111110010111, + 0b100101010100000, + ], + [ // Q + 0b011010101011111, + 0b011000001101000, + 0b011111100110001, + 0b011101000000110, + 0b010010010110100, + 0b010000110000011, + 0b010111011011010, + 0b010101111101101, + ], + [ // H + 0b001011010001001, + 0b001001110111110, + 0b001110011100111, + 0b001100111010000, + 0b000011101100010, + 0b000001001010101, + 0b000110100001100, + 0b000100000111011, + ], + ]; + + /** + * The current ECC level value + * + * L: 0b01 + * M: 0b00 + * Q: 0b11 + * H: 0b10 + */ + private int $eccLevel; + + /** + * @param int $eccLevel containing the two bits encoding a QR Code's error correction level + * + * @todo: accept string values (PHP8+) + * @see https://github.com/chillerlan/php-qrcode/discussions/160 + * + * @throws \chillerlan\QRCode\QRCodeException + */ + public function __construct(int $eccLevel){ + + if((0b11 & $eccLevel) !== $eccLevel){ + throw new QRCodeException('invalid ECC level'); + } + + $this->eccLevel = $eccLevel; + } + + /** + * returns the string representation of the current ECC level + */ + public function __toString():string{ + return [ + self::L => 'L', + self::M => 'M', + self::Q => 'Q', + self::H => 'H', + ][$this->eccLevel]; + } + + /** + * returns the current ECC level + */ + public function getLevel():int{ + return $this->eccLevel; + } + + /** + * returns the ordinal value of the current ECC level + * + * references to the keys of the following tables: + * + * @see \chillerlan\QRCode\Common\EccLevel::MAX_BITS + * @see \chillerlan\QRCode\Common\EccLevel::FORMAT_PATTERN + * @see \chillerlan\QRCode\Common\Version::RSBLOCKS + */ + public function getOrdinal():int{ + return [ + self::L => 0, + self::M => 1, + self::Q => 2, + self::H => 3, + ][$this->eccLevel]; + } + + /** + * returns the format pattern for the given $eccLevel and $maskPattern + */ + public function getformatPattern(MaskPattern $maskPattern):int{ + return self::FORMAT_PATTERN[$this->getOrdinal()][$maskPattern->getPattern()]; + } + + /** + * returns an array with the max bit lengths for version 1-40 and the current ECC level + * + * @return int[] + */ + public function getMaxBits():array{ + $col = array_column(self::MAX_BITS, $this->getOrdinal()); + + unset($col[0]); // remove the inavlid index 0 + + return $col; + } + + /** + * Returns the maximum bit length for the given version and current ECC level + */ + public function getMaxBitsForVersion(Version $version):int{ + return self::MAX_BITS[$version->getVersionNumber()][$this->getOrdinal()]; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/GDLuminanceSource.php b/src/vendor/chillerlan/php-qrcode/src/Common/GDLuminanceSource.php new file mode 100644 index 00000000..027466f3 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/GDLuminanceSource.php @@ -0,0 +1,97 @@ + + * @copyright 2021 Smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\Decoder\QRCodeDecoderException; +use chillerlan\Settings\SettingsContainerInterface; +use function file_get_contents, get_resource_type, imagecolorat, imagecolorsforindex, + imagecreatefromstring, imagefilter, imagesx, imagesy, is_resource; +use const IMG_FILTER_BRIGHTNESS, IMG_FILTER_CONTRAST, IMG_FILTER_GRAYSCALE, IMG_FILTER_NEGATE, PHP_MAJOR_VERSION; + +/** + * This class is used to help decode images from files which arrive as GD Resource + * It does not support rotation. + */ +class GDLuminanceSource extends LuminanceSourceAbstract{ + + /** + * @var resource|\GdImage + */ + protected $gdImage; + + /** + * GDLuminanceSource constructor. + * + * @param resource|\GdImage $gdImage + * @param \chillerlan\Settings\SettingsContainerInterface|null $options + * + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + public function __construct($gdImage, SettingsContainerInterface $options = null){ + + /** @noinspection PhpFullyQualifiedNameUsageInspection */ + if( + (PHP_MAJOR_VERSION >= 8 && !$gdImage instanceof \GdImage) // @todo: remove version check in v6 + || (PHP_MAJOR_VERSION < 8 && (!is_resource($gdImage) || get_resource_type($gdImage) !== 'gd')) + ){ + throw new QRCodeDecoderException('Invalid GD image source.'); // @codeCoverageIgnore + } + + parent::__construct(imagesx($gdImage), imagesy($gdImage), $options); + + $this->gdImage = $gdImage; + + if($this->options->readerGrayscale){ + imagefilter($this->gdImage, IMG_FILTER_GRAYSCALE); + } + + if($this->options->readerInvertColors){ + imagefilter($this->gdImage, IMG_FILTER_NEGATE); + } + + if($this->options->readerIncreaseContrast){ + imagefilter($this->gdImage, IMG_FILTER_BRIGHTNESS, -100); + imagefilter($this->gdImage, IMG_FILTER_CONTRAST, -100); + } + + $this->setLuminancePixels(); + } + + /** + * + */ + protected function setLuminancePixels():void{ + + for($j = 0; $j < $this->height; $j++){ + for($i = 0; $i < $this->width; $i++){ + $argb = imagecolorat($this->gdImage, $i, $j); + $pixel = imagecolorsforindex($this->gdImage, $argb); + + $this->setLuminancePixel($pixel['red'], $pixel['green'], $pixel['blue']); + } + } + + } + + /** @inheritDoc */ + public static function fromFile(string $path, SettingsContainerInterface $options = null):self{ + return new self(imagecreatefromstring(file_get_contents(self::checkFile($path))), $options); + } + + /** @inheritDoc */ + public static function fromBlob(string $blob, SettingsContainerInterface $options = null):self{ + return new self(imagecreatefromstring($blob), $options); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/GF256.php b/src/vendor/chillerlan/php-qrcode/src/Common/GF256.php new file mode 100644 index 00000000..d8ba0950 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/GF256.php @@ -0,0 +1,154 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\QRCodeException; + +use function array_fill; + +/** + * This class contains utility methods for performing mathematical operations over + * the Galois Fields. Operations use a given primitive polynomial in calculations. + * + * Throughout this package, elements of the GF are represented as an int + * for convenience and speed (but at the cost of memory). + * + * + * @author Sean Owen + * @author David Olivier + */ +final class GF256{ + + /** + * irreducible polynomial whose coefficients are represented by the bits of an int, + * where the least-significant bit represents the constant coefficient + */ +# private int $primitive = 0x011D; + + private const logTable = [ + 0, // the first value is never returned, index starts at 1 + 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, + 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, + 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, + 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, + 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, + 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, + 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, + 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, + 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, + 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, + 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, + 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, + 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, + 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, + 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, + 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175, + ]; + + private const expTable = [ + 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, + 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, + 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, + 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, + 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, + 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, + 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, + 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, + 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, + 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, + 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, + 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, + 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, + 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, + 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, + 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1, + ]; + + /** + * Implements both addition and subtraction -- they are the same in GF(size). + * + * @return int sum/difference of a and b + */ + public static function addOrSubtract(int $a, int $b):int{ + return ($a ^ $b); + } + + /** + * @return GenericGFPoly the monomial representing coefficient * x^degree + * @throws \chillerlan\QRCode\QRCodeException + */ + public static function buildMonomial(int $degree, int $coefficient):GenericGFPoly{ + + if($degree < 0){ + throw new QRCodeException('degree < 0'); + } + + $coefficients = array_fill(0, ($degree + 1), 0); + $coefficients[0] = $coefficient; + + return new GenericGFPoly($coefficients); + } + + /** + * @return int 2 to the power of $a in GF(size) + */ + public static function exp(int $a):int{ + + if($a < 0){ + $a += 255; + } + elseif($a >= 256){ + $a -= 255; + } + + return self::expTable[$a]; + } + + /** + * @return int base 2 log of $a in GF(size) + * @throws \chillerlan\QRCode\QRCodeException + */ + public static function log(int $a):int{ + + if($a < 1){ + throw new QRCodeException('$a < 1'); + } + + return self::logTable[$a]; + } + + /** + * @return int multiplicative inverse of a + * @throws \chillerlan\QRCode\QRCodeException + */ + public static function inverse(int $a):int{ + + if($a === 0){ + throw new QRCodeException('$a === 0'); + } + + return self::expTable[(256 - self::logTable[$a] - 1)]; + } + + /** + * @return int product of a and b in GF(size) + */ + public static function multiply(int $a, int $b):int{ + + if($a === 0 || $b === 0){ + return 0; + } + + return self::expTable[((self::logTable[$a] + self::logTable[$b]) % 255)]; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php b/src/vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php new file mode 100644 index 00000000..ae361b9d --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php @@ -0,0 +1,263 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\QRCodeException; +use function array_fill, array_slice, array_splice, count; + +/** + * Represents a polynomial whose coefficients are elements of a GF. + * Instances of this class are immutable. + * + * Much credit is due to William Rucklidge since portions of this code are an indirect + * port of his C++ Reed-Solomon implementation. + * + * @author Sean Owen + */ +final class GenericGFPoly{ + + private array $coefficients; + + /** + * @param array $coefficients array coefficients as ints representing elements of GF(size), arranged + * from most significant (highest-power term) coefficient to the least significant + * @param int|null $degree + * + * @throws \chillerlan\QRCode\QRCodeException if argument is null or empty, or if leading coefficient is 0 and this + * is not a constant polynomial (that is, it is not the monomial "0") + */ + public function __construct(array $coefficients, int $degree = null){ + $degree ??= 0; + + if(empty($coefficients)){ + throw new QRCodeException('arg $coefficients is empty'); + } + + if($degree < 0){ + throw new QRCodeException('negative degree'); + } + + $coefficientsLength = count($coefficients); + + // Leading term must be non-zero for anything except the constant polynomial "0" + $firstNonZero = 0; + + while($firstNonZero < $coefficientsLength && $coefficients[$firstNonZero] === 0){ + $firstNonZero++; + } + + $this->coefficients = [0]; + + if($firstNonZero !== $coefficientsLength){ + $this->coefficients = array_fill(0, ($coefficientsLength - $firstNonZero + $degree), 0); + + for($i = 0; $i < ($coefficientsLength - $firstNonZero); $i++){ + $this->coefficients[$i] = $coefficients[($i + $firstNonZero)]; + } + } + + } + + /** + * @return int $coefficient of x^degree term in this polynomial + */ + public function getCoefficient(int $degree):int{ + return $this->coefficients[(count($this->coefficients) - 1 - $degree)]; + } + + /** + * @return int[] + */ + public function getCoefficients():array{ + return $this->coefficients; + } + + /** + * @return int $degree of this polynomial + */ + public function getDegree():int{ + return (count($this->coefficients) - 1); + } + + /** + * @return bool true if this polynomial is the monomial "0" + */ + public function isZero():bool{ + return $this->coefficients[0] === 0; + } + + /** + * @return int evaluation of this polynomial at a given point + */ + public function evaluateAt(int $a):int{ + + if($a === 0){ + // Just return the x^0 coefficient + return $this->getCoefficient(0); + } + + $result = 0; + + foreach($this->coefficients as $c){ + // if $a === 1 just the sum of the coefficients + $result = GF256::addOrSubtract((($a === 1) ? $result : GF256::multiply($a, $result)), $c); + } + + return $result; + } + + /** + * + */ + public function multiply(GenericGFPoly $other):self{ + + if($this->isZero() || $other->isZero()){ + return new self([0]); + } + + $product = array_fill(0, (count($this->coefficients) + count($other->coefficients) - 1), 0); + + foreach($this->coefficients as $i => $aCoeff){ + foreach($other->coefficients as $j => $bCoeff){ + $product[($i + $j)] ^= GF256::multiply($aCoeff, $bCoeff); + } + } + + return new self($product); + } + + /** + * @return \chillerlan\QRCode\Common\GenericGFPoly[] [quotient, remainder] + * @throws \chillerlan\QRCode\QRCodeException + */ + public function divide(GenericGFPoly $other):array{ + + if($other->isZero()){ + throw new QRCodeException('Division by 0'); + } + + $quotient = new self([0]); + $remainder = clone $this; + + $denominatorLeadingTerm = $other->getCoefficient($other->getDegree()); + $inverseDenominatorLeadingTerm = GF256::inverse($denominatorLeadingTerm); + + while($remainder->getDegree() >= $other->getDegree() && !$remainder->isZero()){ + $scale = GF256::multiply($remainder->getCoefficient($remainder->getDegree()), $inverseDenominatorLeadingTerm); + $diff = ($remainder->getDegree() - $other->getDegree()); + $quotient = $quotient->addOrSubtract(GF256::buildMonomial($diff, $scale)); + $remainder = $remainder->addOrSubtract($other->multiplyByMonomial($diff, $scale)); + } + + return [$quotient, $remainder]; + + } + + /** + * + */ + public function multiplyInt(int $scalar):self{ + + if($scalar === 0){ + return new self([0]); + } + + if($scalar === 1){ + return $this; + } + + $product = array_fill(0, count($this->coefficients), 0); + + foreach($this->coefficients as $i => $c){ + $product[$i] = GF256::multiply($c, $scalar); + } + + return new self($product); + } + + /** + * @throws \chillerlan\QRCode\QRCodeException + */ + public function multiplyByMonomial(int $degree, int $coefficient):self{ + + if($degree < 0){ + throw new QRCodeException('degree < 0'); + } + + if($coefficient === 0){ + return new self([0]); + } + + $product = array_fill(0, (count($this->coefficients) + $degree), 0); + + foreach($this->coefficients as $i => $c){ + $product[$i] = GF256::multiply($c, $coefficient); + } + + return new self($product); + } + + /** + * + */ + public function mod(GenericGFPoly $other):self{ + + if((count($this->coefficients) - count($other->coefficients)) < 0){ + return $this; + } + + $ratio = (GF256::log($this->coefficients[0]) - GF256::log($other->coefficients[0])); + + foreach($other->coefficients as $i => $c){ + $this->coefficients[$i] ^= GF256::exp(GF256::log($c) + $ratio); + } + + return (new self($this->coefficients))->mod($other); + } + + /** + * + */ + public function addOrSubtract(GenericGFPoly $other):self{ + + if($this->isZero()){ + return $other; + } + + if($other->isZero()){ + return $this; + } + + $smallerCoefficients = $this->coefficients; + $largerCoefficients = $other->coefficients; + + if(count($smallerCoefficients) > count($largerCoefficients)){ + $temp = $smallerCoefficients; + $smallerCoefficients = $largerCoefficients; + $largerCoefficients = $temp; + } + + $sumDiff = array_fill(0, count($largerCoefficients), 0); + $lengthDiff = (count($largerCoefficients) - count($smallerCoefficients)); + // Copy high-order terms only found in higher-degree polynomial's coefficients + array_splice($sumDiff, 0, $lengthDiff, array_slice($largerCoefficients, 0, $lengthDiff)); + + $countLargerCoefficients = count($largerCoefficients); + + for($i = $lengthDiff; $i < $countLargerCoefficients; $i++){ + $sumDiff[$i] = GF256::addOrSubtract($smallerCoefficients[($i - $lengthDiff)], $largerCoefficients[$i]); + } + + return new self($sumDiff); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/IMagickLuminanceSource.php b/src/vendor/chillerlan/php-qrcode/src/Common/IMagickLuminanceSource.php new file mode 100644 index 00000000..d4f66c5c --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/IMagickLuminanceSource.php @@ -0,0 +1,78 @@ + + * @copyright 2021 Smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\Settings\SettingsContainerInterface; +use Imagick; +use function count; + +/** + * This class is used to help decode images from files which arrive as Imagick Resource + * It does not support rotation. + */ +class IMagickLuminanceSource extends LuminanceSourceAbstract{ + + protected Imagick $imagick; + + /** + * IMagickLuminanceSource constructor. + */ + public function __construct(Imagick $imagick, SettingsContainerInterface $options = null){ + parent::__construct($imagick->getImageWidth(), $imagick->getImageHeight(), $options); + + $this->imagick = $imagick; + + if($this->options->readerGrayscale){ + $this->imagick->setImageColorspace(Imagick::COLORSPACE_GRAY); + } + + if($this->options->readerInvertColors){ + $this->imagick->negateImage($this->options->readerGrayscale); + } + + if($this->options->readerIncreaseContrast){ + for($i = 0; $i < 10; $i++){ + $this->imagick->contrastImage(false); // misleading docs + } + } + + $this->setLuminancePixels(); + } + + /** + * + */ + protected function setLuminancePixels():void{ + $pixels = $this->imagick->exportImagePixels(1, 1, $this->width, $this->height, 'RGB', Imagick::PIXEL_CHAR); + $count = count($pixels); + + for($i = 0; $i < $count; $i += 3){ + $this->setLuminancePixel(($pixels[$i] & 0xff), ($pixels[($i + 1)] & 0xff), ($pixels[($i + 2)] & 0xff)); + } + } + + /** @inheritDoc */ + public static function fromFile(string $path, SettingsContainerInterface $options = null):self{ + return new self(new Imagick(self::checkFile($path)), $options); + } + + /** @inheritDoc */ + public static function fromBlob(string $blob, SettingsContainerInterface $options = null):self{ + $im = new Imagick; + $im->readImageBlob($blob); + + return new self($im, $options); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceAbstract.php b/src/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceAbstract.php new file mode 100644 index 00000000..432a6e0d --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceAbstract.php @@ -0,0 +1,104 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\Decoder\QRCodeDecoderException; +use chillerlan\QRCode\QROptions; +use chillerlan\Settings\SettingsContainerInterface; +use function array_slice, array_splice, file_exists, is_file, is_readable, realpath; + +/** + * The purpose of this class hierarchy is to abstract different bitmap implementations across + * platforms into a standard interface for requesting greyscale luminance values. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +abstract class LuminanceSourceAbstract implements LuminanceSourceInterface{ + + /** @var \chillerlan\QRCode\QROptions|\chillerlan\Settings\SettingsContainerInterface */ + protected SettingsContainerInterface $options; + protected array $luminances; + protected int $width; + protected int $height; + + /** + * + */ + public function __construct(int $width, int $height, SettingsContainerInterface $options = null){ + $this->width = $width; + $this->height = $height; + $this->options = ($options ?? new QROptions); + + $this->luminances = []; + } + + /** @inheritDoc */ + public function getLuminances():array{ + return $this->luminances; + } + + /** @inheritDoc */ + public function getWidth():int{ + return $this->width; + } + + /** @inheritDoc */ + public function getHeight():int{ + return $this->height; + } + + /** @inheritDoc */ + public function getRow(int $y):array{ + + if($y < 0 || $y >= $this->getHeight()){ + throw new QRCodeDecoderException('Requested row is outside the image: '.$y); + } + + $arr = []; + + array_splice($arr, 0, $this->width, array_slice($this->luminances, ($y * $this->width), $this->width)); + + return $arr; + } + + /** + * + */ + protected function setLuminancePixel(int $r, int $g, int $b):void{ + $this->luminances[] = ($r === $g && $g === $b) + // Image is already greyscale, so pick any channel. + ? $r // (($r + 128) % 256) - 128; + // Calculate luminance cheaply, favoring green. + : (($r + 2 * $g + $b) / 4); // (((($r + 2 * $g + $b) / 4) + 128) % 256) - 128; + } + + /** + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + protected static function checkFile(string $path):string{ + $path = trim($path); + + if(!file_exists($path) || !is_file($path) || !is_readable($path)){ + throw new QRCodeDecoderException('invalid file: '.$path); + } + + $realpath = realpath($path); + + if($realpath === false){ + throw new QRCodeDecoderException('unable to resolve path: '.$path); + } + + return $realpath; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceInterface.php b/src/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceInterface.php new file mode 100644 index 00000000..64409e36 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceInterface.php @@ -0,0 +1,61 @@ + + * @copyright 2021 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Common; + +/** + */ +interface LuminanceSourceInterface{ + + /** + * Fetches luminance data for the underlying bitmap. Values should be fetched using: + * `int luminance = array[y * width + x] & 0xff` + * + * @return array A row-major 2D array of luminance values. Do not use result $length as it may be + * larger than $width * $height bytes on some platforms. Do not modify the contents + * of the result. + */ + public function getLuminances():array; + + /** + * @return int The width of the bitmap. + */ + public function getWidth():int; + + /** + * @return int The height of the bitmap. + */ + public function getHeight():int; + + /** + * Fetches one row of luminance data from the underlying platform's bitmap. Values range from + * 0 (black) to 255 (white). Because Java does not have an unsigned byte type, callers will have + * to bitwise and with 0xff for each value. It is preferable for implementations of this method + * to only fetch this row rather than the whole image, since no 2D Readers may be installed and + * getLuminances() may never be called. + * + * @param int $y The row to fetch, which must be in [0,getHeight()) + * + * @return array An array containing the luminance data. + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + public function getRow(int $y):array; + + /** + * Creates a LuminanceSource instance from the given file + */ + public static function fromFile(string $path):self; + + /** + * Creates a LuminanceSource instance from the given data blob + */ + public static function fromBlob(string $blob):self; + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/MaskPattern.php b/src/vendor/chillerlan/php-qrcode/src/Common/MaskPattern.php new file mode 100644 index 00000000..8441a7f5 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/MaskPattern.php @@ -0,0 +1,329 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\QRCodeException; +use chillerlan\QRCode\Data\QRMatrix; +use Closure; +use function abs, array_column, array_search, intdiv, min; + +/** + * ISO/IEC 18004:2000 Section 8.8.1 + * ISO/IEC 18004:2000 Section 8.8.2 - Evaluation of masking results + * + * @see http://www.thonky.com/qr-code-tutorial/data-masking + * @see https://github.com/zxing/zxing/blob/e9e2bd280bcaeabd59d0f955798384fe6c018a6c/core/src/main/java/com/google/zxing/qrcode/encoder/MaskUtil.java + */ +final class MaskPattern{ + + /** + * @see \chillerlan\QRCode\QROptionsTrait::$maskPattern + * + * @var int + */ + public const AUTO = -1; + + public const PATTERN_000 = 0b000; + public const PATTERN_001 = 0b001; + public const PATTERN_010 = 0b010; + public const PATTERN_011 = 0b011; + public const PATTERN_100 = 0b100; + public const PATTERN_101 = 0b101; + public const PATTERN_110 = 0b110; + public const PATTERN_111 = 0b111; + + /** + * @var int[] + */ + public const PATTERNS = [ + self::PATTERN_000, + self::PATTERN_001, + self::PATTERN_010, + self::PATTERN_011, + self::PATTERN_100, + self::PATTERN_101, + self::PATTERN_110, + self::PATTERN_111, + ]; + + /* + * Penalty scores + * + * ISO/IEC 18004:2000 Section 8.8.1 - Table 24 + */ + private const PENALTY_N1 = 3; + private const PENALTY_N2 = 3; + private const PENALTY_N3 = 40; + private const PENALTY_N4 = 10; + + /** + * The current mask pattern value (0-7) + */ + private int $maskPattern; + + /** + * MaskPattern constructor. + * + * @throws \chillerlan\QRCode\QRCodeException + */ + public function __construct(int $maskPattern){ + + if((0b111 & $maskPattern) !== $maskPattern){ + throw new QRCodeException('invalid mask pattern'); + } + + $this->maskPattern = $maskPattern; + } + + /** + * Returns the current mask pattern + */ + public function getPattern():int{ + return $this->maskPattern; + } + + /** + * Returns a closure that applies the mask for the chosen mask pattern. + * + * Note that the diagram in section 6.8.1 is misleading since it indicates that $i is column position + * and $j is row position. In fact, as the text says, $i is row position and $j is column position. + * + * @see https://www.thonky.com/qr-code-tutorial/mask-patterns + * @see https://github.com/zxing/zxing/blob/e9e2bd280bcaeabd59d0f955798384fe6c018a6c/core/src/main/java/com/google/zxing/qrcode/decoder/DataMask.java#L32-L117 + */ + public function getMask():Closure{ + // $x = column (width), $y = row (height) + return [ + self::PATTERN_000 => fn(int $x, int $y):bool => (($x + $y) % 2) === 0, + self::PATTERN_001 => fn(int $x, int $y):bool => ($y % 2) === 0, + self::PATTERN_010 => fn(int $x, int $y):bool => ($x % 3) === 0, + self::PATTERN_011 => fn(int $x, int $y):bool => (($x + $y) % 3) === 0, + self::PATTERN_100 => fn(int $x, int $y):bool => ((intdiv($y, 2) + intdiv($x, 3)) % 2) === 0, + self::PATTERN_101 => fn(int $x, int $y):bool => (($x * $y) % 6) === 0, + self::PATTERN_110 => fn(int $x, int $y):bool => (($x * $y) % 6) < 3, + self::PATTERN_111 => fn(int $x, int $y):bool => (($x + $y + (($x * $y) % 3)) % 2) === 0, + ][$this->maskPattern]; + } + + /** + * Evaluates the matrix of the given data interface and returns a new mask pattern instance for the best result + */ + public static function getBestPattern(QRMatrix $QRMatrix):self{ + $penalties = []; + $size = $QRMatrix->getSize(); + + foreach(self::PATTERNS as $pattern){ + $mp = new self($pattern); + $matrix = (clone $QRMatrix)->setFormatInfo($mp)->mask($mp)->getMatrix(true); + $penalty = 0; + + for($level = 1; $level <= 4; $level++){ + $penalty += self::{'testRule'.$level}($matrix, $size, $size); + } + + $penalties[$pattern] = (int)$penalty; + } + + return new self(array_search(min($penalties), $penalties, true)); + } + + /** + * Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and + * give penalty to them. Example: 00000 or 11111. + */ + public static function testRule1(array $matrix, int $height, int $width):int{ + $penalty = 0; + + // horizontal + foreach($matrix as $row){ + $penalty += self::applyRule1($row); + } + + // vertical + for($x = 0; $x < $width; $x++){ + $penalty += self::applyRule1(array_column($matrix, $x)); + } + + return $penalty; + } + + /** + * + */ + private static function applyRule1(array $rc):int{ + $penalty = 0; + $numSameBitCells = 0; + $prevBit = null; + + foreach($rc as $val){ + + if($val === $prevBit){ + $numSameBitCells++; + } + else{ + + if($numSameBitCells >= 5){ + $penalty += (self::PENALTY_N1 + $numSameBitCells - 5); + } + + $numSameBitCells = 1; // Include the cell itself. + $prevBit = $val; + } + } + + if($numSameBitCells >= 5){ + $penalty += (self::PENALTY_N1 + $numSameBitCells - 5); + } + + return $penalty; + } + + /** + * Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give + * penalty to them. This is actually equivalent to the spec's rule, which is to find MxN blocks and give a + * penalty proportional to (M-1)x(N-1), because this is the number of 2x2 blocks inside such a block. + */ + public static function testRule2(array $matrix, int $height, int $width):int{ + $penalty = 0; + + foreach($matrix as $y => $row){ + + if($y > ($height - 2)){ + break; + } + + foreach($row as $x => $val){ + + if($x > ($width - 2)){ + break; + } + + if( + $val === $row[($x + 1)] + && $val === $matrix[($y + 1)][$x] + && $val === $matrix[($y + 1)][($x + 1)] + ){ + $penalty++; + } + } + } + + return (self::PENALTY_N2 * $penalty); + } + + /** + * Apply mask penalty rule 3 and return the penalty. Find consecutive runs of 1:1:3:1:1:4 + * starting with black, or 4:1:1:3:1:1 starting with white, and give penalty to them. If we + * find patterns like 000010111010000, we give penalty once. + */ + public static function testRule3(array $matrix, int $height, int $width):int{ + $penalties = 0; + + foreach($matrix as $y => $row){ + foreach($row as $x => $val){ + + if( + ($x + 6) < $width + && $val + && !$row[($x + 1)] + && $row[($x + 2)] + && $row[($x + 3)] + && $row[($x + 4)] + && !$row[($x + 5)] + && $row[($x + 6)] + && ( + self::isWhiteHorizontal($row, $width, ($x - 4), $x) + || self::isWhiteHorizontal($row, $width, ($x + 7), ($x + 11)) + ) + ){ + $penalties++; + } + + if( + ($y + 6) < $height + && $val + && !$matrix[($y + 1)][$x] + && $matrix[($y + 2)][$x] + && $matrix[($y + 3)][$x] + && $matrix[($y + 4)][$x] + && !$matrix[($y + 5)][$x] + && $matrix[($y + 6)][$x] + && ( + self::isWhiteVertical($matrix, $height, $x, ($y - 4), $y) + || self::isWhiteVertical($matrix, $height, $x, ($y + 7), ($y + 11)) + ) + ){ + $penalties++; + } + + } + } + + return ($penalties * self::PENALTY_N3); + } + + /** + * + */ + private static function isWhiteHorizontal(array $row, int $width, int $from, int $to):bool{ + + if($from < 0 || $width < $to){ + return false; + } + + for($x = $from; $x < $to; $x++){ + if($row[$x]){ + return false; + } + } + + return true; + } + + /** + * + */ + private static function isWhiteVertical(array $matrix, int $height, int $x, int $from, int $to):bool{ + + if($from < 0 || $height < $to){ + return false; + } + + for($y = $from; $y < $to; $y++){ + if($matrix[$y][$x] === true){ + return false; + } + } + + return true; + } + + /** + * Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give + * penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance. + */ + public static function testRule4(array $matrix, int $height, int $width):int{ + $darkCells = 0; + $totalCells = ($height * $width); + + foreach($matrix as $row){ + foreach($row as $val){ + if($val === true){ + $darkCells++; + } + } + } + + return (intdiv((abs($darkCells * 2 - $totalCells) * 10), $totalCells) * self::PENALTY_N4); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/Mode.php b/src/vendor/chillerlan/php-qrcode/src/Common/Mode.php new file mode 100644 index 00000000..523d3791 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/Mode.php @@ -0,0 +1,96 @@ + + * @copyright 2020 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\Data\{AlphaNum, Byte, Hanzi, Kanji, Number}; +use chillerlan\QRCode\QRCodeException; + +/** + * Data mode information - ISO 18004:2006, 6.4.1, Tables 2 and 3 + */ +final class Mode{ + + // ISO/IEC 18004:2000 Table 2 + + /** @var int */ + public const TERMINATOR = 0b0000; + /** @var int */ + public const NUMBER = 0b0001; + /** @var int */ + public const ALPHANUM = 0b0010; + /** @var int */ + public const BYTE = 0b0100; + /** @var int */ + public const KANJI = 0b1000; + /** @var int */ + public const HANZI = 0b1101; + /** @var int */ + public const STRCTURED_APPEND = 0b0011; + /** @var int */ + public const FNC1_FIRST = 0b0101; + /** @var int */ + public const FNC1_SECOND = 0b1001; + /** @var int */ + public const ECI = 0b0111; + + /** + * mode length bits for the version breakpoints 1-9, 10-26 and 27-40 + * + * ISO/IEC 18004:2000 Table 3 - Number of bits in Character Count Indicator + */ + public const LENGTH_BITS = [ + self::NUMBER => [10, 12, 14], + self::ALPHANUM => [ 9, 11, 13], + self::BYTE => [ 8, 16, 16], + self::KANJI => [ 8, 10, 12], + self::HANZI => [ 8, 10, 12], + self::ECI => [ 0, 0, 0], + ]; + + /** + * Map of data mode => interface (detection order) + * + * @var string[] + */ + public const INTERFACES = [ + self::NUMBER => Number::class, + self::ALPHANUM => AlphaNum::class, + self::KANJI => Kanji::class, + self::HANZI => Hanzi::class, + self::BYTE => Byte::class, + ]; + + /** + * returns the length bits for the version breakpoints 1-9, 10-26 and 27-40 + * + * @throws \chillerlan\QRCode\QRCodeException + */ + public static function getLengthBitsForVersion(int $mode, int $version):int{ + + if(!isset(self::LENGTH_BITS[$mode])){ + throw new QRCodeException('invalid mode given'); + } + + $minVersion = 0; + + foreach([9, 26, 40] as $key => $breakpoint){ + + if($version > $minVersion && $version <= $breakpoint){ + return self::LENGTH_BITS[$mode][$key]; + } + + $minVersion = $breakpoint; + } + + throw new QRCodeException(sprintf('invalid version number: %d', $version)); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/Version.php b/src/vendor/chillerlan/php-qrcode/src/Common/Version.php new file mode 100644 index 00000000..fe7240f8 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/Version.php @@ -0,0 +1,287 @@ + + * @copyright 2020 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\QRCodeException; + +/** + * Version related tables and methods + */ +final class Version{ + + /** + * Enable version auto detection + * + * @see \chillerlan\QRCode\QROptionsTrait::$version + * + * @var int + */ + public const AUTO = -1; + + /** + * ISO/IEC 18004:2000 Annex E, Table E.1 - Row/column coordinates of center module of Alignment Patterns + * + * version -> pattern + * + * @var int[][] + */ + private const ALIGNMENT_PATTERN = [ + 1 => [], + 2 => [6, 18], + 3 => [6, 22], + 4 => [6, 26], + 5 => [6, 30], + 6 => [6, 34], + 7 => [6, 22, 38], + 8 => [6, 24, 42], + 9 => [6, 26, 46], + 10 => [6, 28, 50], + 11 => [6, 30, 54], + 12 => [6, 32, 58], + 13 => [6, 34, 62], + 14 => [6, 26, 46, 66], + 15 => [6, 26, 48, 70], + 16 => [6, 26, 50, 74], + 17 => [6, 30, 54, 78], + 18 => [6, 30, 56, 82], + 19 => [6, 30, 58, 86], + 20 => [6, 34, 62, 90], + 21 => [6, 28, 50, 72, 94], + 22 => [6, 26, 50, 74, 98], + 23 => [6, 30, 54, 78, 102], + 24 => [6, 28, 54, 80, 106], + 25 => [6, 32, 58, 84, 110], + 26 => [6, 30, 58, 86, 114], + 27 => [6, 34, 62, 90, 118], + 28 => [6, 26, 50, 74, 98, 122], + 29 => [6, 30, 54, 78, 102, 126], + 30 => [6, 26, 52, 78, 104, 130], + 31 => [6, 30, 56, 82, 108, 134], + 32 => [6, 34, 60, 86, 112, 138], + 33 => [6, 30, 58, 86, 114, 142], + 34 => [6, 34, 62, 90, 118, 146], + 35 => [6, 30, 54, 78, 102, 126, 150], + 36 => [6, 24, 50, 76, 102, 128, 154], + 37 => [6, 28, 54, 80, 106, 132, 158], + 38 => [6, 32, 58, 84, 110, 136, 162], + 39 => [6, 26, 54, 82, 110, 138, 166], + 40 => [6, 30, 58, 86, 114, 142, 170], + ]; + + /** + * ISO/IEC 18004:2000 Annex D, Table D.1 - Version information bit stream for each version + * + * no version pattern for QR Codes < 7 + * + * @var int[] + */ + private const VERSION_PATTERN = [ + 7 => 0b000111110010010100, + 8 => 0b001000010110111100, + 9 => 0b001001101010011001, + 10 => 0b001010010011010011, + 11 => 0b001011101111110110, + 12 => 0b001100011101100010, + 13 => 0b001101100001000111, + 14 => 0b001110011000001101, + 15 => 0b001111100100101000, + 16 => 0b010000101101111000, + 17 => 0b010001010001011101, + 18 => 0b010010101000010111, + 19 => 0b010011010100110010, + 20 => 0b010100100110100110, + 21 => 0b010101011010000011, + 22 => 0b010110100011001001, + 23 => 0b010111011111101100, + 24 => 0b011000111011000100, + 25 => 0b011001000111100001, + 26 => 0b011010111110101011, + 27 => 0b011011000010001110, + 28 => 0b011100110000011010, + 29 => 0b011101001100111111, + 30 => 0b011110110101110101, + 31 => 0b011111001001010000, + 32 => 0b100000100111010101, + 33 => 0b100001011011110000, + 34 => 0b100010100010111010, + 35 => 0b100011011110011111, + 36 => 0b100100101100001011, + 37 => 0b100101010000101110, + 38 => 0b100110101001100100, + 39 => 0b100111010101000001, + 40 => 0b101000110001101001, + ]; + + /** + * ISO/IEC 18004:2000 Tables 13-22 - Error correction characteristics + * + * @see http://www.thonky.com/qr-code-tutorial/error-correction-table + */ + private const RSBLOCKS = [ + 1 => [[ 7, [[ 1, 19], [ 0, 0]]], [10, [[ 1, 16], [ 0, 0]]], [13, [[ 1, 13], [ 0, 0]]], [17, [[ 1, 9], [ 0, 0]]]], + 2 => [[10, [[ 1, 34], [ 0, 0]]], [16, [[ 1, 28], [ 0, 0]]], [22, [[ 1, 22], [ 0, 0]]], [28, [[ 1, 16], [ 0, 0]]]], + 3 => [[15, [[ 1, 55], [ 0, 0]]], [26, [[ 1, 44], [ 0, 0]]], [18, [[ 2, 17], [ 0, 0]]], [22, [[ 2, 13], [ 0, 0]]]], + 4 => [[20, [[ 1, 80], [ 0, 0]]], [18, [[ 2, 32], [ 0, 0]]], [26, [[ 2, 24], [ 0, 0]]], [16, [[ 4, 9], [ 0, 0]]]], + 5 => [[26, [[ 1, 108], [ 0, 0]]], [24, [[ 2, 43], [ 0, 0]]], [18, [[ 2, 15], [ 2, 16]]], [22, [[ 2, 11], [ 2, 12]]]], + 6 => [[18, [[ 2, 68], [ 0, 0]]], [16, [[ 4, 27], [ 0, 0]]], [24, [[ 4, 19], [ 0, 0]]], [28, [[ 4, 15], [ 0, 0]]]], + 7 => [[20, [[ 2, 78], [ 0, 0]]], [18, [[ 4, 31], [ 0, 0]]], [18, [[ 2, 14], [ 4, 15]]], [26, [[ 4, 13], [ 1, 14]]]], + 8 => [[24, [[ 2, 97], [ 0, 0]]], [22, [[ 2, 38], [ 2, 39]]], [22, [[ 4, 18], [ 2, 19]]], [26, [[ 4, 14], [ 2, 15]]]], + 9 => [[30, [[ 2, 116], [ 0, 0]]], [22, [[ 3, 36], [ 2, 37]]], [20, [[ 4, 16], [ 4, 17]]], [24, [[ 4, 12], [ 4, 13]]]], + 10 => [[18, [[ 2, 68], [ 2, 69]]], [26, [[ 4, 43], [ 1, 44]]], [24, [[ 6, 19], [ 2, 20]]], [28, [[ 6, 15], [ 2, 16]]]], + 11 => [[20, [[ 4, 81], [ 0, 0]]], [30, [[ 1, 50], [ 4, 51]]], [28, [[ 4, 22], [ 4, 23]]], [24, [[ 3, 12], [ 8, 13]]]], + 12 => [[24, [[ 2, 92], [ 2, 93]]], [22, [[ 6, 36], [ 2, 37]]], [26, [[ 4, 20], [ 6, 21]]], [28, [[ 7, 14], [ 4, 15]]]], + 13 => [[26, [[ 4, 107], [ 0, 0]]], [22, [[ 8, 37], [ 1, 38]]], [24, [[ 8, 20], [ 4, 21]]], [22, [[12, 11], [ 4, 12]]]], + 14 => [[30, [[ 3, 115], [ 1, 116]]], [24, [[ 4, 40], [ 5, 41]]], [20, [[11, 16], [ 5, 17]]], [24, [[11, 12], [ 5, 13]]]], + 15 => [[22, [[ 5, 87], [ 1, 88]]], [24, [[ 5, 41], [ 5, 42]]], [30, [[ 5, 24], [ 7, 25]]], [24, [[11, 12], [ 7, 13]]]], + 16 => [[24, [[ 5, 98], [ 1, 99]]], [28, [[ 7, 45], [ 3, 46]]], [24, [[15, 19], [ 2, 20]]], [30, [[ 3, 15], [13, 16]]]], + 17 => [[28, [[ 1, 107], [ 5, 108]]], [28, [[10, 46], [ 1, 47]]], [28, [[ 1, 22], [15, 23]]], [28, [[ 2, 14], [17, 15]]]], + 18 => [[30, [[ 5, 120], [ 1, 121]]], [26, [[ 9, 43], [ 4, 44]]], [28, [[17, 22], [ 1, 23]]], [28, [[ 2, 14], [19, 15]]]], + 19 => [[28, [[ 3, 113], [ 4, 114]]], [26, [[ 3, 44], [11, 45]]], [26, [[17, 21], [ 4, 22]]], [26, [[ 9, 13], [16, 14]]]], + 20 => [[28, [[ 3, 107], [ 5, 108]]], [26, [[ 3, 41], [13, 42]]], [30, [[15, 24], [ 5, 25]]], [28, [[15, 15], [10, 16]]]], + 21 => [[28, [[ 4, 116], [ 4, 117]]], [26, [[17, 42], [ 0, 0]]], [28, [[17, 22], [ 6, 23]]], [30, [[19, 16], [ 6, 17]]]], + 22 => [[28, [[ 2, 111], [ 7, 112]]], [28, [[17, 46], [ 0, 0]]], [30, [[ 7, 24], [16, 25]]], [24, [[34, 13], [ 0, 0]]]], + 23 => [[30, [[ 4, 121], [ 5, 122]]], [28, [[ 4, 47], [14, 48]]], [30, [[11, 24], [14, 25]]], [30, [[16, 15], [14, 16]]]], + 24 => [[30, [[ 6, 117], [ 4, 118]]], [28, [[ 6, 45], [14, 46]]], [30, [[11, 24], [16, 25]]], [30, [[30, 16], [ 2, 17]]]], + 25 => [[26, [[ 8, 106], [ 4, 107]]], [28, [[ 8, 47], [13, 48]]], [30, [[ 7, 24], [22, 25]]], [30, [[22, 15], [13, 16]]]], + 26 => [[28, [[10, 114], [ 2, 115]]], [28, [[19, 46], [ 4, 47]]], [28, [[28, 22], [ 6, 23]]], [30, [[33, 16], [ 4, 17]]]], + 27 => [[30, [[ 8, 122], [ 4, 123]]], [28, [[22, 45], [ 3, 46]]], [30, [[ 8, 23], [26, 24]]], [30, [[12, 15], [28, 16]]]], + 28 => [[30, [[ 3, 117], [10, 118]]], [28, [[ 3, 45], [23, 46]]], [30, [[ 4, 24], [31, 25]]], [30, [[11, 15], [31, 16]]]], + 29 => [[30, [[ 7, 116], [ 7, 117]]], [28, [[21, 45], [ 7, 46]]], [30, [[ 1, 23], [37, 24]]], [30, [[19, 15], [26, 16]]]], + 30 => [[30, [[ 5, 115], [10, 116]]], [28, [[19, 47], [10, 48]]], [30, [[15, 24], [25, 25]]], [30, [[23, 15], [25, 16]]]], + 31 => [[30, [[13, 115], [ 3, 116]]], [28, [[ 2, 46], [29, 47]]], [30, [[42, 24], [ 1, 25]]], [30, [[23, 15], [28, 16]]]], + 32 => [[30, [[17, 115], [ 0, 0]]], [28, [[10, 46], [23, 47]]], [30, [[10, 24], [35, 25]]], [30, [[19, 15], [35, 16]]]], + 33 => [[30, [[17, 115], [ 1, 116]]], [28, [[14, 46], [21, 47]]], [30, [[29, 24], [19, 25]]], [30, [[11, 15], [46, 16]]]], + 34 => [[30, [[13, 115], [ 6, 116]]], [28, [[14, 46], [23, 47]]], [30, [[44, 24], [ 7, 25]]], [30, [[59, 16], [ 1, 17]]]], + 35 => [[30, [[12, 121], [ 7, 122]]], [28, [[12, 47], [26, 48]]], [30, [[39, 24], [14, 25]]], [30, [[22, 15], [41, 16]]]], + 36 => [[30, [[ 6, 121], [14, 122]]], [28, [[ 6, 47], [34, 48]]], [30, [[46, 24], [10, 25]]], [30, [[ 2, 15], [64, 16]]]], + 37 => [[30, [[17, 122], [ 4, 123]]], [28, [[29, 46], [14, 47]]], [30, [[49, 24], [10, 25]]], [30, [[24, 15], [46, 16]]]], + 38 => [[30, [[ 4, 122], [18, 123]]], [28, [[13, 46], [32, 47]]], [30, [[48, 24], [14, 25]]], [30, [[42, 15], [32, 16]]]], + 39 => [[30, [[20, 117], [ 4, 118]]], [28, [[40, 47], [ 7, 48]]], [30, [[43, 24], [22, 25]]], [30, [[10, 15], [67, 16]]]], + 40 => [[30, [[19, 118], [ 6, 119]]], [28, [[18, 47], [31, 48]]], [30, [[34, 24], [34, 25]]], [30, [[20, 15], [61, 16]]]], + ]; + + /** + * ISO/IEC 18004:2000 Table 1 - Data capacity of all versions of QR Code + */ + private const TOTAL_CODEWORDS = [ + 1 => 26, + 2 => 44, + 3 => 70, + 4 => 100, + 5 => 134, + 6 => 172, + 7 => 196, + 8 => 242, + 9 => 292, + 10 => 346, + 11 => 404, + 12 => 466, + 13 => 532, + 14 => 581, + 15 => 655, + 16 => 733, + 17 => 815, + 18 => 901, + 19 => 991, + 20 => 1085, + 21 => 1156, + 22 => 1258, + 23 => 1364, + 24 => 1474, + 25 => 1588, + 26 => 1706, + 27 => 1828, + 28 => 1921, + 29 => 2051, + 30 => 2185, + 31 => 2323, + 32 => 2465, + 33 => 2611, + 34 => 2761, + 35 => 2876, + 36 => 3034, + 37 => 3196, + 38 => 3362, + 39 => 3532, + 40 => 3706, + ]; + + /** + * QR Code version number + */ + private int $version; + + /** + * Version constructor. + * + * @throws \chillerlan\QRCode\QRCodeException + */ + public function __construct(int $version){ + + if($version < 1 || $version > 40){ + throw new QRCodeException('invalid version given'); + } + + $this->version = $version; + } + + /** + * returns the current version number as string + */ + public function __toString():string{ + return (string)$this->version; + } + + /** + * returns the current version number + */ + public function getVersionNumber():int{ + return $this->version; + } + + /** + * the matrix size for the given version + */ + public function getDimension():int{ + return (($this->version * 4) + 17); + } + + /** + * the version pattern for the given version + */ + public function getVersionPattern():?int{ + return (self::VERSION_PATTERN[$this->version] ?? null); + } + + /** + * the alignment patterns for the current version + * + * @return int[] + */ + public function getAlignmentPattern():array{ + return self::ALIGNMENT_PATTERN[$this->version]; + } + + /** + * returns ECC block information for the given $version and $eccLevel + */ + public function getRSBlocks(EccLevel $eccLevel):array{ + return self::RSBLOCKS[$this->version][$eccLevel->getOrdinal()]; + } + + /** + * returns the maximum codewords for the current version + */ + public function getTotalCodewords():int{ + return self::TOTAL_CODEWORDS[$this->version]; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php b/src/vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php new file mode 100644 index 00000000..77242d72 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php @@ -0,0 +1,137 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, Mode}; +use function array_flip, ceil, intdiv, str_split; + +/** + * Alphanumeric mode: 0 to 9, A to Z, space, $ % * + - . / : + * + * ISO/IEC 18004:2000 Section 8.3.3 + * ISO/IEC 18004:2000 Section 8.4.3 + */ +final class AlphaNum extends QRDataModeAbstract{ + + /** + * ISO/IEC 18004:2000 Table 5 + * + * @var int[] + */ + private const CHAR_TO_ORD = [ + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, + '8' => 8, '9' => 9, 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 'E' => 14, 'F' => 15, + 'G' => 16, 'H' => 17, 'I' => 18, 'J' => 19, 'K' => 20, 'L' => 21, 'M' => 22, 'N' => 23, + 'O' => 24, 'P' => 25, 'Q' => 26, 'R' => 27, 'S' => 28, 'T' => 29, 'U' => 30, 'V' => 31, + 'W' => 32, 'X' => 33, 'Y' => 34, 'Z' => 35, ' ' => 36, '$' => 37, '%' => 38, '*' => 39, + '+' => 40, '-' => 41, '.' => 42, '/' => 43, ':' => 44, + ]; + + /** + * @inheritDoc + */ + public const DATAMODE = Mode::ALPHANUM; + + /** + * @inheritDoc + */ + public function getLengthInBits():int{ + return (int)ceil($this->getCharCount() * (11 / 2)); + } + + /** + * @inheritDoc + */ + public static function validateString(string $string):bool{ + + if($string === ''){ + return false; + } + + foreach(str_split($string) as $chr){ + if(!isset(self::CHAR_TO_ORD[$chr])){ + return false; + } + } + + return true; + } + + /** + * @inheritDoc + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{ + $len = $this->getCharCount(); + + $bitBuffer + ->put(self::DATAMODE, 4) + ->put($len, $this::getLengthBits($versionNumber)) + ; + + // encode 2 characters in 11 bits + for($i = 0; ($i + 1) < $len; $i += 2){ + $bitBuffer->put((self::CHAR_TO_ORD[$this->data[$i]] * 45 + self::CHAR_TO_ORD[$this->data[($i + 1)]]), 11); + } + + // encode a remaining character in 6 bits + if($i < $len){ + $bitBuffer->put(self::CHAR_TO_ORD[$this->data[$i]], 6); + } + + return $this; + } + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + $charmap = array_flip(self::CHAR_TO_ORD); + + // @todo + $toAlphaNumericChar = function(int $ord) use ($charmap):string{ + + if(isset($charmap[$ord])){ + return $charmap[$ord]; + } + + throw new QRCodeDataException('invalid character value: '.$ord); + }; + + $result = ''; + // Read two characters at a time + while($length > 1){ + + if($bitBuffer->available() < 11){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + $nextTwoCharsBits = $bitBuffer->read(11); + $result .= $toAlphaNumericChar(intdiv($nextTwoCharsBits, 45)); + $result .= $toAlphaNumericChar($nextTwoCharsBits % 45); + $length -= 2; + } + + if($length === 1){ + // special case: one character left + if($bitBuffer->available() < 6){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + $result .= $toAlphaNumericChar($bitBuffer->read(6)); + } + + return $result; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/Byte.php b/src/vendor/chillerlan/php-qrcode/src/Data/Byte.php new file mode 100644 index 00000000..10ab8526 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/Byte.php @@ -0,0 +1,85 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, Mode}; +use function chr, ord; + +/** + * 8-bit Byte mode, ISO-8859-1 or UTF-8 + * + * ISO/IEC 18004:2000 Section 8.3.4 + * ISO/IEC 18004:2000 Section 8.4.4 + */ +final class Byte extends QRDataModeAbstract{ + + /** + * @inheritDoc + */ + public const DATAMODE = Mode::BYTE; + + /** + * @inheritDoc + */ + public function getLengthInBits():int{ + return ($this->getCharCount() * 8); + } + + /** + * @inheritDoc + */ + public static function validateString(string $string):bool{ + return $string !== ''; + } + + /** + * @inheritDoc + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{ + $len = $this->getCharCount(); + + $bitBuffer + ->put(self::DATAMODE, 4) + ->put($len, $this::getLengthBits($versionNumber)) + ; + + $i = 0; + + while($i < $len){ + $bitBuffer->put(ord($this->data[$i]), 8); + $i++; + } + + return $this; + } + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + + if($bitBuffer->available() < (8 * $length)){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + $readBytes = ''; + + for($i = 0; $i < $length; $i++){ + $readBytes .= chr($bitBuffer->read(8)); + } + + return $readBytes; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/ECI.php b/src/vendor/chillerlan/php-qrcode/src/Data/ECI.php new file mode 100644 index 00000000..3029e837 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/ECI.php @@ -0,0 +1,155 @@ + + * @copyright 2020 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, ECICharset, Mode}; +use function mb_convert_encoding, mb_detect_encoding, mb_internal_encoding, sprintf; + +/** + * Adds an ECI Designator + * + * ISO/IEC 18004:2000 8.4.1.1 + * + * Please note that you have to take care for the correct data encoding when adding with QRCode::add*Segment() + */ +final class ECI extends QRDataModeAbstract{ + + /** + * @inheritDoc + */ + public const DATAMODE = Mode::ECI; + + /** + * The current ECI encoding id + */ + private int $encoding; + + /** + * @inheritDoc + * @noinspection PhpMissingParentConstructorInspection + */ + public function __construct(int $encoding){ + + if($encoding < 0 || $encoding > 999999){ + throw new QRCodeDataException(sprintf('invalid encoding id: "%s"', $encoding)); + } + + $this->encoding = $encoding; + } + + /** + * @inheritDoc + */ + public function getLengthInBits():int{ + + if($this->encoding < 128){ + return 8; + } + + if($this->encoding < 16384){ + return 16; + } + + return 24; + } + + /** + * Writes an ECI designator to the bitbuffer + * + * @inheritDoc + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{ + $bitBuffer->put(self::DATAMODE, 4); + + if($this->encoding < 128){ + $bitBuffer->put($this->encoding, 8); + } + elseif($this->encoding < 16384){ + $bitBuffer->put(($this->encoding | 0x8000), 16); + } + elseif($this->encoding < 1000000){ + $bitBuffer->put(($this->encoding | 0xC00000), 24); + } + else{ + throw new QRCodeDataException('invalid ECI ID'); + } + + return $this; + } + + /** + * Reads and parses the value of an ECI designator + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function parseValue(BitBuffer $bitBuffer):ECICharset{ + $firstByte = $bitBuffer->read(8); + + // just one byte + if(($firstByte & 0b10000000) === 0){ + $id = ($firstByte & 0b01111111); + } + // two bytes + elseif(($firstByte & 0b11000000) === 0b10000000){ + $id = ((($firstByte & 0b00111111) << 8) | $bitBuffer->read(8)); + } + // three bytes + elseif(($firstByte & 0b11100000) === 0b11000000){ + $id = ((($firstByte & 0b00011111) << 16) | $bitBuffer->read(16)); + } + else{ + throw new QRCodeDataException(sprintf('error decoding ECI value first byte: %08b', $firstByte)); // @codeCoverageIgnore + } + + return new ECICharset($id); + } + + /** + * @codeCoverageIgnore Unused, but required as per interface + */ + public static function validateString(string $string):bool{ + return true; + } + + /** + * Reads and decodes the ECI designator including the following byte sequence + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $eciCharset = self::parseValue($bitBuffer); + $nextMode = $bitBuffer->read(4); + + if($nextMode !== Mode::BYTE){ + throw new QRCodeDataException(sprintf('ECI designator followed by invalid mode: "%04b"', $nextMode)); + } + + $data = Byte::decodeSegment($bitBuffer, $versionNumber); + $encoding = $eciCharset->getName(); + + if($encoding === null){ + // The spec isn't clear on this mode; see + // section 6.4.5: t does not say which encoding to assuming + // upon decoding. I have seen ISO-8859-1 used as well as + // Shift_JIS -- without anything like an ECI designator to + // give a hint. + $encoding = mb_detect_encoding($data, ['ISO-8859-1', 'Windows-1252', 'SJIS', 'UTF-8'], true); + + if($encoding === false){ + throw new QRCodeDataException('could not determine encoding in ECI mode'); // @codeCoverageIgnore + } + } + + return mb_convert_encoding($data, mb_internal_encoding(), $encoding); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/Hanzi.php b/src/vendor/chillerlan/php-qrcode/src/Data/Hanzi.php new file mode 100644 index 00000000..4476ad9d --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/Hanzi.php @@ -0,0 +1,205 @@ + + * @copyright 2020 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, Mode}; +use Throwable; +use function chr, implode, intdiv, is_string, mb_convert_encoding, mb_detect_encoding, + mb_detect_order, mb_internal_encoding, mb_strlen, ord, sprintf, strlen; + +/** + * Hanzi (simplified Chinese) mode, GBT18284-2000: 13-bit double-byte characters from the GB2312/GB18030 character set + * + * Please note that this is not part of the QR Code specification and may not be supported by all readers (ZXing-based ones do). + * + * @see https://en.wikipedia.org/wiki/GB_2312 + * @see http://www.herongyang.com/GB2312/Introduction-of-GB2312.html + * @see https://en.wikipedia.org/wiki/GBK_(character_encoding)#Encoding + * @see https://gist.github.com/codemasher/91da33c44bfb48a81a6c1426bb8e4338 + * @see https://github.com/zxing/zxing/blob/dfb06fa33b17a9e68321be151c22846c7b78048f/core/src/main/java/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java#L172-L209 + * @see https://www.chinesestandard.net/PDF/English.aspx/GBT18284-2000 + */ +final class Hanzi extends QRDataModeAbstract{ + + /** + * possible values: GB2312, GB18030 + * + * @var string + */ + public const ENCODING = 'GB18030'; + + /** + * @todo: other subsets??? + * + * @var int + */ + public const GB2312_SUBSET = 0b0001; + + /** + * @inheritDoc + */ + public const DATAMODE = Mode::HANZI; + + /** + * @inheritDoc + */ + protected function getCharCount():int{ + return mb_strlen($this->data, self::ENCODING); + } + + /** + * @inheritDoc + */ + public function getLengthInBits():int{ + return ($this->getCharCount() * 13); + } + + /** + * @inheritDoc + */ + public static function convertEncoding(string $string):string{ + mb_detect_order([mb_internal_encoding(), 'UTF-8', 'GB2312', 'GB18030', 'CP936', 'EUC-CN', 'HZ']); + + $detected = mb_detect_encoding($string, null, true); + + if($detected === false){ + throw new QRCodeDataException('mb_detect_encoding error'); + } + + if($detected === self::ENCODING){ + return $string; + } + + $string = mb_convert_encoding($string, self::ENCODING, $detected); + + if(!is_string($string)){ + throw new QRCodeDataException('mb_convert_encoding error'); + } + + return $string; + } + + /** + * checks if a string qualifies as Hanzi/GB2312 + */ + public static function validateString(string $string):bool{ + + try{ + $string = self::convertEncoding($string); + } + catch(Throwable $e){ + return false; + } + + $len = strlen($string); + + if($len < 2 || ($len % 2) !== 0){ + return false; + } + + for($i = 0; $i < $len; $i += 2){ + $byte1 = ord($string[$i]); + $byte2 = ord($string[($i + 1)]); + + // byte 1 unused ranges + if($byte1 < 0xa1 || ($byte1 > 0xa9 && $byte1 < 0xb0) || $byte1 > 0xf7){ + return false; + } + + // byte 2 unused ranges + if($byte2 < 0xa1 || $byte2 > 0xfe){ + return false; + } + + } + + return true; + } + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException on an illegal character occurence + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{ + + $bitBuffer + ->put(self::DATAMODE, 4) + ->put($this::GB2312_SUBSET, 4) + ->put($this->getCharCount(), $this::getLengthBits($versionNumber)) + ; + + $len = strlen($this->data); + + for($i = 0; ($i + 1) < $len; $i += 2){ + $c = (((0xff & ord($this->data[$i])) << 8) | (0xff & ord($this->data[($i + 1)]))); + + if($c >= 0xa1a1 && $c <= 0xaafe){ + $c -= 0x0a1a1; + } + elseif($c >= 0xb0a1 && $c <= 0xfafe){ + $c -= 0x0a6a1; + } + else{ + throw new QRCodeDataException(sprintf('illegal char at %d [%d]', ($i + 1), $c)); + } + + $bitBuffer->put((((($c >> 8) & 0xff) * 0x060) + ($c & 0xff)), 13); + } + + if($i < $len){ + throw new QRCodeDataException(sprintf('illegal char at %d', ($i + 1))); + } + + return $this; + } + + /** + * See specification GBT 18284-2000 + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + + // Hanzi mode contains a subset indicator right after mode indicator + if($bitBuffer->read(4) !== self::GB2312_SUBSET){ + throw new QRCodeDataException('ecpected subset indicator for Hanzi mode'); + } + + $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + + if($bitBuffer->available() < ($length * 13)){ + throw new QRCodeDataException('not enough bits available'); + } + + // Each character will require 2 bytes. Read the characters as 2-byte pairs and decode as GB2312 afterwards + $buffer = []; + $offset = 0; + + while($length > 0){ + // Each 13 bits encodes a 2-byte character + $twoBytes = $bitBuffer->read(13); + $assembledTwoBytes = ((intdiv($twoBytes, 0x060) << 8) | ($twoBytes % 0x060)); + + $assembledTwoBytes += ($assembledTwoBytes < 0x00a00) // 0x003BF + ? 0x0a1a1 // In the 0xA1A1 to 0xAAFE range + : 0x0a6a1; // In the 0xB0A1 to 0xFAFE range + + $buffer[$offset] = chr(0xff & ($assembledTwoBytes >> 8)); + $buffer[($offset + 1)] = chr(0xff & $assembledTwoBytes); + $offset += 2; + $length--; + } + + return mb_convert_encoding(implode($buffer), mb_internal_encoding(), self::ENCODING); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/Kanji.php b/src/vendor/chillerlan/php-qrcode/src/Data/Kanji.php new file mode 100644 index 00000000..e42f96dc --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/Kanji.php @@ -0,0 +1,191 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, Mode}; +use Throwable; +use function chr, implode, intdiv, is_string, mb_convert_encoding, mb_detect_encoding, + mb_detect_order, mb_internal_encoding, mb_strlen, ord, sprintf, strlen; + +/** + * Kanji mode: 13-bit double-byte characters from the Shift-JIS character set + * + * ISO/IEC 18004:2000 Section 8.3.5 + * ISO/IEC 18004:2000 Section 8.4.5 + * + * @see https://en.wikipedia.org/wiki/Shift_JIS#As_defined_in_JIS_X_0208:1997 + * @see http://www.rikai.com/library/kanjitables/kanji_codes.sjis.shtml + * @see https://gist.github.com/codemasher/d07d3e6e9346c08e7a41b8b978784952 + */ +final class Kanji extends QRDataModeAbstract{ + + /** + * possible values: SJIS, SJIS-2004 + * + * SJIS-2004 may produce errors in PHP < 8 + * + * @var string + */ + public const ENCODING = 'SJIS'; + + /** + * @inheritDoc + */ + public const DATAMODE = Mode::KANJI; + + /** + * @inheritDoc + */ + protected function getCharCount():int{ + return mb_strlen($this->data, self::ENCODING); + } + + /** + * @inheritDoc + */ + public function getLengthInBits():int{ + return ($this->getCharCount() * 13); + } + + /** + * @inheritDoc + */ + public static function convertEncoding(string $string):string{ + mb_detect_order([mb_internal_encoding(), 'UTF-8', 'SJIS', 'SJIS-2004']); + + $detected = mb_detect_encoding($string, null, true); + + if($detected === false){ + throw new QRCodeDataException('mb_detect_encoding error'); + } + + if($detected === self::ENCODING){ + return $string; + } + + $string = mb_convert_encoding($string, self::ENCODING, $detected); + + if(!is_string($string)){ + throw new QRCodeDataException(sprintf('invalid encoding: %s', $detected)); + } + + return $string; + } + + /** + * checks if a string qualifies as SJIS Kanji + */ + public static function validateString(string $string):bool{ + + try{ + $string = self::convertEncoding($string); + } + catch(Throwable $e){ + return false; + } + + $len = strlen($string); + + if($len < 2 || ($len % 2) !== 0){ + return false; + } + + for($i = 0; $i < $len; $i += 2){ + $byte1 = ord($string[$i]); + $byte2 = ord($string[($i + 1)]); + + // byte 1 unused and vendor ranges + if($byte1 < 0x81 || ($byte1 > 0x84 && $byte1 < 0x88) || ($byte1 > 0x9f && $byte1 < 0xe0) || $byte1 > 0xea){ + return false; + } + + // byte 2 unused ranges + if($byte2 < 0x40 || $byte2 === 0x7f || $byte2 > 0xfc){ + return false; + } + + } + + return true; + } + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException on an illegal character occurence + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{ + + $bitBuffer + ->put(self::DATAMODE, 4) + ->put($this->getCharCount(), $this::getLengthBits($versionNumber)) + ; + + $len = strlen($this->data); + + for($i = 0; ($i + 1) < $len; $i += 2){ + $c = (((0xff & ord($this->data[$i])) << 8) | (0xff & ord($this->data[($i + 1)]))); + + if($c >= 0x8140 && $c <= 0x9ffc){ + $c -= 0x8140; + } + elseif($c >= 0xe040 && $c <= 0xebbf){ + $c -= 0xc140; + } + else{ + throw new QRCodeDataException(sprintf('illegal char at %d [%d]', ($i + 1), $c)); + } + + $bitBuffer->put((((($c >> 8) & 0xff) * 0xc0) + ($c & 0xff)), 13); + } + + if($i < $len){ + throw new QRCodeDataException(sprintf('illegal char at %d', ($i + 1))); + } + + return $this; + } + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + + if($bitBuffer->available() < ($length * 13)){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + // Each character will require 2 bytes. Read the characters as 2-byte pairs and decode as SJIS afterwards + $buffer = []; + $offset = 0; + + while($length > 0){ + // Each 13 bits encodes a 2-byte character + $twoBytes = $bitBuffer->read(13); + $assembledTwoBytes = ((intdiv($twoBytes, 0x0c0) << 8) | ($twoBytes % 0x0c0)); + + $assembledTwoBytes += ($assembledTwoBytes < 0x01f00) + ? 0x08140 // In the 0x8140 to 0x9FFC range + : 0x0c140; // In the 0xE040 to 0xEBBF range + + $buffer[$offset] = chr(0xff & ($assembledTwoBytes >> 8)); + $buffer[($offset + 1)] = chr(0xff & $assembledTwoBytes); + $offset += 2; + $length--; + } + + return mb_convert_encoding(implode($buffer), mb_internal_encoding(), self::ENCODING); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/Number.php b/src/vendor/chillerlan/php-qrcode/src/Data/Number.php new file mode 100644 index 00000000..285be373 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/Number.php @@ -0,0 +1,182 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, Mode}; +use function array_flip, ceil, intdiv, str_split, substr, unpack; + +/** + * Numeric mode: decimal digits 0 to 9 + * + * ISO/IEC 18004:2000 Section 8.3.2 + * ISO/IEC 18004:2000 Section 8.4.2 + */ +final class Number extends QRDataModeAbstract{ + + /** + * @var int[] + */ + private const NUMBER_TO_ORD = [ + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, + ]; + + /** + * @inheritDoc + */ + public const DATAMODE = Mode::NUMBER; + + /** + * @inheritDoc + */ + public function getLengthInBits():int{ + return (int)ceil($this->getCharCount() * (10 / 3)); + } + + /** + * @inheritDoc + */ + public static function validateString(string $string):bool{ + + if($string === ''){ + return false; + } + + foreach(str_split($string) as $chr){ + if(!isset(self::NUMBER_TO_ORD[$chr])){ + return false; + } + } + + return true; + } + + /** + * @inheritDoc + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{ + $len = $this->getCharCount(); + + $bitBuffer + ->put(self::DATAMODE, 4) + ->put($len, $this::getLengthBits($versionNumber)) + ; + + $i = 0; + + // encode numeric triplets in 10 bits + while(($i + 2) < $len){ + $bitBuffer->put($this->parseInt(substr($this->data, $i, 3)), 10); + $i += 3; + } + + if($i < $len){ + + // encode 2 remaining numbers in 7 bits + if(($len - $i) === 2){ + $bitBuffer->put($this->parseInt(substr($this->data, $i, 2)), 7); + } + // encode one remaining number in 4 bits + elseif(($len - $i) === 1){ + $bitBuffer->put($this->parseInt(substr($this->data, $i, 1)), 4); + } + + } + + return $this; + } + + /** + * get the code for the given numeric string + */ + private function parseInt(string $string):int{ + $num = 0; + + foreach(unpack('C*', $string) as $chr){ + $num = ($num * 10 + $chr - 48); + } + + return $num; + } + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + $charmap = array_flip(self::NUMBER_TO_ORD); + + // @todo + $toNumericChar = function(int $ord) use ($charmap):string{ + + if(isset($charmap[$ord])){ + return $charmap[$ord]; + } + + throw new QRCodeDataException('invalid character value: '.$ord); + }; + + $result = ''; + // Read three digits at a time + while($length >= 3){ + // Each 10 bits encodes three digits + if($bitBuffer->available() < 10){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + $threeDigitsBits = $bitBuffer->read(10); + + if($threeDigitsBits >= 1000){ + throw new QRCodeDataException('error decoding numeric value'); + } + + $result .= $toNumericChar(intdiv($threeDigitsBits, 100)); + $result .= $toNumericChar(intdiv($threeDigitsBits, 10) % 10); + $result .= $toNumericChar($threeDigitsBits % 10); + + $length -= 3; + } + + if($length === 2){ + // Two digits left over to read, encoded in 7 bits + if($bitBuffer->available() < 7){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + $twoDigitsBits = $bitBuffer->read(7); + + if($twoDigitsBits >= 100){ + throw new QRCodeDataException('error decoding numeric value'); + } + + $result .= $toNumericChar(intdiv($twoDigitsBits, 10)); + $result .= $toNumericChar($twoDigitsBits % 10); + } + elseif($length === 1){ + // One digit left over to read + if($bitBuffer->available() < 4){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + $digitBits = $bitBuffer->read(4); + + if($digitBits >= 10){ + throw new QRCodeDataException('error decoding numeric value'); + } + + $result .= $toNumericChar($digitBits); + } + + return $result; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php b/src/vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php new file mode 100644 index 00000000..04ffbd7e --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php @@ -0,0 +1,20 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\QRCodeException; + +/** + * An exception container + */ +final class QRCodeDataException extends QRCodeException{ + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/QRData.php b/src/vendor/chillerlan/php-qrcode/src/Data/QRData.php new file mode 100644 index 00000000..4b2dcf77 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/QRData.php @@ -0,0 +1,263 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, EccLevel, Mode, Version}; +use chillerlan\Settings\SettingsContainerInterface; +use function sprintf; + +/** + * Processes the binary data and maps it on a QRMatrix which is then being returned + */ +final class QRData{ + + /** + * the options instance + * + * @var \chillerlan\Settings\SettingsContainerInterface|\chillerlan\QRCode\QROptions + */ + private SettingsContainerInterface $options; + + /** + * a BitBuffer instance + */ + private BitBuffer $bitBuffer; + + /** + * an EccLevel instance + */ + private EccLevel $eccLevel; + + /** + * current QR Code version + */ + private Version $version; + + /** + * @var \chillerlan\QRCode\Data\QRDataModeInterface[] + */ + private array $dataSegments = []; + + /** + * Max bits for the current ECC mode + * + * @var int[] + */ + private array $maxBitsForEcc; + + /** + * QRData constructor. + */ + public function __construct(SettingsContainerInterface $options, array $dataSegments = []){ + $this->options = $options; + $this->bitBuffer = new BitBuffer; + $this->eccLevel = new EccLevel($this->options->eccLevel); + $this->maxBitsForEcc = $this->eccLevel->getMaxBits(); + + $this->setData($dataSegments); + } + + /** + * Sets the data string (internally called by the constructor) + * + * Subsequent calls will overwrite the current state - use the QRCode::add*Segement() method instead + * + * @param \chillerlan\QRCode\Data\QRDataModeInterface[] $dataSegments + */ + public function setData(array $dataSegments):self{ + $this->dataSegments = $dataSegments; + $this->version = $this->getMinimumVersion(); + + $this->bitBuffer->clear(); + $this->writeBitBuffer(); + + return $this; + } + + /** + * Returns the current BitBuffer instance + * + * @codeCoverageIgnore + */ + public function getBitBuffer():BitBuffer{ + return $this->bitBuffer; + } + + /** + * Sets a BitBuffer object + * + * This can be used instead of setData(), however, the version auto-detection is not available in this case. + * The version needs to match the length bits range for the data mode the data has been encoded with, + * additionally the bit array needs to contain enough pad bits. + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function setBitBuffer(BitBuffer $bitBuffer):self{ + + if($this->options->version === Version::AUTO){ + throw new QRCodeDataException('version auto detection is not available'); + } + + if($bitBuffer->getLength() === 0){ + throw new QRCodeDataException('the given BitBuffer is empty'); + } + + $this->dataSegments = []; + $this->bitBuffer = $bitBuffer; + $this->version = new Version($this->options->version); + + return $this; + } + + /** + * returns a fresh matrix object with the data written and masked with the given $maskPattern + */ + public function writeMatrix():QRMatrix{ + return (new QRMatrix($this->version, $this->eccLevel)) + ->initFunctionalPatterns() + ->writeCodewords($this->bitBuffer) + ; + } + + /** + * estimates the total length of the several mode segments in order to guess the minimum version + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function estimateTotalBitLength():int{ + $length = 0; + + foreach($this->dataSegments as $segment){ + // data length of the current segment + $length += $segment->getLengthInBits(); + // +4 bits for the mode descriptor + $length += 4; + // Hanzi mode sets an additional 4 bit long subset identifier + if($segment instanceof Hanzi){ + $length += 4; + } + } + + $provisionalVersion = null; + + foreach($this->maxBitsForEcc as $version => $maxBits){ + + if($length <= $maxBits){ + $provisionalVersion = $version; + } + + } + + if($provisionalVersion !== null){ + + // add character count indicator bits for the provisional version + foreach($this->dataSegments as $segment){ + $length += Mode::getLengthBitsForVersion($segment::DATAMODE, $provisionalVersion); + } + + // it seems that in some cases the estimated total length is not 100% accurate, + // so we substract 4 bits from the total when not in mixed mode + if(count($this->dataSegments) <= 1){ + $length -= 4; + } + + // we've got a match! + // or let's see if there's a higher version number available + if($length <= $this->maxBitsForEcc[$provisionalVersion] || isset($this->maxBitsForEcc[($provisionalVersion + 1)])){ + return $length; + } + + } + + throw new QRCodeDataException(sprintf('estimated data exceeds %d bits', $length)); + } + + /** + * returns the minimum version number for the given string + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function getMinimumVersion():Version{ + + if($this->options->version !== Version::AUTO){ + return new Version($this->options->version); + } + + $total = $this->estimateTotalBitLength(); + + // guess the version number within the given range + for($version = $this->options->versionMin; $version <= $this->options->versionMax; $version++){ + if($total <= ($this->maxBitsForEcc[$version] - 4)){ + return new Version($version); + } + } + + // it's almost impossible to run into this one as $this::estimateTotalBitLength() would throw first + throw new QRCodeDataException('failed to guess minimum version'); // @codeCoverageIgnore + } + + /** + * creates a BitBuffer and writes the string data to it + * + * @throws \chillerlan\QRCode\QRCodeException on data overflow + */ + private function writeBitBuffer():void{ + $MAX_BITS = $this->eccLevel->getMaxBitsForVersion($this->version); + + foreach($this->dataSegments as $segment){ + $segment->write($this->bitBuffer, $this->version->getVersionNumber()); + } + + // overflow, likely caused due to invalid version setting + if($this->bitBuffer->getLength() > $MAX_BITS){ + throw new QRCodeDataException( + sprintf('code length overflow. (%d > %d bit)', $this->bitBuffer->getLength(), $MAX_BITS) + ); + } + + // add terminator (ISO/IEC 18004:2000 Table 2) + if(($this->bitBuffer->getLength() + 4) <= $MAX_BITS){ + $this->bitBuffer->put(Mode::TERMINATOR, 4); + } + + // Padding: ISO/IEC 18004:2000 8.4.9 Bit stream to codeword conversion + + // if the final codeword is not exactly 8 bits in length, it shall be made 8 bits long + // by the addition of padding bits with binary value 0 + while(($this->bitBuffer->getLength() % 8) !== 0){ + + if($this->bitBuffer->getLength() === $MAX_BITS){ + break; + } + + $this->bitBuffer->putBit(false); + } + + // The message bit stream shall then be extended to fill the data capacity of the symbol + // corresponding to the Version and Error Correction Level, by the addition of the Pad + // Codewords 11101100 and 00010001 alternately. + $alternate = false; + + while(($this->bitBuffer->getLength() + 8) <= $MAX_BITS){ + $this->bitBuffer->put(($alternate) ? 0b00010001 : 0b11101100, 8); + + $alternate = !$alternate; + } + + // In certain versions of symbol, it may be necessary to add 3, 4 or 7 Remainder Bits (all zeros) + // to the end of the message in order exactly to fill the symbol capacity + while($this->bitBuffer->getLength() <= $MAX_BITS){ + $this->bitBuffer->putBit(false); + } + + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/QRDataModeAbstract.php b/src/vendor/chillerlan/php-qrcode/src/Data/QRDataModeAbstract.php new file mode 100644 index 00000000..94b93ac0 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/QRDataModeAbstract.php @@ -0,0 +1,61 @@ + + * @copyright 2020 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\Mode; + +/** + * abstract methods for the several data modes + */ +abstract class QRDataModeAbstract implements QRDataModeInterface{ + + /** + * The data to write + */ + protected string $data; + + /** + * QRDataModeAbstract constructor. + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function __construct(string $data){ + $data = $this::convertEncoding($data); + + if(!$this::validateString($data)){ + throw new QRCodeDataException('invalid data'); + } + + $this->data = $data; + } + + /** + * returns the character count of the $data string + */ + protected function getCharCount():int{ + return strlen($this->data); + } + + /** + * @inheritDoc + */ + public static function convertEncoding(string $string):string{ + return $string; + } + + /** + * shortcut + */ + protected static function getLengthBits(int $versionNumber):int{ + return Mode::getLengthBitsForVersion(static::DATAMODE, $versionNumber); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/QRDataModeInterface.php b/src/vendor/chillerlan/php-qrcode/src/Data/QRDataModeInterface.php new file mode 100644 index 00000000..321cf60b --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/QRDataModeInterface.php @@ -0,0 +1,63 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\BitBuffer; + +/** + * Specifies the methods reqired for the data modules (Number, Alphanum, Byte and Kanji) + */ +interface QRDataModeInterface{ + + /** + * the current data mode: Number, Alphanum, Kanji, Hanzi, Byte, ECI + * + * tbh I hate this constant here, but it's part of the interface, so I can't just declare it in the abstract class. + * (phan will complain about a PhanAccessOverridesFinalConstant) + * + * @see https://wiki.php.net/rfc/final_class_const + * + * @var int + * @see \chillerlan\QRCode\Common\Mode + * @internal do not call this constant from the interface, but rather from one of the child classes + */ + public const DATAMODE = -1; + + /** + * retruns the length in bits of the data string + */ + public function getLengthInBits():int; + + /** + * encoding conversion helper + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function convertEncoding(string $string):string; + + /** + * checks if the given string qualifies for the encoder module + */ + public static function validateString(string $string):bool; + + /** + * writes the actual data string to the BitBuffer, uses the given version to determine the length bits + * + * @see \chillerlan\QRCode\Data\QRData::writeBitBuffer() + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface; + + /** + * reads a segment from the BitBuffer and decodes in the current data mode + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string; + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php b/src/vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php new file mode 100644 index 00000000..d92c1a78 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php @@ -0,0 +1,812 @@ + + * @copyright 2017 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, EccLevel, MaskPattern, Version}; +use function array_fill, array_map, array_reverse, count, intdiv; + +/** + * Holds an array representation of the final QR Code that contains numerical values for later output modifications; + * maps the ECC coded binary data and applies the mask pattern + * + * @see http://www.thonky.com/qr-code-tutorial/format-version-information + */ +class QRMatrix{ + + /* + * special values + */ + + /** @var int */ + public const IS_DARK = 0b100000000000; + /** @var int */ + public const M_NULL = 0b000000000000; + /** @var int */ + public const M_LOGO = 0b001000000000; + /** @var int */ + public const M_LOGO_DARK = 0b101000000000; + + /* + * light values + */ + + /** @var int */ + public const M_DATA = 0b000000000010; + /** @var int */ + public const M_FINDER = 0b000000000100; + /** @var int */ + public const M_SEPARATOR = 0b000000001000; + /** @var int */ + public const M_ALIGNMENT = 0b000000010000; + /** @var int */ + public const M_TIMING = 0b000000100000; + /** @var int */ + public const M_FORMAT = 0b000001000000; + /** @var int */ + public const M_VERSION = 0b000010000000; + /** @var int */ + public const M_QUIETZONE = 0b000100000000; + + /* + * dark values + */ + + /** @var int */ + public const M_DARKMODULE = 0b100000000001; + /** @var int */ + public const M_DATA_DARK = 0b100000000010; + /** @var int */ + public const M_FINDER_DARK = 0b100000000100; + /** @var int */ + public const M_ALIGNMENT_DARK = 0b100000010000; + /** @var int */ + public const M_TIMING_DARK = 0b100000100000; + /** @var int */ + public const M_FORMAT_DARK = 0b100001000000; + /** @var int */ + public const M_VERSION_DARK = 0b100010000000; + /** @var int */ + public const M_FINDER_DOT = 0b110000000000; + + /* + * values used for reversed reflectance + */ + + /** @var int */ + public const M_DARKMODULE_LIGHT = 0b000000000001; + /** @var int */ + public const M_FINDER_DOT_LIGHT = 0b010000000000; + /** @var int */ + public const M_SEPARATOR_DARK = 0b100000001000; + /** @var int */ + public const M_QUIETZONE_DARK = 0b100100000000; + + /** + * Map of flag => coord + * + * @see \chillerlan\QRCode\Data\QRMatrix::checkNeighbours() + * + * @var array + */ + protected const neighbours = [ + 0b00000001 => [-1, -1], + 0b00000010 => [ 0, -1], + 0b00000100 => [ 1, -1], + 0b00001000 => [ 1, 0], + 0b00010000 => [ 1, 1], + 0b00100000 => [ 0, 1], + 0b01000000 => [-1, 1], + 0b10000000 => [-1, 0], + ]; + + /** + * the matrix version - always set in QRMatrix, may be null in BitMatrix + */ + protected ?Version $version = null; + + /** + * the current ECC level - always set in QRMatrix, may be null in BitMatrix + */ + protected ?EccLevel $eccLevel = null; + + /** + * the mask pattern that was used in the most recent operation, set via: + * + * - QRMatrix::setFormatInfo() + * - QRMatrix::mask() + * - BitMatrix::readFormatInformation() + */ + protected ?MaskPattern $maskPattern = null; + + /** + * the size (side length) of the matrix, including quiet zone (if created) + */ + protected int $moduleCount; + + /** + * the actual matrix data array + * + * @var int[][] + */ + protected array $matrix; + + /** + * QRMatrix constructor. + */ + public function __construct(Version $version, EccLevel $eccLevel){ + $this->version = $version; + $this->eccLevel = $eccLevel; + $this->moduleCount = $this->version->getDimension(); + $this->matrix = $this->createMatrix($this->moduleCount, $this::M_NULL); + } + + /** + * Creates a 2-dimensional array (square) of the given $size + */ + protected function createMatrix(int $size, int $value):array{ + return array_fill(0, $size, array_fill(0, $size, $value)); + } + + /** + * shortcut to initialize the functional patterns + */ + public function initFunctionalPatterns():self{ + return $this + ->setFinderPattern() + ->setSeparators() + ->setAlignmentPattern() + ->setTimingPattern() + ->setDarkModule() + ->setVersionNumber() + ->setFormatInfo() + ; + } + + /** + * Returns the data matrix, returns a pure boolean representation if $boolean is set to true + * + * @return int[][]|bool[][] + */ + public function getMatrix(bool $boolean = null):array{ + + if($boolean !== true){ + return $this->matrix; + } + + $matrix = $this->matrix; + + foreach($matrix as &$row){ + $row = array_map([$this, 'isDark'], $row); + } + + return $matrix; + } + + /** + * @deprecated 5.0.0 use QRMatrix::getMatrix() instead + * @see \chillerlan\QRCode\Data\QRMatrix::getMatrix() + * @codeCoverageIgnore + */ + public function matrix(bool $boolean = null):array{ + return $this->getMatrix($boolean); + } + + /** + * Returns the current version number + */ + public function getVersion():?Version{ + return $this->version; + } + + /** + * @deprecated 5.0.0 use QRMatrix::getVersion() instead + * @see \chillerlan\QRCode\Data\QRMatrix::getVersion() + * @codeCoverageIgnore + */ + public function version():?Version{ + return $this->getVersion(); + } + + /** + * Returns the current ECC level + */ + public function getEccLevel():?EccLevel{ + return $this->eccLevel; + } + + /** + * @deprecated 5.0.0 use QRMatrix::getEccLevel() instead + * @see \chillerlan\QRCode\Data\QRMatrix::getEccLevel() + * @codeCoverageIgnore + */ + public function eccLevel():?EccLevel{ + return $this->getEccLevel(); + } + + /** + * Returns the current mask pattern + */ + public function getMaskPattern():?MaskPattern{ + return $this->maskPattern; + } + + /** + * @deprecated 5.0.0 use QRMatrix::getMaskPattern() instead + * @see \chillerlan\QRCode\Data\QRMatrix::getMaskPattern() + * @codeCoverageIgnore + */ + public function maskPattern():?MaskPattern{ + return $this->getMaskPattern(); + } + + /** + * Returns the absoulute size of the matrix, including quiet zone (after setting it). + * + * size = version * 4 + 17 [ + 2 * quietzone size] + */ + public function getSize():int{ + return $this->moduleCount; + } + + /** + * @deprecated 5.0.0 use QRMatrix::getSize() instead + * @see \chillerlan\QRCode\Data\QRMatrix::getSize() + * @codeCoverageIgnore + */ + public function size():int{ + return $this->getSize(); + } + + /** + * Returns the value of the module at position [$x, $y] or -1 if the coordinate is outside the matrix + */ + public function get(int $x, int $y):int{ + + if(!isset($this->matrix[$y][$x])){ + return -1; + } + + return $this->matrix[$y][$x]; + } + + /** + * Sets the $M_TYPE value for the module at position [$x, $y] + * + * true => $M_TYPE | 0x800 + * false => $M_TYPE + */ + public function set(int $x, int $y, bool $value, int $M_TYPE):self{ + + if(isset($this->matrix[$y][$x])){ + // we don't know whether the input is dark, so we remove the dark bit + $M_TYPE &= ~$this::IS_DARK; + + if($value === true){ + $M_TYPE |= $this::IS_DARK; + } + + $this->matrix[$y][$x] = $M_TYPE; + } + + return $this; + } + + /** + * Fills an area of $width * $height, from the given starting point [$startX, $startY] (top left) with $value for $M_TYPE. + */ + public function setArea(int $startX, int $startY, int $width, int $height, bool $value, int $M_TYPE):self{ + + for($y = $startY; $y < ($startY + $height); $y++){ + for($x = $startX; $x < ($startX + $width); $x++){ + $this->set($x, $y, $value, $M_TYPE); + } + } + + return $this; + } + + /** + * Flips the value of the module at ($x, $y) + */ + public function flip(int $x, int $y):self{ + + if(isset($this->matrix[$y][$x])){ + $this->matrix[$y][$x] ^= $this::IS_DARK; + } + + return $this; + } + + /** + * Checks whether the module at ($x, $y) is of the given $M_TYPE + * + * true => $value & $M_TYPE === $M_TYPE + * + * Also, returns false if the given coordinates are out of range. + */ + public function checkType(int $x, int $y, int $M_TYPE):bool{ + + if(isset($this->matrix[$y][$x])){ + return ($this->matrix[$y][$x] & $M_TYPE) === $M_TYPE; + } + + return false; + } + + /** + * Checks whether the module at ($x, $y) is in the given array of $M_TYPES, + * returns true if a match is found, otherwise false. + */ + public function checkTypeIn(int $x, int $y, array $M_TYPES):bool{ + + foreach($M_TYPES as $type){ + if($this->checkType($x, $y, $type)){ + return true; + } + } + + return false; + } + + /** + * Checks whether the module at ($x, $y) is true (dark) or false (light) + * + * Also, returns false if the given coordinates are out of range. + */ + public function check(int $x, int $y):bool{ + + if(isset($this->matrix[$y][$x])){ + return $this->isDark($this->matrix[$y][$x]); + } + + return false; + } + + /** + * Checks whether the given $M_TYPE is a dark value + */ + public function isDark(int $M_TYPE):bool{ + return ($M_TYPE & $this::IS_DARK) === $this::IS_DARK; + } + + /** + * Checks the status of the neighbouring modules for the module at ($x, $y) and returns a bitmask with the results. + * + * The 8 flags of the bitmask represent the status of each of the neighbouring fields, + * starting with the lowest bit for top left, going clockwise: + * + * 0 1 2 + * 7 # 3 + * 6 5 4 + */ + public function checkNeighbours(int $x, int $y, int $M_TYPE = null):int{ + $bits = 0; + + foreach($this::neighbours as $bit => [$ix, $iy]){ + $ix += $x; + $iy += $y; + + // $M_TYPE is given, skip if the field is not the same type + if($M_TYPE !== null && !$this->checkType($ix, $iy, $M_TYPE)){ + continue; + } + + if($this->checkType($ix, $iy, $this::IS_DARK)){ + $bits |= $bit; + } + } + + return $bits; + } + + /** + * Sets the "dark module", that is always on the same position 1x1px away from the bottom left finder + * + * 4 * version + 9 or moduleCount - 8 + */ + public function setDarkModule():self{ + $this->set(8, ($this->moduleCount - 8), true, $this::M_DARKMODULE); + + return $this; + } + + /** + * Draws the 7x7 finder patterns in the corners top left/right and bottom left + * + * ISO/IEC 18004:2000 Section 7.3.2 + */ + public function setFinderPattern():self{ + + $pos = [ + [0, 0], // top left + [($this->moduleCount - 7), 0], // top right + [0, ($this->moduleCount - 7)], // bottom left + ]; + + foreach($pos as $c){ + $this + ->setArea( $c[0] , $c[1] , 7, 7, true, $this::M_FINDER) + ->setArea(($c[0] + 1), ($c[1] + 1), 5, 5, false, $this::M_FINDER) + ->setArea(($c[0] + 2), ($c[1] + 2), 3, 3, true, $this::M_FINDER_DOT) + ; + } + + return $this; + } + + /** + * Draws the separator lines around the finder patterns + * + * ISO/IEC 18004:2000 Section 7.3.3 + */ + public function setSeparators():self{ + + $h = [ + [7, 0], + [($this->moduleCount - 8), 0], + [7, ($this->moduleCount - 8)], + ]; + + $v = [ + [7, 7], + [($this->moduleCount - 1), 7], + [7, ($this->moduleCount - 8)], + ]; + + for($c = 0; $c < 3; $c++){ + for($i = 0; $i < 8; $i++){ + $this->set( $h[$c][0] , ($h[$c][1] + $i), false, $this::M_SEPARATOR); + $this->set(($v[$c][0] - $i), $v[$c][1] , false, $this::M_SEPARATOR); + } + } + + return $this; + } + + + /** + * Draws the 5x5 alignment patterns + * + * ISO/IEC 18004:2000 Section 7.3.5 + */ + public function setAlignmentPattern():self{ + $alignmentPattern = $this->version->getAlignmentPattern(); + + foreach($alignmentPattern as $y){ + foreach($alignmentPattern as $x){ + + // skip existing patterns + if($this->matrix[$y][$x] !== $this::M_NULL){ + continue; + } + + $this + ->setArea(($x - 2), ($y - 2), 5, 5, true, $this::M_ALIGNMENT) + ->setArea(($x - 1), ($y - 1), 3, 3, false, $this::M_ALIGNMENT) + ->set($x, $y, true, $this::M_ALIGNMENT) + ; + + } + } + + return $this; + } + + + /** + * Draws the timing pattern (h/v checkered line between the finder patterns) + * + * ISO/IEC 18004:2000 Section 7.3.4 + */ + public function setTimingPattern():self{ + + for($i = 8; $i < ($this->moduleCount - 8); $i++){ + + if($this->matrix[6][$i] !== $this::M_NULL || $this->matrix[$i][6] !== $this::M_NULL){ + continue; + } + + $v = ($i % 2) === 0; + + $this->set($i, 6, $v, $this::M_TIMING); // h + $this->set(6, $i, $v, $this::M_TIMING); // v + } + + return $this; + } + + /** + * Draws the version information, 2x 3x6 pixel + * + * ISO/IEC 18004:2000 Section 8.10 + */ + public function setVersionNumber():self{ + $bits = $this->version->getVersionPattern(); + + if($bits !== null){ + + for($i = 0; $i < 18; $i++){ + $a = intdiv($i, 3); + $b = (($i % 3) + ($this->moduleCount - 8 - 3)); + $v = (($bits >> $i) & 1) === 1; + + $this->set($b, $a, $v, $this::M_VERSION); // ne + $this->set($a, $b, $v, $this::M_VERSION); // sw + } + + } + + return $this; + } + + /** + * Draws the format info along the finder patterns. If no $maskPattern, all format info modules will be set to false. + * + * ISO/IEC 18004:2000 Section 8.9 + */ + public function setFormatInfo(MaskPattern $maskPattern = null):self{ + $this->maskPattern = $maskPattern; + $bits = 0; // sets all format fields to false (test mode) + + if($this->maskPattern instanceof MaskPattern){ + $bits = $this->eccLevel->getformatPattern($this->maskPattern); + } + + for($i = 0; $i < 15; $i++){ + $v = (($bits >> $i) & 1) === 1; + + if($i < 6){ + $this->set(8, $i, $v, $this::M_FORMAT); + } + elseif($i < 8){ + $this->set(8, ($i + 1), $v, $this::M_FORMAT); + } + else{ + $this->set(8, ($this->moduleCount - 15 + $i), $v, $this::M_FORMAT); + } + + if($i < 8){ + $this->set(($this->moduleCount - $i - 1), 8, $v, $this::M_FORMAT); + } + elseif($i < 9){ + $this->set(((15 - $i)), 8, $v, $this::M_FORMAT); + } + else{ + $this->set((15 - $i - 1), 8, $v, $this::M_FORMAT); + } + + } + + return $this; + } + + /** + * Draws the "quiet zone" of $size around the matrix + * + * ISO/IEC 18004:2000 Section 7.3.7 + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function setQuietZone(int $quietZoneSize):self{ + + // early exit if there's nothing to add + if($quietZoneSize < 1){ + return $this; + } + + if($this->matrix[($this->moduleCount - 1)][($this->moduleCount - 1)] === $this::M_NULL){ + throw new QRCodeDataException('use only after writing data'); + } + + // create a matrix with the new size + $newSize = ($this->moduleCount + ($quietZoneSize * 2)); + $newMatrix = $this->createMatrix($newSize, $this::M_QUIETZONE); + + // copy over the current matrix + foreach($this->matrix as $y => $row){ + foreach($row as $x => $val){ + $newMatrix[($y + $quietZoneSize)][($x + $quietZoneSize)] = $val; + } + } + + // set the new values + $this->moduleCount = $newSize; + $this->matrix = $newMatrix; + + return $this; + } + + /** + * Rotates the matrix by 90 degrees clock wise + */ + public function rotate90():self{ + /** @phan-suppress-next-line PhanParamTooFewInternalUnpack */ + $this->matrix = array_map((fn(int ...$a):array => array_reverse($a)), ...$this->matrix); + + return $this; + } + + /** + * Inverts the values of the whole matrix + * + * ISO/IEC 18004:2015 Section 6.2 - Reflectance reversal + */ + public function invert():self{ + + foreach($this->matrix as $y => $row){ + foreach($row as $x => $val){ + + // skip null fields + if($val === $this::M_NULL){ + continue; + } + + $this->flip($x, $y); + } + } + + return $this; + } + + /** + * Clears a space of $width * $height in order to add a logo or text. + * If no $height is given, the space will be assumed a square of $width. + * + * Additionally, the logo space can be positioned within the QR Code using $startX and $startY. + * If either of these are null, the logo space will be centered in that direction. + * ECC level "H" (30%) is required. + * + * The coordinates of $startX and $startY do not include the quiet zone: + * [0, 0] is always the top left module of the top left finder pattern, negative values go into the quiet zone top and left. + * + * Please note that adding a logo space minimizes the error correction capacity of the QR Code and + * created images may become unreadable, especially when printed with a chance to receive damage. + * Please test thoroughly before using this feature in production. + * + * This method should be called from within an output module (after the matrix has been filled with data). + * Note that there is no restiction on how many times this method could be called on the same matrix instance. + * + * @link https://github.com/chillerlan/php-qrcode/issues/52 + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function setLogoSpace(int $width, int $height = null, int $startX = null, int $startY = null):self{ + $height ??= $width; + + // if width and height happen to be negative or 0 (default value), just return - nothing to do + if($width <= 0 || $height <= 0){ + return $this; // @codeCoverageIgnore + } + + // for logos, we operate in ECC H (30%) only + if($this->eccLevel->getLevel() !== EccLevel::H){ + throw new QRCodeDataException('ECC level "H" required to add logo space'); + } + + // $this->moduleCount includes the quiet zone (if created), we need the QR size here + $dimension = $this->version->getDimension(); + + // throw if the size exceeds the qrcode size + if($width > $dimension || $height > $dimension){ + throw new QRCodeDataException('logo dimensions exceed matrix size'); + } + + // we need uneven sizes to center the logo space, adjust if needed + if($startX === null && ($width % 2) === 0){ + $width++; + } + + if($startY === null && ($height % 2) === 0){ + $height++; + } + + // throw if the logo space exceeds the maximum error correction capacity + if(($width * $height) > (int)($dimension * $dimension * 0.25)){ + throw new QRCodeDataException('logo space exceeds the maximum error correction capacity'); + } + + $quietzone = (($this->moduleCount - $dimension) / 2); + $end = ($this->moduleCount - $quietzone); + + // determine start coordinates + $startX ??= (($dimension - $width) / 2); + $startY ??= (($dimension - $height) / 2); + $endX = ($quietzone + $startX + $width); + $endY = ($quietzone + $startY + $height); + + // clear the space + for($y = ($quietzone + $startY); $y < $endY; $y++){ + for($x = ($quietzone + $startX); $x < $endX; $x++){ + // out of bounds, skip + if($x < $quietzone || $y < $quietzone ||$x >= $end || $y >= $end){ + continue; + } + + $this->set($x, $y, false, $this::M_LOGO); + } + } + + return $this; + } + + /** + * Maps the interleaved binary $data on the matrix + */ + public function writeCodewords(BitBuffer $bitBuffer):self{ + $data = (new ReedSolomonEncoder($this->version, $this->eccLevel))->interleaveEcBytes($bitBuffer); + $byteCount = count($data); + $iByte = 0; + $iBit = 7; + $direction = true; + + for($i = ($this->moduleCount - 1); $i > 0; $i -= 2){ + + // skip vertical alignment pattern + if($i === 6){ + $i--; + } + + for($count = 0; $count < $this->moduleCount; $count++){ + $y = $count; + + if($direction){ + $y = ($this->moduleCount - 1 - $count); + } + + for($col = 0; $col < 2; $col++){ + $x = ($i - $col); + + // skip functional patterns + if($this->matrix[$y][$x] !== $this::M_NULL){ + continue; + } + + $this->matrix[$y][$x] = $this::M_DATA; + + if($iByte < $byteCount && (($data[$iByte] >> $iBit--) & 1) === 1){ + $this->matrix[$y][$x] |= $this::IS_DARK; + } + + if($iBit === -1){ + $iByte++; + $iBit = 7; + } + } + } + + $direction = !$direction; // switch directions + } + + return $this; + } + + /** + * Applies/reverses the mask pattern + * + * ISO/IEC 18004:2000 Section 8.8.1 + */ + public function mask(MaskPattern $maskPattern):self{ + $this->maskPattern = $maskPattern; + $mask = $this->maskPattern->getMask(); + + foreach($this->matrix as $y => $row){ + foreach($row as $x => $val){ + // skip non-data modules + if(($val & $this::M_DATA) === $this::M_DATA && $mask($x, $y)){ + $this->flip($x, $y); + } + } + } + + return $this; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/ReedSolomonEncoder.php b/src/vendor/chillerlan/php-qrcode/src/Data/ReedSolomonEncoder.php new file mode 100644 index 00000000..60444378 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/ReedSolomonEncoder.php @@ -0,0 +1,127 @@ + + * @copyright 2021 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, EccLevel, GenericGFPoly, GF256, Version}; +use function array_fill, array_merge, count, max; + +/** + * Reed-Solomon encoding - ISO/IEC 18004:2000 Section 8.5 ff + * + * @see http://www.thonky.com/qr-code-tutorial/error-correction-coding + */ +final class ReedSolomonEncoder{ + + private Version $version; + private EccLevel $eccLevel; + + private array $interleavedData; + private int $interleavedDataIndex; + + /** + * ReedSolomonDecoder constructor + */ + public function __construct(Version $version, EccLevel $eccLevel){ + $this->version = $version; + $this->eccLevel = $eccLevel; + } + + /** + * ECC encoding and interleaving + * + * @throws \chillerlan\QRCode\QRCodeException + */ + public function interleaveEcBytes(BitBuffer $bitBuffer):array{ + [$numEccCodewords, [[$l1, $b1], [$l2, $b2]]] = $this->version->getRSBlocks($this->eccLevel); + + $rsBlocks = array_fill(0, $l1, [($numEccCodewords + $b1), $b1]); + + if($l2 > 0){ + $rsBlocks = array_merge($rsBlocks, array_fill(0, $l2, [($numEccCodewords + $b2), $b2])); + } + + $bitBufferData = $bitBuffer->getBuffer(); + $dataBytes = []; + $ecBytes = []; + $maxDataBytes = 0; + $maxEcBytes = 0; + $dataByteOffset = 0; + + foreach($rsBlocks as $key => [$rsBlockTotal, $dataByteCount]){ + $dataBytes[$key] = []; + + for($i = 0; $i < $dataByteCount; $i++){ + $dataBytes[$key][$i] = ($bitBufferData[($i + $dataByteOffset)] & 0xff); + } + + $ecByteCount = ($rsBlockTotal - $dataByteCount); + $ecBytes[$key] = $this->encode($dataBytes[$key], $ecByteCount); + $maxDataBytes = max($maxDataBytes, $dataByteCount); + $maxEcBytes = max($maxEcBytes, $ecByteCount); + $dataByteOffset += $dataByteCount; + } + + $this->interleavedData = array_fill(0, $this->version->getTotalCodewords(), 0); + $this->interleavedDataIndex = 0; + $numRsBlocks = ($l1 + $l2); + + $this->interleave($dataBytes, $maxDataBytes, $numRsBlocks); + $this->interleave($ecBytes, $maxEcBytes, $numRsBlocks); + + return $this->interleavedData; + } + + /** + * + */ + private function encode(array $dataBytes, int $ecByteCount):array{ + $rsPoly = new GenericGFPoly([1]); + + for($i = 0; $i < $ecByteCount; $i++){ + $rsPoly = $rsPoly->multiply(new GenericGFPoly([1, GF256::exp($i)])); + } + + $rsPolyDegree = $rsPoly->getDegree(); + + $modCoefficients = (new GenericGFPoly($dataBytes, $rsPolyDegree)) + ->mod($rsPoly) + ->getCoefficients() + ; + + $ecBytes = array_fill(0, $rsPolyDegree, 0); + $count = (count($modCoefficients) - $rsPolyDegree); + + foreach($ecBytes as $i => &$val){ + $modIndex = ($i + $count); + $val = 0; + + if($modIndex >= 0){ + $val = $modCoefficients[$modIndex]; + } + } + + return $ecBytes; + } + + /** + * + */ + private function interleave(array $byteArray, int $maxBytes, int $numRsBlocks):void{ + for($x = 0; $x < $maxBytes; $x++){ + for($y = 0; $y < $numRsBlocks; $y++){ + if($x < count($byteArray[$y])){ + $this->interleavedData[$this->interleavedDataIndex++] = $byteArray[$y][$x]; + } + } + } + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Decoder/Binarizer.php b/src/vendor/chillerlan/php-qrcode/src/Decoder/Binarizer.php new file mode 100644 index 00000000..7b7b49f6 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Decoder/Binarizer.php @@ -0,0 +1,361 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Decoder; + +use chillerlan\QRCode\Common\LuminanceSourceInterface; +use chillerlan\QRCode\Data\QRMatrix; +use function array_fill, count, intdiv, max; + +/** + * This class implements a local thresholding algorithm, which while slower than the + * GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for + * high frequency images of barcodes with black data on white backgrounds. For this application, + * it does a much better job than a global blackpoint with severe shadows and gradients. + * However, it tends to produce artifacts on lower frequency images and is therefore not + * a good general purpose binarizer for uses outside ZXing. + * + * This class extends GlobalHistogramBinarizer, using the older histogram approach for 1D readers, + * and the newer local approach for 2D readers. 1D decoding using a per-row histogram is already + * inherently local, and only fails for horizontal gradients. We can revisit that problem later, + * but for now it was not a win to use local blocks for 1D. + * + * This Binarizer is the default for the unit tests and the recommended class for library users. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +final class Binarizer{ + + // This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels. + // So this is the smallest dimension in each axis we can accept. + private const BLOCK_SIZE_POWER = 3; + private const BLOCK_SIZE = 8; // ...0100...00 + private const BLOCK_SIZE_MASK = 7; // ...0011...11 + private const MINIMUM_DIMENSION = 40; + private const MIN_DYNAMIC_RANGE = 24; + +# private const LUMINANCE_BITS = 5; + private const LUMINANCE_SHIFT = 3; + private const LUMINANCE_BUCKETS = 32; + + private LuminanceSourceInterface $source; + private array $luminances; + + /** + * + */ + public function __construct(LuminanceSourceInterface $source){ + $this->source = $source; + $this->luminances = $this->source->getLuminances(); + } + + /** + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + private function estimateBlackPoint(array $buckets):int{ + // Find the tallest peak in the histogram. + $numBuckets = count($buckets); + $maxBucketCount = 0; + $firstPeak = 0; + $firstPeakSize = 0; + + for($x = 0; $x < $numBuckets; $x++){ + + if($buckets[$x] > $firstPeakSize){ + $firstPeak = $x; + $firstPeakSize = $buckets[$x]; + } + + if($buckets[$x] > $maxBucketCount){ + $maxBucketCount = $buckets[$x]; + } + } + + // Find the second-tallest peak which is somewhat far from the tallest peak. + $secondPeak = 0; + $secondPeakScore = 0; + + for($x = 0; $x < $numBuckets; $x++){ + $distanceToBiggest = ($x - $firstPeak); + // Encourage more distant second peaks by multiplying by square of distance. + $score = ($buckets[$x] * $distanceToBiggest * $distanceToBiggest); + + if($score > $secondPeakScore){ + $secondPeak = $x; + $secondPeakScore = $score; + } + } + + // Make sure firstPeak corresponds to the black peak. + if($firstPeak > $secondPeak){ + $temp = $firstPeak; + $firstPeak = $secondPeak; + $secondPeak = $temp; + } + + // If there is too little contrast in the image to pick a meaningful black point, throw rather + // than waste time trying to decode the image, and risk false positives. + if(($secondPeak - $firstPeak) <= ($numBuckets / 16)){ + throw new QRCodeDecoderException('no meaningful dark point found'); // @codeCoverageIgnore + } + + // Find a valley between them that is low and closer to the white peak. + $bestValley = ($secondPeak - 1); + $bestValleyScore = -1; + + for($x = ($secondPeak - 1); $x > $firstPeak; $x--){ + $fromFirst = ($x - $firstPeak); + $score = ($fromFirst * $fromFirst * ($secondPeak - $x) * ($maxBucketCount - $buckets[$x])); + + if($score > $bestValleyScore){ + $bestValley = $x; + $bestValleyScore = $score; + } + } + + return ($bestValley << self::LUMINANCE_SHIFT); + } + + /** + * Calculates the final BitMatrix once for all requests. This could be called once from the + * constructor instead, but there are some advantages to doing it lazily, such as making + * profiling easier, and not doing heavy lifting when callers don't expect it. + * + * Converts a 2D array of luminance data to 1 bit data. As above, assume this method is expensive + * and do not call it repeatedly. This method is intended for decoding 2D barcodes and may or + * may not apply sharpening. Therefore, a row from this matrix may not be identical to one + * fetched using getBlackRow(), so don't mix and match between them. + * + * @return \chillerlan\QRCode\Decoder\BitMatrix The 2D array of bits for the image (true means black). + */ + public function getBlackMatrix():BitMatrix{ + $width = $this->source->getWidth(); + $height = $this->source->getHeight(); + + if($width >= self::MINIMUM_DIMENSION && $height >= self::MINIMUM_DIMENSION){ + $subWidth = ($width >> self::BLOCK_SIZE_POWER); + + if(($width & self::BLOCK_SIZE_MASK) !== 0){ + $subWidth++; + } + + $subHeight = ($height >> self::BLOCK_SIZE_POWER); + + if(($height & self::BLOCK_SIZE_MASK) !== 0){ + $subHeight++; + } + + return $this->calculateThresholdForBlock($subWidth, $subHeight, $width, $height); + } + + // If the image is too small, fall back to the global histogram approach. + return $this->getHistogramBlackMatrix($width, $height); + } + + /** + * + */ + private function getHistogramBlackMatrix(int $width, int $height):BitMatrix{ + + // Quickly calculates the histogram by sampling four rows from the image. This proved to be + // more robust on the blackbox tests than sampling a diagonal as we used to do. + $buckets = array_fill(0, self::LUMINANCE_BUCKETS, 0); + $right = intdiv(($width * 4), 5); + $x = intdiv($width, 5); + + for($y = 1; $y < 5; $y++){ + $row = intdiv(($height * $y), 5); + $localLuminances = $this->source->getRow($row); + + for(; $x < $right; $x++){ + $pixel = ($localLuminances[$x] & 0xff); + $buckets[($pixel >> self::LUMINANCE_SHIFT)]++; + } + } + + $blackPoint = $this->estimateBlackPoint($buckets); + + // We delay reading the entire image luminance until the black point estimation succeeds. + // Although we end up reading four rows twice, it is consistent with our motto of + // "fail quickly" which is necessary for continuous scanning. + $matrix = new BitMatrix(max($width, $height)); + + for($y = 0; $y < $height; $y++){ + $offset = ($y * $width); + + for($x = 0; $x < $width; $x++){ + $matrix->set($x, $y, (($this->luminances[($offset + $x)] & 0xff) < $blackPoint), QRMatrix::M_DATA); + } + } + + return $matrix; + } + + /** + * Calculates a single black point for each block of pixels and saves it away. + * See the following thread for a discussion of this algorithm: + * + * @see http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0 + */ + private function calculateBlackPoints(int $subWidth, int $subHeight, int $width, int $height):array{ + $blackPoints = array_fill(0, $subHeight, array_fill(0, $subWidth, 0)); + + for($y = 0; $y < $subHeight; $y++){ + $yoffset = ($y << self::BLOCK_SIZE_POWER); + $maxYOffset = ($height - self::BLOCK_SIZE); + + if($yoffset > $maxYOffset){ + $yoffset = $maxYOffset; + } + + for($x = 0; $x < $subWidth; $x++){ + $xoffset = ($x << self::BLOCK_SIZE_POWER); + $maxXOffset = ($width - self::BLOCK_SIZE); + + if($xoffset > $maxXOffset){ + $xoffset = $maxXOffset; + } + + $sum = 0; + $min = 255; + $max = 0; + + for($yy = 0, $offset = ($yoffset * $width + $xoffset); $yy < self::BLOCK_SIZE; $yy++, $offset += $width){ + + for($xx = 0; $xx < self::BLOCK_SIZE; $xx++){ + $pixel = ((int)($this->luminances[(int)($offset + $xx)]) & 0xff); + $sum += $pixel; + // still looking for good contrast + if($pixel < $min){ + $min = $pixel; + } + + if($pixel > $max){ + $max = $pixel; + } + } + + // short-circuit min/max tests once dynamic range is met + if(($max - $min) > self::MIN_DYNAMIC_RANGE){ + // finish the rest of the rows quickly + for($yy++, $offset += $width; $yy < self::BLOCK_SIZE; $yy++, $offset += $width){ + for($xx = 0; $xx < self::BLOCK_SIZE; $xx++){ + $sum += ((int)($this->luminances[(int)($offset + $xx)]) & 0xff); + } + } + } + } + + // The default estimate is the average of the values in the block. + $average = ($sum >> (self::BLOCK_SIZE_POWER * 2)); + + if(($max - $min) <= self::MIN_DYNAMIC_RANGE){ + // If variation within the block is low, assume this is a block with only light or only + // dark pixels. In that case we do not want to use the average, as it would divide this + // low contrast area into black and white pixels, essentially creating data out of noise. + // + // The default assumption is that the block is light/background. Since no estimate for + // the level of dark pixels exists locally, use half the min for the block. + $average = ($min / 2); + + if($y > 0 && $x > 0){ + // Correct the "white background" assumption for blocks that have neighbors by comparing + // the pixels in this block to the previously calculated black points. This is based on + // the fact that dark barcode symbology is always surrounded by some amount of light + // background for which reasonable black point estimates were made. The bp estimated at + // the boundaries is used for the interior. + + // The (min < bp) is arbitrary but works better than other heuristics that were tried. + $averageNeighborBlackPoint = ( + ($blackPoints[($y - 1)][$x] + (2 * $blackPoints[$y][($x - 1)]) + $blackPoints[($y - 1)][($x - 1)]) / 4 + ); + + if($min < $averageNeighborBlackPoint){ + $average = $averageNeighborBlackPoint; + } + } + } + + $blackPoints[$y][$x] = $average; + } + } + + return $blackPoints; + } + + /** + * For each block in the image, calculate the average black point using a 5x5 grid + * of the surrounding blocks. Also handles the corner cases (fractional blocks are computed based + * on the last pixels in the row/column which are also used in the previous block). + */ + private function calculateThresholdForBlock(int $subWidth, int $subHeight, int $width, int $height):BitMatrix{ + $matrix = new BitMatrix(max($width, $height)); + $blackPoints = $this->calculateBlackPoints($subWidth, $subHeight, $width, $height); + + for($y = 0; $y < $subHeight; $y++){ + $yoffset = ($y << self::BLOCK_SIZE_POWER); + $maxYOffset = ($height - self::BLOCK_SIZE); + + if($yoffset > $maxYOffset){ + $yoffset = $maxYOffset; + } + + for($x = 0; $x < $subWidth; $x++){ + $xoffset = ($x << self::BLOCK_SIZE_POWER); + $maxXOffset = ($width - self::BLOCK_SIZE); + + if($xoffset > $maxXOffset){ + $xoffset = $maxXOffset; + } + + $left = $this->cap($x, 2, ($subWidth - 3)); + $top = $this->cap($y, 2, ($subHeight - 3)); + $sum = 0; + + for($z = -2; $z <= 2; $z++){ + $br = $blackPoints[($top + $z)]; + $sum += ($br[($left - 2)] + $br[($left - 1)] + $br[$left] + $br[($left + 1)] + $br[($left + 2)]); + } + + $average = (int)($sum / 25); + + // Applies a single threshold to a block of pixels. + for($j = 0, $o = ($yoffset * $width + $xoffset); $j < self::BLOCK_SIZE; $j++, $o += $width){ + for($i = 0; $i < self::BLOCK_SIZE; $i++){ + // Comparison needs to be <= so that black == 0 pixels are black even if the threshold is 0. + $v = (((int)($this->luminances[($o + $i)]) & 0xff) <= $average); + + $matrix->set(($xoffset + $i), ($yoffset + $j), $v, QRMatrix::M_DATA); + } + } + } + } + + return $matrix; + } + + /** + * @noinspection PhpSameParameterValueInspection + */ + private function cap(int $value, int $min, int $max):int{ + + if($value < $min){ + return $min; + } + + if($value > $max){ + return $max; + } + + return $value; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Decoder/BitMatrix.php b/src/vendor/chillerlan/php-qrcode/src/Decoder/BitMatrix.php new file mode 100644 index 00000000..ea2e321b --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Decoder/BitMatrix.php @@ -0,0 +1,430 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Decoder; + +use chillerlan\QRCode\Common\{EccLevel, MaskPattern, Version}; +use chillerlan\QRCode\Data\{QRCodeDataException, QRMatrix}; +use function array_fill, array_reverse, count; +use const PHP_INT_MAX, PHP_INT_SIZE; + +/** + * Extended QRMatrix to map read data from the Binarizer + */ +final class BitMatrix extends QRMatrix{ + + /** + * See ISO 18004:2006, Annex C, Table C.1 + * + * [data bits, sequence after masking] + */ + private const DECODE_LOOKUP = [ + 0x5412, // 0101010000010010 + 0x5125, // 0101000100100101 + 0x5E7C, // 0101111001111100 + 0x5B4B, // 0101101101001011 + 0x45F9, // 0100010111111001 + 0x40CE, // 0100000011001110 + 0x4F97, // 0100111110010111 + 0x4AA0, // 0100101010100000 + 0x77C4, // 0111011111000100 + 0x72F3, // 0111001011110011 + 0x7DAA, // 0111110110101010 + 0x789D, // 0111100010011101 + 0x662F, // 0110011000101111 + 0x6318, // 0110001100011000 + 0x6C41, // 0110110001000001 + 0x6976, // 0110100101110110 + 0x1689, // 0001011010001001 + 0x13BE, // 0001001110111110 + 0x1CE7, // 0001110011100111 + 0x19D0, // 0001100111010000 + 0x0762, // 0000011101100010 + 0x0255, // 0000001001010101 + 0x0D0C, // 0000110100001100 + 0x083B, // 0000100000111011 + 0x355F, // 0011010101011111 + 0x3068, // 0011000001101000 + 0x3F31, // 0011111100110001 + 0x3A06, // 0011101000000110 + 0x24B4, // 0010010010110100 + 0x2183, // 0010000110000011 + 0x2EDA, // 0010111011011010 + 0x2BED, // 0010101111101101 + ]; + + private const FORMAT_INFO_MASK_QR = 0x5412; // 0101010000010010 + + /** + * This flag has effect only on the copyVersionBit() method. + * Before proceeding with readCodewords() the resetInfo() method should be called. + */ + private bool $mirror = false; + + /** + * @noinspection PhpMissingParentConstructorInspection + */ + public function __construct(int $dimension){ + $this->moduleCount = $dimension; + $this->matrix = array_fill(0, $this->moduleCount, array_fill(0, $this->moduleCount, $this::M_NULL)); + } + + /** + * Resets the current version info in order to attempt another reading + */ + public function resetVersionInfo():self{ + $this->version = null; + $this->eccLevel = null; + $this->maskPattern = null; + + return $this; + } + + /** + * Mirror the bit matrix diagonally in order to attempt a second reading. + */ + public function mirrorDiagonal():self{ + $this->mirror = !$this->mirror; + + // mirror vertically + $this->matrix = array_reverse($this->matrix); + // rotate by 90 degrees clockwise + /** @phan-suppress-next-line PhanTypeMismatchReturnSuperType */ + return $this->rotate90(); + } + + /** + * Reads the bits in the BitMatrix representing the finder pattern in the + * correct order in order to reconstruct the codewords bytes contained within the + * QR Code. Throws if the exact number of bytes expected is not read. + * + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + public function readCodewords():array{ + + $this + ->readFormatInformation() + ->readVersion() + ->mask($this->maskPattern) // reverse the mask pattern + ; + + // invoke a fresh matrix with only the function & format patterns to compare against + $matrix = (new QRMatrix($this->version, $this->eccLevel)) + ->initFunctionalPatterns() + ->setFormatInfo($this->maskPattern) + ; + + $result = []; + $byte = 0; + $bitsRead = 0; + $direction = true; + + // Read columns in pairs, from right to left + for($i = ($this->moduleCount - 1); $i > 0; $i -= 2){ + + // Skip whole column with vertical alignment pattern; + // saves time and makes the other code proceed more cleanly + if($i === 6){ + $i--; + } + // Read alternatingly from bottom to top then top to bottom + for($count = 0; $count < $this->moduleCount; $count++){ + $y = ($direction) ? ($this->moduleCount - 1 - $count) : $count; + + for($col = 0; $col < 2; $col++){ + $x = ($i - $col); + + // Ignore bits covered by the function pattern + if($matrix->get($x, $y) !== $this::M_NULL){ + continue; + } + + $bitsRead++; + $byte <<= 1; + + if($this->check($x, $y)){ + $byte |= 1; + } + // If we've made a whole byte, save it off + if($bitsRead === 8){ + $result[] = $byte; + $bitsRead = 0; + $byte = 0; + } + } + } + + $direction = !$direction; // switch directions + } + + if(count($result) !== $this->version->getTotalCodewords()){ + throw new QRCodeDecoderException('result count differs from total codewords for version'); + } + + // bytes encoded within the QR Code + return $result; + } + + /** + * Reads format information from one of its two locations within the QR Code. + * Throws if both format information locations cannot be parsed as the valid encoding of format information. + * + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + private function readFormatInformation():self{ + + if($this->eccLevel !== null && $this->maskPattern !== null){ + return $this; + } + + // Read top-left format info bits + $formatInfoBits1 = 0; + + for($i = 0; $i < 6; $i++){ + $formatInfoBits1 = $this->copyVersionBit($i, 8, $formatInfoBits1); + } + + // ... and skip a bit in the timing pattern ... + $formatInfoBits1 = $this->copyVersionBit(7, 8, $formatInfoBits1); + $formatInfoBits1 = $this->copyVersionBit(8, 8, $formatInfoBits1); + $formatInfoBits1 = $this->copyVersionBit(8, 7, $formatInfoBits1); + // ... and skip a bit in the timing pattern ... + for($j = 5; $j >= 0; $j--){ + $formatInfoBits1 = $this->copyVersionBit(8, $j, $formatInfoBits1); + } + + // Read the top-right/bottom-left pattern too + $formatInfoBits2 = 0; + $jMin = ($this->moduleCount - 7); + + for($j = ($this->moduleCount - 1); $j >= $jMin; $j--){ + $formatInfoBits2 = $this->copyVersionBit(8, $j, $formatInfoBits2); + } + + for($i = ($this->moduleCount - 8); $i < $this->moduleCount; $i++){ + $formatInfoBits2 = $this->copyVersionBit($i, 8, $formatInfoBits2); + } + + $formatInfo = $this->doDecodeFormatInformation($formatInfoBits1, $formatInfoBits2); + + if($formatInfo === null){ + + // Should return null, but, some QR codes apparently do not mask this info. + // Try again by actually masking the pattern first. + $formatInfo = $this->doDecodeFormatInformation( + ($formatInfoBits1 ^ $this::FORMAT_INFO_MASK_QR), + ($formatInfoBits2 ^ $this::FORMAT_INFO_MASK_QR) + ); + + // still nothing??? + if($formatInfo === null){ + throw new QRCodeDecoderException('failed to read format info'); // @codeCoverageIgnore + } + + } + + $this->eccLevel = new EccLevel(($formatInfo >> 3) & 0x03); // Bits 3,4 + $this->maskPattern = new MaskPattern($formatInfo & 0x07); // Bottom 3 bits + + return $this; + } + + /** + * + */ + private function copyVersionBit(int $i, int $j, int $versionBits):int{ + + $bit = $this->mirror + ? $this->check($j, $i) + : $this->check($i, $j); + + return ($bit) ? (($versionBits << 1) | 0x1) : ($versionBits << 1); + } + + /** + * Returns information about the format it specifies, or null if it doesn't seem to match any known pattern + */ + private function doDecodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2):?int{ + $bestDifference = PHP_INT_MAX; + $bestFormatInfo = 0; + + // Find the int in FORMAT_INFO_DECODE_LOOKUP with the fewest bits differing + foreach($this::DECODE_LOOKUP as $maskedBits => $dataBits){ + + if($maskedFormatInfo1 === $dataBits || $maskedFormatInfo2 === $dataBits){ + // Found an exact match + return $maskedBits; + } + + $bitsDifference = $this->numBitsDiffering($maskedFormatInfo1, $dataBits); + + if($bitsDifference < $bestDifference){ + $bestFormatInfo = $maskedBits; + $bestDifference = $bitsDifference; + } + + if($maskedFormatInfo1 !== $maskedFormatInfo2){ + // also try the other option + $bitsDifference = $this->numBitsDiffering($maskedFormatInfo2, $dataBits); + + if($bitsDifference < $bestDifference){ + $bestFormatInfo = $maskedBits; + $bestDifference = $bitsDifference; + } + } + } + // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match + if($bestDifference <= 3){ + return $bestFormatInfo; + } + + return null; + } + + /** + * Reads version information from one of its two locations within the QR Code. + * Throws if both version information locations cannot be parsed as the valid encoding of version information. + * + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + * @noinspection DuplicatedCode + */ + private function readVersion():self{ + + if($this->version !== null){ + return $this; + } + + $provisionalVersion = (($this->moduleCount - 17) / 4); + + // no version info if v < 7 + if($provisionalVersion < 7){ + $this->version = new Version($provisionalVersion); + + return $this; + } + + // Read top-right version info: 3 wide by 6 tall + $versionBits = 0; + $ijMin = ($this->moduleCount - 11); + + for($y = 5; $y >= 0; $y--){ + for($x = ($this->moduleCount - 9); $x >= $ijMin; $x--){ + $versionBits = $this->copyVersionBit($x, $y, $versionBits); + } + } + + $this->version = $this->decodeVersionInformation($versionBits); + + if($this->version !== null && $this->version->getDimension() === $this->moduleCount){ + return $this; + } + + // Hmm, failed. Try bottom left: 6 wide by 3 tall + $versionBits = 0; + + for($x = 5; $x >= 0; $x--){ + for($y = ($this->moduleCount - 9); $y >= $ijMin; $y--){ + $versionBits = $this->copyVersionBit($x, $y, $versionBits); + } + } + + $this->version = $this->decodeVersionInformation($versionBits); + + if($this->version !== null && $this->version->getDimension() === $this->moduleCount){ + return $this; + } + + throw new QRCodeDecoderException('failed to read version'); + } + + /** + * Decodes the version information from the given bit sequence, returns null if no valid match is found. + */ + private function decodeVersionInformation(int $versionBits):?Version{ + $bestDifference = PHP_INT_MAX; + $bestVersion = 0; + + for($i = 7; $i <= 40; $i++){ + $targetVersion = new Version($i); + $targetVersionPattern = $targetVersion->getVersionPattern(); + + // Do the version info bits match exactly? done. + if($targetVersionPattern === $versionBits){ + return $targetVersion; + } + + // Otherwise see if this is the closest to a real version info bit string + // we have seen so far + /** @phan-suppress-next-line PhanTypeMismatchArgumentNullable ($targetVersionPattern is never null here) */ + $bitsDifference = $this->numBitsDiffering($versionBits, $targetVersionPattern); + + if($bitsDifference < $bestDifference){ + $bestVersion = $i; + $bestDifference = $bitsDifference; + } + } + // We can tolerate up to 3 bits of error since no two version info codewords will + // differ in less than 8 bits. + if($bestDifference <= 3){ + return new Version($bestVersion); + } + + // If we didn't find a close enough match, fail + return null; + } + + /** + * + */ + private function uRShift(int $a, int $b):int{ + + if($b === 0){ + return $a; + } + + return (($a >> $b) & ~((1 << (8 * PHP_INT_SIZE - 1)) >> ($b - 1))); + } + + /** + * + */ + private function numBitsDiffering(int $a, int $b):int{ + // a now has a 1 bit exactly where its bit differs with b's + $a ^= $b; + // Offset $i holds the number of 1-bits in the binary representation of $i + $BITS_SET_IN_HALF_BYTE = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4]; + // Count bits set quickly with a series of lookups: + $count = 0; + + for($i = 0; $i < 32; $i += 4){ + $count += $BITS_SET_IN_HALF_BYTE[($this->uRShift($a, $i) & 0x0F)]; + } + + return $count; + } + + /** + * @codeCoverageIgnore + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function setQuietZone(int $quietZoneSize = null):self{ + throw new QRCodeDataException('not supported'); + } + + /** + * @codeCoverageIgnore + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function setLogoSpace(int $width, int $height = null, int $startX = null, int $startY = null):self{ + throw new QRCodeDataException('not supported'); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Decoder/Decoder.php b/src/vendor/chillerlan/php-qrcode/src/Decoder/Decoder.php new file mode 100644 index 00000000..6f369a6d --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Decoder/Decoder.php @@ -0,0 +1,173 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Decoder; + +use chillerlan\QRCode\Common\{BitBuffer, EccLevel, LuminanceSourceInterface, MaskPattern, Mode, Version}; +use chillerlan\QRCode\Data\{AlphaNum, Byte, ECI, Hanzi, Kanji, Number}; +use chillerlan\QRCode\Detector\Detector; +use Throwable; +use function chr, str_replace; + +/** + * The main class which implements QR Code decoding -- as opposed to locating and extracting + * the QR Code from an image. + * + * @author Sean Owen + */ +final class Decoder{ + + private ?Version $version = null; + private ?EccLevel $eccLevel = null; + private ?MaskPattern $maskPattern = null; + private BitBuffer $bitBuffer; + + /** + * Decodes a QR Code represented as a BitMatrix. + * A 1 or "true" is taken to mean a black module. + * + * @throws \Throwable|\chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + public function decode(LuminanceSourceInterface $source):DecoderResult{ + $matrix = (new Detector($source))->detect(); + + try{ + // clone the BitMatrix to avoid errors in case we run into mirroring + return $this->decodeMatrix(clone $matrix); + } + catch(Throwable $e){ + + try{ + /* + * Prepare for a mirrored reading. + * + * Since we're here, this means we have successfully detected some kind + * of version and format information when mirrored. This is a good sign, + * that the QR code may be mirrored, and we should try once more with a + * mirrored content. + */ + return $this->decodeMatrix($matrix->resetVersionInfo()->mirrorDiagonal()); + } + catch(Throwable $f){ + // Throw the exception from the original reading + throw $e; + } + + } + + } + + /** + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + private function decodeMatrix(BitMatrix $matrix):DecoderResult{ + // Read raw codewords + $rawCodewords = $matrix->readCodewords(); + $this->version = $matrix->getVersion(); + $this->eccLevel = $matrix->getEccLevel(); + $this->maskPattern = $matrix->getMaskPattern(); + + if($this->version === null || $this->eccLevel === null || $this->maskPattern === null){ + throw new QRCodeDecoderException('unable to read version or format info'); // @codeCoverageIgnore + } + + $resultBytes = (new ReedSolomonDecoder($this->version, $this->eccLevel))->decode($rawCodewords); + + return $this->decodeBitStream($resultBytes); + } + + /** + * Decode the contents of that stream of bytes + * + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + private function decodeBitStream(BitBuffer $bitBuffer):DecoderResult{ + $this->bitBuffer = $bitBuffer; + $versionNumber = $this->version->getVersionNumber(); + $symbolSequence = -1; + $parityData = -1; + $fc1InEffect = false; + $result = ''; + + // While still another segment to read... + while($this->bitBuffer->available() >= 4){ + $datamode = $this->bitBuffer->read(4); // mode is encoded by 4 bits + + // OK, assume we're done + if($datamode === Mode::TERMINATOR){ + break; + } + elseif($datamode === Mode::NUMBER){ + $result .= Number::decodeSegment($this->bitBuffer, $versionNumber); + } + elseif($datamode === Mode::ALPHANUM){ + $result .= $this->decodeAlphanumSegment($versionNumber, $fc1InEffect); + } + elseif($datamode === Mode::BYTE){ + $result .= Byte::decodeSegment($this->bitBuffer, $versionNumber); + } + elseif($datamode === Mode::KANJI){ + $result .= Kanji::decodeSegment($this->bitBuffer, $versionNumber); + } + elseif($datamode === Mode::STRCTURED_APPEND){ + + if($this->bitBuffer->available() < 16){ + throw new QRCodeDecoderException('structured append: not enough bits left'); + } + // sequence number and parity is added later to the result metadata + // Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue + $symbolSequence = $this->bitBuffer->read(8); + $parityData = $this->bitBuffer->read(8); + } + elseif($datamode === Mode::FNC1_FIRST || $datamode === Mode::FNC1_SECOND){ + // We do little with FNC1 except alter the parsed result a bit according to the spec + $fc1InEffect = true; + } + elseif($datamode === Mode::ECI){ + $result .= ECI::decodeSegment($this->bitBuffer, $versionNumber); + } + elseif($datamode === Mode::HANZI){ + $result .= Hanzi::decodeSegment($this->bitBuffer, $versionNumber); + } + else{ + throw new QRCodeDecoderException('invalid data mode'); + } + + } + + return new DecoderResult([ + 'rawBytes' => $this->bitBuffer, + 'data' => $result, + 'version' => $this->version, + 'eccLevel' => $this->eccLevel, + 'maskPattern' => $this->maskPattern, + 'structuredAppendParity' => $parityData, + 'structuredAppendSequence' => $symbolSequence, + ]); + } + + /** + * + */ + private function decodeAlphanumSegment(int $versionNumber, bool $fc1InEffect):string{ + $str = AlphaNum::decodeSegment($this->bitBuffer, $versionNumber); + + // See section 6.4.8.1, 6.4.8.2 + if($fc1InEffect){ // ??? + // We need to massage the result a bit if in an FNC1 mode: + $str = str_replace(chr(0x1d), '%', $str); + $str = str_replace('%%', '%', $str); + } + + return $str; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php b/src/vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php new file mode 100644 index 00000000..02b4d793 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php @@ -0,0 +1,99 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Decoder; + +use chillerlan\QRCode\Common\{BitBuffer, EccLevel, MaskPattern, Version}; +use chillerlan\QRCode\Data\QRMatrix; +use function property_exists; + +/** + * Encapsulates the result of decoding a matrix of bits. This typically + * applies to 2D barcode formats. For now, it contains the raw bytes obtained + * as well as a String interpretation of those bytes, if applicable. + * + * @property \chillerlan\QRCode\Common\BitBuffer $rawBytes + * @property string $data + * @property \chillerlan\QRCode\Common\Version $version + * @property \chillerlan\QRCode\Common\EccLevel $eccLevel + * @property \chillerlan\QRCode\Common\MaskPattern $maskPattern + * @property int $structuredAppendParity + * @property int $structuredAppendSequence + */ +final class DecoderResult{ + + private BitBuffer $rawBytes; + private Version $version; + private EccLevel $eccLevel; + private MaskPattern $maskPattern; + private string $data = ''; + private int $structuredAppendParity = -1; + private int $structuredAppendSequence = -1; + + /** + * DecoderResult constructor. + */ + public function __construct(iterable $properties = null){ + + if(!empty($properties)){ + + foreach($properties as $property => $value){ + + if(!property_exists($this, $property)){ + continue; + } + + $this->{$property} = $value; + } + + } + + } + + /** + * @return mixed|null + */ + public function __get(string $property){ + + if(property_exists($this, $property)){ + return $this->{$property}; + } + + return null; + } + + /** + * + */ + public function __toString():string{ + return $this->data; + } + + /** + * + */ + public function hasStructuredAppend():bool{ + return $this->structuredAppendParity >= 0 && $this->structuredAppendSequence >= 0; + } + + /** + * Returns a QRMatrix instance with the settings and data of the reader result + */ + public function getQRMatrix():QRMatrix{ + return (new QRMatrix($this->version, $this->eccLevel)) + ->initFunctionalPatterns() + ->writeCodewords($this->rawBytes) + ->setFormatInfo($this->maskPattern) + ->mask($this->maskPattern) + ; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Decoder/QRCodeDecoderException.php b/src/vendor/chillerlan/php-qrcode/src/Decoder/QRCodeDecoderException.php new file mode 100644 index 00000000..11157afc --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Decoder/QRCodeDecoderException.php @@ -0,0 +1,20 @@ + + * @copyright 2021 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Decoder; + +use chillerlan\QRCode\QRCodeException; + +/** + * An exception container + */ +final class QRCodeDecoderException extends QRCodeException{ + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Decoder/ReedSolomonDecoder.php b/src/vendor/chillerlan/php-qrcode/src/Decoder/ReedSolomonDecoder.php new file mode 100644 index 00000000..2bd539aa --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Decoder/ReedSolomonDecoder.php @@ -0,0 +1,313 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Decoder; + +use chillerlan\QRCode\Common\{BitBuffer, EccLevel, GenericGFPoly, GF256, Version}; +use function array_fill, array_reverse, count; + +/** + * Implements Reed-Solomon decoding + * + * The algorithm will not be explained here, but the following references were helpful + * in creating this implementation: + * + * - Bruce Maggs "Decoding Reed-Solomon Codes" (see discussion of Forney's Formula) + * http://www.cs.cmu.edu/afs/cs.cmu.edu/project/pscico-guyb/realworld/www/rs_decode.ps + * - J.I. Hall. "Chapter 5. Generalized Reed-Solomon Codes" (see discussion of Euclidean algorithm) + * https://users.math.msu.edu/users/halljo/classes/codenotes/GRS.pdf + * + * Much credit is due to William Rucklidge since portions of this code are an indirect + * port of his C++ Reed-Solomon implementation. + * + * @author Sean Owen + * @author William Rucklidge + * @author sanfordsquires + */ +final class ReedSolomonDecoder{ + + private Version $version; + private EccLevel $eccLevel; + + /** + * ReedSolomonDecoder constructor + */ + public function __construct(Version $version, EccLevel $eccLevel){ + $this->version = $version; + $this->eccLevel = $eccLevel; + } + + /** + * Error-correct and copy data blocks together into a stream of bytes + */ + public function decode(array $rawCodewords):BitBuffer{ + $dataBlocks = $this->deinterleaveRawBytes($rawCodewords); + $dataBytes = []; + + foreach($dataBlocks as [$numDataCodewords, $codewordBytes]){ + $corrected = $this->correctErrors($codewordBytes, $numDataCodewords); + + for($i = 0; $i < $numDataCodewords; $i++){ + $dataBytes[] = $corrected[$i]; + } + } + + return new BitBuffer($dataBytes); + } + + /** + * When QR Codes use multiple data blocks, they are actually interleaved. + * That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This + * method will separate the data into original blocks. + * + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + private function deinterleaveRawBytes(array $rawCodewords):array{ + // Figure out the number and size of data blocks used by this version and + // error correction level + [$numEccCodewords, $eccBlocks] = $this->version->getRSBlocks($this->eccLevel); + + // Now establish DataBlocks of the appropriate size and number of data codewords + $result = [];//new DataBlock[$totalBlocks]; + $numResultBlocks = 0; + + foreach($eccBlocks as [$numEccBlocks, $eccPerBlock]){ + for($i = 0; $i < $numEccBlocks; $i++, $numResultBlocks++){ + $result[$numResultBlocks] = [$eccPerBlock, array_fill(0, ($numEccCodewords + $eccPerBlock), 0)]; + } + } + + // All blocks have the same amount of data, except that the last n + // (where n may be 0) have 1 more byte. Figure out where these start. + /** @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset */ + $shorterBlocksTotalCodewords = count($result[0][1]); + $longerBlocksStartAt = (count($result) - 1); + + while($longerBlocksStartAt >= 0){ + $numCodewords = count($result[$longerBlocksStartAt][1]); + + if($numCodewords == $shorterBlocksTotalCodewords){ + break; + } + + $longerBlocksStartAt--; + } + + $longerBlocksStartAt++; + + $shorterBlocksNumDataCodewords = ($shorterBlocksTotalCodewords - $numEccCodewords); + // The last elements of result may be 1 element longer; + // first fill out as many elements as all of them have + $rawCodewordsOffset = 0; + + for($i = 0; $i < $shorterBlocksNumDataCodewords; $i++){ + for($j = 0; $j < $numResultBlocks; $j++){ + $result[$j][1][$i] = $rawCodewords[$rawCodewordsOffset++]; + } + } + + // Fill out the last data block in the longer ones + for($j = $longerBlocksStartAt; $j < $numResultBlocks; $j++){ + $result[$j][1][$shorterBlocksNumDataCodewords] = $rawCodewords[$rawCodewordsOffset++]; + } + + // Now add in error correction blocks + /** @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset */ + $max = count($result[0][1]); + + for($i = $shorterBlocksNumDataCodewords; $i < $max; $i++){ + for($j = 0; $j < $numResultBlocks; $j++){ + $iOffset = ($j < $longerBlocksStartAt) ? $i : ($i + 1); + $result[$j][1][$iOffset] = $rawCodewords[$rawCodewordsOffset++]; + } + } + + // DataBlocks containing original bytes, "de-interleaved" from representation in the QR Code + return $result; + } + + /** + * Given data and error-correction codewords received, possibly corrupted by errors, attempts to + * correct the errors in-place using Reed-Solomon error correction. + */ + private function correctErrors(array $codewordBytes, int $numDataCodewords):array{ + // First read into an array of ints + $codewordsInts = []; + + foreach($codewordBytes as $codewordByte){ + $codewordsInts[] = ($codewordByte & 0xFF); + } + + $decoded = $this->decodeWords($codewordsInts, (count($codewordBytes) - $numDataCodewords)); + + // Copy back into array of bytes -- only need to worry about the bytes that were data + // We don't care about errors in the error-correction codewords + for($i = 0; $i < $numDataCodewords; $i++){ + $codewordBytes[$i] = $decoded[$i]; + } + + return $codewordBytes; + } + + /** + * Decodes given set of received codewords, which include both data and error-correction + * codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place, + * in the input. + * + * @param array $received data and error-correction codewords + * @param int $numEccCodewords number of error-correction codewords available + * + * @return int[] + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException if decoding fails for any reason + */ + private function decodeWords(array $received, int $numEccCodewords):array{ + $poly = new GenericGFPoly($received); + $syndromeCoefficients = []; + $error = false; + + for($i = 0; $i < $numEccCodewords; $i++){ + $syndromeCoefficients[$i] = $poly->evaluateAt(GF256::exp($i)); + + if($syndromeCoefficients[$i] !== 0){ + $error = true; + } + } + + if(!$error){ + return $received; + } + + [$sigma, $omega] = $this->runEuclideanAlgorithm( + GF256::buildMonomial($numEccCodewords, 1), + new GenericGFPoly(array_reverse($syndromeCoefficients)), + $numEccCodewords + ); + + $errorLocations = $this->findErrorLocations($sigma); + $errorMagnitudes = $this->findErrorMagnitudes($omega, $errorLocations); + $errorLocationsCount = count($errorLocations); + $receivedCount = count($received); + + for($i = 0; $i < $errorLocationsCount; $i++){ + $position = ($receivedCount - 1 - GF256::log($errorLocations[$i])); + + if($position < 0){ + throw new QRCodeDecoderException('Bad error location'); + } + + $received[$position] ^= $errorMagnitudes[$i]; + } + + return $received; + } + + /** + * @return \chillerlan\QRCode\Common\GenericGFPoly[] [sigma, omega] + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + private function runEuclideanAlgorithm(GenericGFPoly $a, GenericGFPoly $b, int $z):array{ + // Assume a's degree is >= b's + if($a->getDegree() < $b->getDegree()){ + $temp = $a; + $a = $b; + $b = $temp; + } + + $rLast = $a; + $r = $b; + $tLast = new GenericGFPoly([0]); + $t = new GenericGFPoly([1]); + + // Run Euclidean algorithm until r's degree is less than z/2 + while((2 * $r->getDegree()) >= $z){ + $rLastLast = $rLast; + $tLastLast = $tLast; + $rLast = $r; + $tLast = $t; + + // Divide rLastLast by rLast, with quotient in q and remainder in r + [$q, $r] = $rLastLast->divide($rLast); + + $t = $q->multiply($tLast)->addOrSubtract($tLastLast); + + if($r->getDegree() >= $rLast->getDegree()){ + throw new QRCodeDecoderException('Division algorithm failed to reduce polynomial?'); + } + } + + $sigmaTildeAtZero = $t->getCoefficient(0); + + if($sigmaTildeAtZero === 0){ + throw new QRCodeDecoderException('sigmaTilde(0) was zero'); + } + + $inverse = GF256::inverse($sigmaTildeAtZero); + + return [$t->multiplyInt($inverse), $r->multiplyInt($inverse)]; + } + + /** + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + private function findErrorLocations(GenericGFPoly $errorLocator):array{ + // This is a direct application of Chien's search + $numErrors = $errorLocator->getDegree(); + + if($numErrors === 1){ // shortcut + return [$errorLocator->getCoefficient(1)]; + } + + $result = array_fill(0, $numErrors, 0); + $e = 0; + + for($i = 1; $i < 256 && $e < $numErrors; $i++){ + if($errorLocator->evaluateAt($i) === 0){ + $result[$e] = GF256::inverse($i); + $e++; + } + } + + if($e !== $numErrors){ + throw new QRCodeDecoderException('Error locator degree does not match number of roots'); + } + + return $result; + } + + /** + * + */ + private function findErrorMagnitudes(GenericGFPoly $errorEvaluator, array $errorLocations):array{ + // This is directly applying Forney's Formula + $s = count($errorLocations); + $result = []; + + for($i = 0; $i < $s; $i++){ + $xiInverse = GF256::inverse($errorLocations[$i]); + $denominator = 1; + + for($j = 0; $j < $s; $j++){ + if($i !== $j){ +# $denominator = GF256::multiply($denominator, GF256::addOrSubtract(1, GF256::multiply($errorLocations[$j], $xiInverse))); + // Above should work but fails on some Apple and Linux JDKs due to a Hotspot bug. + // Below is a funny-looking workaround from Steven Parkes + $term = GF256::multiply($errorLocations[$j], $xiInverse); + $denominator = GF256::multiply($denominator, ((($term & 0x1) === 0) ? ($term | 1) : ($term & ~1))); + } + } + + $result[$i] = GF256::multiply($errorEvaluator->evaluateAt($xiInverse), GF256::inverse($denominator)); + } + + return $result; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php b/src/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php new file mode 100644 index 00000000..72feafdf --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php @@ -0,0 +1,34 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Detector; + +/** + * Encapsulates an alignment pattern, which are the smaller square patterns found in + * all but the simplest QR Codes. + * + * @author Sean Owen + */ +final class AlignmentPattern extends ResultPoint{ + + /** + * Combines this object's current estimate of a finder pattern position and module size + * with a new estimate. It returns a new FinderPattern containing an average of the two. + */ + public function combineEstimate(float $i, float $j, float $newModuleSize):self{ + return new self( + (($this->x + $j) / 2.0), + (($this->y + $i) / 2.0), + (($this->estimatedModuleSize + $newModuleSize) / 2.0) + ); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php b/src/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php new file mode 100644 index 00000000..ca62c6f3 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php @@ -0,0 +1,283 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Detector; + +use chillerlan\QRCode\Decoder\BitMatrix; +use function abs, count; + +/** + * This class attempts to find alignment patterns in a QR Code. Alignment patterns look like finder + * patterns but are smaller and appear at regular intervals throughout the image. + * + * At the moment this only looks for the bottom-right alignment pattern. + * + * This is mostly a simplified copy of FinderPatternFinder. It is copied, + * pasted and stripped down here for maximum performance but does unfortunately duplicate + * some code. + * + * This class is thread-safe but not reentrant. Each thread must allocate its own object. + * + * @author Sean Owen + */ +final class AlignmentPatternFinder{ + + private BitMatrix $matrix; + private float $moduleSize; + /** @var \chillerlan\QRCode\Detector\AlignmentPattern[] */ + private array $possibleCenters; + + /** + * Creates a finder that will look in a portion of the whole image. + * + * @param \chillerlan\QRCode\Decoder\BitMatrix $matrix image to search + * @param float $moduleSize estimated module size so far + */ + public function __construct(BitMatrix $matrix, float $moduleSize){ + $this->matrix = $matrix; + $this->moduleSize = $moduleSize; + $this->possibleCenters = []; + } + + /** + * This method attempts to find the bottom-right alignment pattern in the image. It is a bit messy since + * it's pretty performance-critical and so is written to be fast foremost. + * + * @param int $startX left column from which to start searching + * @param int $startY top row from which to start searching + * @param int $width width of region to search + * @param int $height height of region to search + * + * @return \chillerlan\QRCode\Detector\AlignmentPattern|null + */ + public function find(int $startX, int $startY, int $width, int $height):?AlignmentPattern{ + $maxJ = ($startX + $width); + $middleI = ($startY + ($height / 2)); + $stateCount = []; + + // We are looking for black/white/black modules in 1:1:1 ratio; + // this tracks the number of black/white/black modules seen so far + for($iGen = 0; $iGen < $height; $iGen++){ + // Search from middle outwards + $i = (int)($middleI + ((($iGen & 0x01) === 0) ? ($iGen + 1) / 2 : -(($iGen + 1) / 2))); + $stateCount[0] = 0; + $stateCount[1] = 0; + $stateCount[2] = 0; + $j = $startX; + // Burn off leading white pixels before anything else; if we start in the middle of + // a white run, it doesn't make sense to count its length, since we don't know if the + // white run continued to the left of the start point + while($j < $maxJ && !$this->matrix->check($j, $i)){ + $j++; + } + + $currentState = 0; + + while($j < $maxJ){ + + if($this->matrix->check($j, $i)){ + // Black pixel + if($currentState === 1){ // Counting black pixels + $stateCount[$currentState]++; + } + // Counting white pixels + else{ + // A winner? + if($currentState === 2){ + // Yes + if($this->foundPatternCross($stateCount)){ + $confirmed = $this->handlePossibleCenter($stateCount, $i, $j); + + if($confirmed !== null){ + return $confirmed; + } + } + + $stateCount[0] = $stateCount[2]; + $stateCount[1] = 1; + $stateCount[2] = 0; + $currentState = 1; + } + else{ + $stateCount[++$currentState]++; + } + } + } + // White pixel + else{ + // Counting black pixels + if($currentState === 1){ + $currentState++; + } + + $stateCount[$currentState]++; + } + + $j++; + } + + if($this->foundPatternCross($stateCount)){ + $confirmed = $this->handlePossibleCenter($stateCount, $i, $maxJ); + + if($confirmed !== null){ + return $confirmed; + } + } + + } + + // Hmm, nothing we saw was observed and confirmed twice. If we had + // any guess at all, return it. + if(count($this->possibleCenters)){ + return $this->possibleCenters[0]; + } + + return null; + } + + /** + * @param int[] $stateCount count of black/white/black pixels just read + * + * @return bool true if the proportions of the counts is close enough to the 1/1/1 ratios + * used by alignment patterns to be considered a match + */ + private function foundPatternCross(array $stateCount):bool{ + $maxVariance = ($this->moduleSize / 2.0); + + for($i = 0; $i < 3; $i++){ + if(abs($this->moduleSize - $stateCount[$i]) >= $maxVariance){ + return false; + } + } + + return true; + } + + /** + * This is called when a horizontal scan finds a possible alignment pattern. It will + * cross-check with a vertical scan, and if successful, will see if this pattern had been + * found on a previous horizontal scan. If so, we consider it confirmed and conclude we have + * found the alignment pattern. + * + * @param int[] $stateCount reading state module counts from horizontal scan + * @param int $i row where alignment pattern may be found + * @param int $j end of possible alignment pattern in row + * + * @return \chillerlan\QRCode\Detector\AlignmentPattern|null if we have found the same pattern twice, or null if not + */ + private function handlePossibleCenter(array $stateCount, int $i, int $j):?AlignmentPattern{ + $stateCountTotal = ($stateCount[0] + $stateCount[1] + $stateCount[2]); + $centerJ = $this->centerFromEnd($stateCount, $j); + $centerI = $this->crossCheckVertical($i, (int)$centerJ, (2 * $stateCount[1]), $stateCountTotal); + + if($centerI !== null){ + $estimatedModuleSize = (($stateCount[0] + $stateCount[1] + $stateCount[2]) / 3.0); + + foreach($this->possibleCenters as $center){ + // Look for about the same center and module size: + if($center->aboutEquals($estimatedModuleSize, $centerI, $centerJ)){ + return $center->combineEstimate($centerI, $centerJ, $estimatedModuleSize); + } + } + + // Hadn't found this before; save it + $point = new AlignmentPattern($centerJ, $centerI, $estimatedModuleSize); + $this->possibleCenters[] = $point; + } + + return null; + } + + /** + * Given a count of black/white/black pixels just seen and an end position, + * figures the location of the center of this black/white/black run. + * + * @param int[] $stateCount + * @param int $end + * + * @return float + */ + private function centerFromEnd(array $stateCount, int $end):float{ + return (float)(($end - $stateCount[2]) - $stateCount[1] / 2); + } + + /** + * After a horizontal scan finds a potential alignment pattern, this method + * "cross-checks" by scanning down vertically through the center of the possible + * alignment pattern to see if the same proportion is detected. + * + * @param int $startI row where an alignment pattern was detected + * @param int $centerJ center of the section that appears to cross an alignment pattern + * @param int $maxCount maximum reasonable number of modules that should be + * observed in any reading state, based on the results of the horizontal scan + * @param int $originalStateCountTotal + * + * @return float|null vertical center of alignment pattern, or null if not found + */ + private function crossCheckVertical(int $startI, int $centerJ, int $maxCount, int $originalStateCountTotal):?float{ + $maxI = $this->matrix->getSize(); + $stateCount = []; + $stateCount[0] = 0; + $stateCount[1] = 0; + $stateCount[2] = 0; + + // Start counting up from center + $i = $startI; + while($i >= 0 && $this->matrix->check($centerJ, $i) && $stateCount[1] <= $maxCount){ + $stateCount[1]++; + $i--; + } + // If already too many modules in this state or ran off the edge: + if($i < 0 || $stateCount[1] > $maxCount){ + return null; + } + + while($i >= 0 && !$this->matrix->check($centerJ, $i) && $stateCount[0] <= $maxCount){ + $stateCount[0]++; + $i--; + } + + if($stateCount[0] > $maxCount){ + return null; + } + + // Now also count down from center + $i = ($startI + 1); + while($i < $maxI && $this->matrix->check($centerJ, $i) && $stateCount[1] <= $maxCount){ + $stateCount[1]++; + $i++; + } + + if($i == $maxI || $stateCount[1] > $maxCount){ + return null; + } + + while($i < $maxI && !$this->matrix->check($centerJ, $i) && $stateCount[2] <= $maxCount){ + $stateCount[2]++; + $i++; + } + + if($stateCount[2] > $maxCount){ + return null; + } + + if((5 * abs(($stateCount[0] + $stateCount[1] + $stateCount[2]) - $originalStateCountTotal)) >= (2 * $originalStateCountTotal)){ + return null; + } + + if(!$this->foundPatternCross($stateCount)){ + return null; + } + + return $this->centerFromEnd($stateCount, $i); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Detector/Detector.php b/src/vendor/chillerlan/php-qrcode/src/Detector/Detector.php new file mode 100644 index 00000000..e43798b9 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Detector/Detector.php @@ -0,0 +1,350 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Detector; + +use chillerlan\QRCode\Common\{LuminanceSourceInterface, Version}; +use chillerlan\QRCode\Decoder\{Binarizer, BitMatrix}; +use function abs, intdiv, is_nan, max, min, round; +use const NAN; + +/** + * Encapsulates logic that can detect a QR Code in an image, even if the QR Code + * is rotated or skewed, or partially obscured. + * + * @author Sean Owen + */ +final class Detector{ + + private BitMatrix $matrix; + + /** + * Detector constructor. + */ + public function __construct(LuminanceSourceInterface $source){ + $this->matrix = (new Binarizer($source))->getBlackMatrix(); + } + + /** + * Detects a QR Code in an image. + */ + public function detect():BitMatrix{ + [$bottomLeft, $topLeft, $topRight] = (new FinderPatternFinder($this->matrix))->find(); + + $moduleSize = $this->calculateModuleSize($topLeft, $topRight, $bottomLeft); + $dimension = $this->computeDimension($topLeft, $topRight, $bottomLeft, $moduleSize); + $provisionalVersion = new Version(intdiv(($dimension - 17), 4)); + $alignmentPattern = null; + + // Anything above version 1 has an alignment pattern + if(!empty($provisionalVersion->getAlignmentPattern())){ + // Guess where a "bottom right" finder pattern would have been + $bottomRightX = ($topRight->getX() - $topLeft->getX() + $bottomLeft->getX()); + $bottomRightY = ($topRight->getY() - $topLeft->getY() + $bottomLeft->getY()); + + // Estimate that alignment pattern is closer by 3 modules + // from "bottom right" to known top left location + $correctionToTopLeft = (1.0 - 3.0 / (float)($provisionalVersion->getDimension() - 7)); + $estAlignmentX = (int)($topLeft->getX() + $correctionToTopLeft * ($bottomRightX - $topLeft->getX())); + $estAlignmentY = (int)($topLeft->getY() + $correctionToTopLeft * ($bottomRightY - $topLeft->getY())); + + // Kind of arbitrary -- expand search radius before giving up + for($i = 4; $i <= 16; $i <<= 1){//?????????? + $alignmentPattern = $this->findAlignmentInRegion($moduleSize, $estAlignmentX, $estAlignmentY, (float)$i); + + if($alignmentPattern !== null){ + break; + } + } + // If we didn't find alignment pattern... well try anyway without it + } + + $transform = $this->createTransform($topLeft, $topRight, $bottomLeft, $dimension, $alignmentPattern); + + return (new GridSampler)->sampleGrid($this->matrix, $dimension, $transform); + } + + /** + * Computes an average estimated module size based on estimated derived from the positions + * of the three finder patterns. + * + * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException + */ + private function calculateModuleSize(FinderPattern $topLeft, FinderPattern $topRight, FinderPattern $bottomLeft):float{ + // Take the average + $moduleSize = (( + $this->calculateModuleSizeOneWay($topLeft, $topRight) + + $this->calculateModuleSizeOneWay($topLeft, $bottomLeft) + ) / 2.0); + + if($moduleSize < 1.0){ + throw new QRCodeDetectorException('module size < 1.0'); + } + + return $moduleSize; + } + + /** + * Estimates module size based on two finder patterns -- it uses + * #sizeOfBlackWhiteBlackRunBothWays(int, int, int, int) to figure the + * width of each, measuring along the axis between their centers. + */ + private function calculateModuleSizeOneWay(FinderPattern $a, FinderPattern $b):float{ + + $moduleSizeEst1 = $this->sizeOfBlackWhiteBlackRunBothWays($a->getX(), $a->getY(), $b->getX(), $b->getY()); + $moduleSizeEst2 = $this->sizeOfBlackWhiteBlackRunBothWays($b->getX(), $b->getY(), $a->getX(), $a->getY()); + + if(is_nan($moduleSizeEst1)){ + return ($moduleSizeEst2 / 7.0); + } + + if(is_nan($moduleSizeEst2)){ + return ($moduleSizeEst1 / 7.0); + } + // Average them, and divide by 7 since we've counted the width of 3 black modules, + // and 1 white and 1 black module on either side. Ergo, divide sum by 14. + return (($moduleSizeEst1 + $moduleSizeEst2) / 14.0); + } + + /** + * See #sizeOfBlackWhiteBlackRun(int, int, int, int); computes the total width of + * a finder pattern by looking for a black-white-black run from the center in the direction + * of another po$(another finder pattern center), and in the opposite direction too. + * + * @noinspection DuplicatedCode + */ + private function sizeOfBlackWhiteBlackRunBothWays(float $fromX, float $fromY, float $toX, float $toY):float{ + $result = $this->sizeOfBlackWhiteBlackRun((int)$fromX, (int)$fromY, (int)$toX, (int)$toY); + $dimension = $this->matrix->getSize(); + // Now count other way -- don't run off image though of course + $scale = 1.0; + $otherToX = ($fromX - ($toX - $fromX)); + + if($otherToX < 0){ + $scale = ($fromX / ($fromX - $otherToX)); + $otherToX = 0; + } + elseif($otherToX >= $dimension){ + $scale = (($dimension - 1 - $fromX) / ($otherToX - $fromX)); + $otherToX = ($dimension - 1); + } + + $otherToY = (int)($fromY - ($toY - $fromY) * $scale); + $scale = 1.0; + + if($otherToY < 0){ + $scale = ($fromY / ($fromY - $otherToY)); + $otherToY = 0; + } + elseif($otherToY >= $dimension){ + $scale = (($dimension - 1 - $fromY) / ($otherToY - $fromY)); + $otherToY = ($dimension - 1); + } + + $otherToX = (int)($fromX + ($otherToX - $fromX) * $scale); + $result += $this->sizeOfBlackWhiteBlackRun((int)$fromX, (int)$fromY, $otherToX, $otherToY); + + // Middle pixel is double-counted this way; subtract 1 + return ($result - 1.0); + } + + /** + * This method traces a line from a po$in the image, in the direction towards another point. + * It begins in a black region, and keeps going until it finds white, then black, then white again. + * It reports the distance from the start to this point. + * + * This is used when figuring out how wide a finder pattern is, when the finder pattern + * may be skewed or rotated. + */ + private function sizeOfBlackWhiteBlackRun(int $fromX, int $fromY, int $toX, int $toY):float{ + // Mild variant of Bresenham's algorithm; + // @see https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm + $steep = abs($toY - $fromY) > abs($toX - $fromX); + + if($steep){ + $temp = $fromX; + $fromX = $fromY; + $fromY = $temp; + $temp = $toX; + $toX = $toY; + $toY = $temp; + } + + $dx = abs($toX - $fromX); + $dy = abs($toY - $fromY); + $error = (-$dx / 2); + $xstep = (($fromX < $toX) ? 1 : -1); + $ystep = (($fromY < $toY) ? 1 : -1); + + // In black pixels, looking for white, first or second time. + $state = 0; + // Loop up until x == toX, but not beyond + $xLimit = ($toX + $xstep); + + for($x = $fromX, $y = $fromY; $x !== $xLimit; $x += $xstep){ + $realX = ($steep) ? $y : $x; + $realY = ($steep) ? $x : $y; + + // Does current pixel mean we have moved white to black or vice versa? + // Scanning black in state 0,2 and white in state 1, so if we find the wrong + // color, advance to next state or end if we are in state 2 already + if(($state === 1) === $this->matrix->check($realX, $realY)){ + + if($state === 2){ + return FinderPattern::distance($x, $y, $fromX, $fromY); + } + + $state++; + } + + $error += $dy; + + if($error > 0){ + + if($y === $toY){ + break; + } + + $y += $ystep; + $error -= $dx; + } + } + + // Found black-white-black; give the benefit of the doubt that the next pixel outside the image + // is "white" so this last po$at (toX+xStep,toY) is the right ending. This is really a + // small approximation; (toX+xStep,toY+yStep) might be really correct. Ignore this. + if($state === 2){ + return FinderPattern::distance(($toX + $xstep), $toY, $fromX, $fromY); + } + + // else we didn't find even black-white-black; no estimate is really possible + return NAN; + } + + /** + * Computes the dimension (number of modules on a size) of the QR Code based on the position + * of the finder patterns and estimated module size. + * + * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException + */ + private function computeDimension(FinderPattern $nw, FinderPattern $ne, FinderPattern $sw, float $size):int{ + $tltrCentersDimension = (int)round($nw->getDistance($ne) / $size); + $tlblCentersDimension = (int)round($nw->getDistance($sw) / $size); + $dimension = (int)((($tltrCentersDimension + $tlblCentersDimension) / 2) + 7); + + switch($dimension % 4){ + case 0: + $dimension++; + break; + // 1? do nothing + case 2: + $dimension--; + break; + case 3: + throw new QRCodeDetectorException('estimated dimension: '.$dimension); + } + + if(($dimension % 4) !== 1){ + throw new QRCodeDetectorException('dimension mod 4 is not 1'); + } + + return $dimension; + } + + /** + * Attempts to locate an alignment pattern in a limited region of the image, which is + * guessed to contain it. + * + * @param float $overallEstModuleSize estimated module size so far + * @param int $estAlignmentX x coordinate of center of area probably containing alignment pattern + * @param int $estAlignmentY y coordinate of above + * @param float $allowanceFactor number of pixels in all directions to search from the center + * + * @return \chillerlan\QRCode\Detector\AlignmentPattern|null if found, or null otherwise + */ + private function findAlignmentInRegion( + float $overallEstModuleSize, + int $estAlignmentX, + int $estAlignmentY, + float $allowanceFactor + ):?AlignmentPattern{ + // Look for an alignment pattern (3 modules in size) around where it should be + $dimension = $this->matrix->getSize(); + $allowance = (int)($allowanceFactor * $overallEstModuleSize); + $alignmentAreaLeftX = max(0, ($estAlignmentX - $allowance)); + $alignmentAreaRightX = min(($dimension - 1), ($estAlignmentX + $allowance)); + + if(($alignmentAreaRightX - $alignmentAreaLeftX) < ($overallEstModuleSize * 3)){ + return null; + } + + $alignmentAreaTopY = max(0, ($estAlignmentY - $allowance)); + $alignmentAreaBottomY = min(($dimension - 1), ($estAlignmentY + $allowance)); + + if(($alignmentAreaBottomY - $alignmentAreaTopY) < ($overallEstModuleSize * 3)){ + return null; + } + + return (new AlignmentPatternFinder($this->matrix, $overallEstModuleSize))->find( + $alignmentAreaLeftX, + $alignmentAreaTopY, + ($alignmentAreaRightX - $alignmentAreaLeftX), + ($alignmentAreaBottomY - $alignmentAreaTopY), + ); + } + + /** + * + */ + private function createTransform( + FinderPattern $nw, + FinderPattern $ne, + FinderPattern $sw, + int $size, + AlignmentPattern $ap = null + ):PerspectiveTransform{ + $dimMinusThree = ($size - 3.5); + + if($ap instanceof AlignmentPattern){ + $bottomRightX = $ap->getX(); + $bottomRightY = $ap->getY(); + $sourceBottomRightX = ($dimMinusThree - 3.0); + $sourceBottomRightY = $sourceBottomRightX; + } + else{ + // Don't have an alignment pattern, just make up the bottom-right point + $bottomRightX = ($ne->getX() - $nw->getX() + $sw->getX()); + $bottomRightY = ($ne->getY() - $nw->getY() + $sw->getY()); + $sourceBottomRightX = $dimMinusThree; + $sourceBottomRightY = $dimMinusThree; + } + + return (new PerspectiveTransform)->quadrilateralToQuadrilateral( + 3.5, + 3.5, + $dimMinusThree, + 3.5, + $sourceBottomRightX, + $sourceBottomRightY, + 3.5, + $dimMinusThree, + $nw->getX(), + $nw->getY(), + $ne->getX(), + $ne->getY(), + $bottomRightX, + $bottomRightY, + $sw->getX(), + $sw->getY() + ); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php b/src/vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php new file mode 100644 index 00000000..1f799e87 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php @@ -0,0 +1,92 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Detector; + +use function sqrt; + +/** + * Encapsulates a finder pattern, which are the three square patterns found in + * the corners of QR Codes. It also encapsulates a count of similar finder patterns, + * as a convenience to the finder's bookkeeping. + * + * @author Sean Owen + */ +final class FinderPattern extends ResultPoint{ + + private int $count; + + /** + * + */ + public function __construct(float $posX, float $posY, float $estimatedModuleSize, int $count = null){ + parent::__construct($posX, $posY, $estimatedModuleSize); + + $this->count = ($count ?? 1); + } + + /** + * + */ + public function getCount():int{ + return $this->count; + } + + /** + * @param \chillerlan\QRCode\Detector\FinderPattern $b second pattern + * + * @return float distance between two points + */ + public function getDistance(FinderPattern $b):float{ + return self::distance($this->x, $this->y, $b->x, $b->y); + } + + /** + * Get square of distance between a and b. + */ + public function getSquaredDistance(FinderPattern $b):float{ + return self::squaredDistance($this->x, $this->y, $b->x, $b->y); + } + + /** + * Combines this object's current estimate of a finder pattern position and module size + * with a new estimate. It returns a new FinderPattern containing a weighted average + * based on count. + */ + public function combineEstimate(float $i, float $j, float $newModuleSize):self{ + $combinedCount = ($this->count + 1); + + return new self( + ($this->count * $this->x + $j) / $combinedCount, + ($this->count * $this->y + $i) / $combinedCount, + ($this->count * $this->estimatedModuleSize + $newModuleSize) / $combinedCount, + $combinedCount + ); + } + + /** + * + */ + private static function squaredDistance(float $aX, float $aY, float $bX, float $bY):float{ + $xDiff = ($aX - $bX); + $yDiff = ($aY - $bY); + + return ($xDiff * $xDiff + $yDiff * $yDiff); + } + + /** + * + */ + public static function distance(float $aX, float $aY, float $bX, float $bY):float{ + return sqrt(self::squaredDistance($aX, $aY, $bX, $bY)); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php b/src/vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php new file mode 100644 index 00000000..755d08c2 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php @@ -0,0 +1,770 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + * + * @phan-file-suppress PhanTypePossiblyInvalidDimOffset + */ + +namespace chillerlan\QRCode\Detector; + +use chillerlan\QRCode\Decoder\BitMatrix; +use function abs, count, intdiv, usort; +use const PHP_FLOAT_MAX; + +/** + * This class attempts to find finder patterns in a QR Code. Finder patterns are the square + * markers at three corners of a QR Code. + * + * This class is thread-safe but not reentrant. Each thread must allocate its own object. + * + * @author Sean Owen + */ +final class FinderPatternFinder{ + + private const MIN_SKIP = 2; + private const MAX_MODULES = 177; // 1 pixel/module times 3 modules/center + private const CENTER_QUORUM = 2; // support up to version 10 for mobile clients + private BitMatrix $matrix; + /** @var \chillerlan\QRCode\Detector\FinderPattern[] */ + private array $possibleCenters; + private bool $hasSkipped = false; + + /** + * Creates a finder that will search the image for three finder patterns. + * + * @param BitMatrix $matrix image to search + */ + public function __construct(BitMatrix $matrix){ + $this->matrix = $matrix; + $this->possibleCenters = []; + } + + /** + * @return \chillerlan\QRCode\Detector\FinderPattern[] + */ + public function find():array{ + $dimension = $this->matrix->getSize(); + + // We are looking for black/white/black/white/black modules in + // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far + // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the + // image, and then account for the center being 3 modules in size. This gives the smallest + // number of pixels the center could be, so skip this often. + $iSkip = intdiv((3 * $dimension), (4 * self::MAX_MODULES)); + + if($iSkip < self::MIN_SKIP){ + $iSkip = self::MIN_SKIP; + } + + $done = false; + + for($i = ($iSkip - 1); ($i < $dimension) && !$done; $i += $iSkip){ + // Get a row of black/white values + $stateCount = $this->getCrossCheckStateCount(); + $currentState = 0; + + for($j = 0; $j < $dimension; $j++){ + + // Black pixel + if($this->matrix->check($j, $i)){ + // Counting white pixels + if(($currentState & 1) === 1){ + $currentState++; + } + + $stateCount[$currentState]++; + } + // White pixel + else{ + // Counting black pixels + if(($currentState & 1) === 0){ + // A winner? + if($currentState === 4){ + // Yes + if($this->foundPatternCross($stateCount)){ + $confirmed = $this->handlePossibleCenter($stateCount, $i, $j); + + if($confirmed){ + // Start examining every other line. Checking each line turned out to be too + // expensive and didn't improve performance. + $iSkip = 3; + + if($this->hasSkipped){ + $done = $this->haveMultiplyConfirmedCenters(); + } + else{ + $rowSkip = $this->findRowSkip(); + + if($rowSkip > $stateCount[2]){ + // Skip rows between row of lower confirmed center + // and top of presumed third confirmed center + // but back up a bit to get a full chance of detecting + // it, entire width of center of finder pattern + + // Skip by rowSkip, but back off by $stateCount[2] (size of last center + // of pattern we saw) to be conservative, and also back off by iSkip which + // is about to be re-added + $i += ($rowSkip - $stateCount[2] - $iSkip); + $j = ($dimension - 1); + } + } + } + else{ + $stateCount = $this->doShiftCounts2($stateCount); + $currentState = 3; + + continue; + } + // Clear state to start looking again + $currentState = 0; + $stateCount = $this->getCrossCheckStateCount(); + } + // No, shift counts back by two + else{ + $stateCount = $this->doShiftCounts2($stateCount); + $currentState = 3; + } + } + else{ + $stateCount[++$currentState]++; + } + } + // Counting white pixels + else{ + $stateCount[$currentState]++; + } + } + } + + if($this->foundPatternCross($stateCount)){ + $confirmed = $this->handlePossibleCenter($stateCount, $i, $dimension); + + if($confirmed){ + $iSkip = $stateCount[0]; + + if($this->hasSkipped){ + // Found a third one + $done = $this->haveMultiplyConfirmedCenters(); + } + } + } + } + + return $this->orderBestPatterns($this->selectBestPatterns()); + } + + /** + * @return int[] + */ + private function getCrossCheckStateCount():array{ + return [0, 0, 0, 0, 0]; + } + + /** + * @param int[] $stateCount + * + * @return int[] + */ + private function doShiftCounts2(array $stateCount):array{ + $stateCount[0] = $stateCount[2]; + $stateCount[1] = $stateCount[3]; + $stateCount[2] = $stateCount[4]; + $stateCount[3] = 1; + $stateCount[4] = 0; + + return $stateCount; + } + + /** + * Given a count of black/white/black/white/black pixels just seen and an end position, + * figures the location of the center of this run. + * + * @param int[] $stateCount + */ + private function centerFromEnd(array $stateCount, int $end):float{ + return (float)(($end - $stateCount[4] - $stateCount[3]) - $stateCount[2] / 2); + } + + /** + * @param int[] $stateCount + */ + private function foundPatternCross(array $stateCount):bool{ + // Allow less than 50% variance from 1-1-3-1-1 proportions + return $this->foundPatternVariance($stateCount, 2.0); + } + + /** + * @param int[] $stateCount + */ + private function foundPatternDiagonal(array $stateCount):bool{ + // Allow less than 75% variance from 1-1-3-1-1 proportions + return $this->foundPatternVariance($stateCount, 1.333); + } + + /** + * @param int[] $stateCount count of black/white/black/white/black pixels just read + * + * @return bool true if the proportions of the counts is close enough to the 1/1/3/1/1 ratios + * used by finder patterns to be considered a match + */ + private function foundPatternVariance(array $stateCount, float $variance):bool{ + $totalModuleSize = 0; + + for($i = 0; $i < 5; $i++){ + $count = $stateCount[$i]; + + if($count === 0){ + return false; + } + + $totalModuleSize += $count; + } + + if($totalModuleSize < 7){ + return false; + } + + $moduleSize = ($totalModuleSize / 7.0); + $maxVariance = ($moduleSize / $variance); + + return + abs($moduleSize - $stateCount[0]) < $maxVariance + && abs($moduleSize - $stateCount[1]) < $maxVariance + && abs(3.0 * $moduleSize - $stateCount[2]) < (3 * $maxVariance) + && abs($moduleSize - $stateCount[3]) < $maxVariance + && abs($moduleSize - $stateCount[4]) < $maxVariance; + } + + /** + * After a vertical and horizontal scan finds a potential finder pattern, this method + * "cross-cross-cross-checks" by scanning down diagonally through the center of the possible + * finder pattern to see if the same proportion is detected. + * + * @param int $centerI row where a finder pattern was detected + * @param int $centerJ center of the section that appears to cross a finder pattern + * + * @return bool true if proportions are withing expected limits + */ + private function crossCheckDiagonal(int $centerI, int $centerJ):bool{ + $stateCount = $this->getCrossCheckStateCount(); + + // Start counting up, left from center finding black center mass + $i = 0; + + while($centerI >= $i && $centerJ >= $i && $this->matrix->check(($centerJ - $i), ($centerI - $i))){ + $stateCount[2]++; + $i++; + } + + if($stateCount[2] === 0){ + return false; + } + + // Continue up, left finding white space + while($centerI >= $i && $centerJ >= $i && !$this->matrix->check(($centerJ - $i), ($centerI - $i))){ + $stateCount[1]++; + $i++; + } + + if($stateCount[1] === 0){ + return false; + } + + // Continue up, left finding black border + while($centerI >= $i && $centerJ >= $i && $this->matrix->check(($centerJ - $i), ($centerI - $i))){ + $stateCount[0]++; + $i++; + } + + if($stateCount[0] === 0){ + return false; + } + + $dimension = $this->matrix->getSize(); + + // Now also count down, right from center + $i = 1; + while(($centerI + $i) < $dimension && ($centerJ + $i) < $dimension && $this->matrix->check(($centerJ + $i), ($centerI + $i))){ + $stateCount[2]++; + $i++; + } + + while(($centerI + $i) < $dimension && ($centerJ + $i) < $dimension && !$this->matrix->check(($centerJ + $i), ($centerI + $i))){ + $stateCount[3]++; + $i++; + } + + if($stateCount[3] === 0){ + return false; + } + + while(($centerI + $i) < $dimension && ($centerJ + $i) < $dimension && $this->matrix->check(($centerJ + $i), ($centerI + $i))){ + $stateCount[4]++; + $i++; + } + + if($stateCount[4] === 0){ + return false; + } + + return $this->foundPatternDiagonal($stateCount); + } + + /** + * After a horizontal scan finds a potential finder pattern, this method + * "cross-checks" by scanning down vertically through the center of the possible + * finder pattern to see if the same proportion is detected. + * + * @param int $startI row where a finder pattern was detected + * @param int $centerJ center of the section that appears to cross a finder pattern + * @param int $maxCount maximum reasonable number of modules that should be + * observed in any reading state, based on the results of the horizontal scan + * @param int $originalStateCountTotal + * + * @return float|null vertical center of finder pattern, or null if not found + * @noinspection DuplicatedCode + */ + private function crossCheckVertical(int $startI, int $centerJ, int $maxCount, int $originalStateCountTotal):?float{ + $maxI = $this->matrix->getSize(); + $stateCount = $this->getCrossCheckStateCount(); + + // Start counting up from center + $i = $startI; + while($i >= 0 && $this->matrix->check($centerJ, $i)){ + $stateCount[2]++; + $i--; + } + + if($i < 0){ + return null; + } + + while($i >= 0 && !$this->matrix->check($centerJ, $i) && $stateCount[1] <= $maxCount){ + $stateCount[1]++; + $i--; + } + + // If already too many modules in this state or ran off the edge: + if($i < 0 || $stateCount[1] > $maxCount){ + return null; + } + + while($i >= 0 && $this->matrix->check($centerJ, $i) && $stateCount[0] <= $maxCount){ + $stateCount[0]++; + $i--; + } + + if($stateCount[0] > $maxCount){ + return null; + } + + // Now also count down from center + $i = ($startI + 1); + while($i < $maxI && $this->matrix->check($centerJ, $i)){ + $stateCount[2]++; + $i++; + } + + if($i === $maxI){ + return null; + } + + while($i < $maxI && !$this->matrix->check($centerJ, $i) && $stateCount[3] < $maxCount){ + $stateCount[3]++; + $i++; + } + + if($i === $maxI || $stateCount[3] >= $maxCount){ + return null; + } + + while($i < $maxI && $this->matrix->check($centerJ, $i) && $stateCount[4] < $maxCount){ + $stateCount[4]++; + $i++; + } + + if($stateCount[4] >= $maxCount){ + return null; + } + + // If we found a finder-pattern-like section, but its size is more than 40% different from + // the original, assume it's a false positive + $stateCountTotal = ($stateCount[0] + $stateCount[1] + $stateCount[2] + $stateCount[3] + $stateCount[4]); + + if((5 * abs($stateCountTotal - $originalStateCountTotal)) >= (2 * $originalStateCountTotal)){ + return null; + } + + if(!$this->foundPatternCross($stateCount)){ + return null; + } + + return $this->centerFromEnd($stateCount, $i); + } + + /** + * Like #crossCheckVertical(int, int, int, int), and in fact is basically identical, + * except it reads horizontally instead of vertically. This is used to cross-cross + * check a vertical cross-check and locate the real center of the alignment pattern. + * @noinspection DuplicatedCode + */ + private function crossCheckHorizontal(int $startJ, int $centerI, int $maxCount, int $originalStateCountTotal):?float{ + $maxJ = $this->matrix->getSize(); + $stateCount = $this->getCrossCheckStateCount(); + + $j = $startJ; + while($j >= 0 && $this->matrix->check($j, $centerI)){ + $stateCount[2]++; + $j--; + } + + if($j < 0){ + return null; + } + + while($j >= 0 && !$this->matrix->check($j, $centerI) && $stateCount[1] <= $maxCount){ + $stateCount[1]++; + $j--; + } + + if($j < 0 || $stateCount[1] > $maxCount){ + return null; + } + + while($j >= 0 && $this->matrix->check($j, $centerI) && $stateCount[0] <= $maxCount){ + $stateCount[0]++; + $j--; + } + + if($stateCount[0] > $maxCount){ + return null; + } + + $j = ($startJ + 1); + while($j < $maxJ && $this->matrix->check($j, $centerI)){ + $stateCount[2]++; + $j++; + } + + if($j === $maxJ){ + return null; + } + + while($j < $maxJ && !$this->matrix->check($j, $centerI) && $stateCount[3] < $maxCount){ + $stateCount[3]++; + $j++; + } + + if($j === $maxJ || $stateCount[3] >= $maxCount){ + return null; + } + + while($j < $maxJ && $this->matrix->check($j, $centerI) && $stateCount[4] < $maxCount){ + $stateCount[4]++; + $j++; + } + + if($stateCount[4] >= $maxCount){ + return null; + } + + // If we found a finder-pattern-like section, but its size is significantly different from + // the original, assume it's a false positive + $stateCountTotal = ($stateCount[0] + $stateCount[1] + $stateCount[2] + $stateCount[3] + $stateCount[4]); + + if((5 * abs($stateCountTotal - $originalStateCountTotal)) >= $originalStateCountTotal){ + return null; + } + + if(!$this->foundPatternCross($stateCount)){ + return null; + } + + return $this->centerFromEnd($stateCount, $j); + } + + /** + * This is called when a horizontal scan finds a possible alignment pattern. It will + * cross-check with a vertical scan, and if successful, will, ah, cross-cross-check + * with another horizontal scan. This is needed primarily to locate the real horizontal + * center of the pattern in cases of extreme skew. + * And then we cross-cross-cross check with another diagonal scan. + * + * If that succeeds the finder pattern location is added to a list that tracks + * the number of times each location has been nearly-matched as a finder pattern. + * Each additional find is more evidence that the location is in fact a finder + * pattern center + * + * @param int[] $stateCount reading state module counts from horizontal scan + * @param int $i row where finder pattern may be found + * @param int $j end of possible finder pattern in row + * + * @return bool if a finder pattern candidate was found this time + */ + private function handlePossibleCenter(array $stateCount, int $i, int $j):bool{ + $stateCountTotal = ($stateCount[0] + $stateCount[1] + $stateCount[2] + $stateCount[3] + $stateCount[4]); + $centerJ = $this->centerFromEnd($stateCount, $j); + $centerI = $this->crossCheckVertical($i, (int)$centerJ, $stateCount[2], $stateCountTotal); + + if($centerI !== null){ + // Re-cross check + $centerJ = $this->crossCheckHorizontal((int)$centerJ, (int)$centerI, $stateCount[2], $stateCountTotal); + if($centerJ !== null && ($this->crossCheckDiagonal((int)$centerI, (int)$centerJ))){ + $estimatedModuleSize = ($stateCountTotal / 7.0); + $found = false; + + // cautious (was in for fool in which $this->possibleCenters is updated) + $count = count($this->possibleCenters); + + for($index = 0; $index < $count; $index++){ + $center = $this->possibleCenters[$index]; + // Look for about the same center and module size: + if($center->aboutEquals($estimatedModuleSize, $centerI, $centerJ)){ + $this->possibleCenters[$index] = $center->combineEstimate($centerI, $centerJ, $estimatedModuleSize); + $found = true; + break; + } + } + + if(!$found){ + $point = new FinderPattern($centerJ, $centerI, $estimatedModuleSize); + $this->possibleCenters[] = $point; + } + + return true; + } + } + + return false; + } + + /** + * @return int number of rows we could safely skip during scanning, based on the first + * two finder patterns that have been located. In some cases their position will + * allow us to infer that the third pattern must lie below a certain point farther + * down in the image. + */ + private function findRowSkip():int{ + $max = count($this->possibleCenters); + + if($max <= 1){ + return 0; + } + + $firstConfirmedCenter = null; + + foreach($this->possibleCenters as $center){ + + if($center->getCount() >= self::CENTER_QUORUM){ + + if($firstConfirmedCenter === null){ + $firstConfirmedCenter = $center; + } + else{ + // We have two confirmed centers + // How far down can we skip before resuming looking for the next + // pattern? In the worst case, only the difference between the + // difference in the x / y coordinates of the two centers. + // This is the case where you find top left last. + $this->hasSkipped = true; + + return (int)((abs($firstConfirmedCenter->getX() - $center->getX()) - + abs($firstConfirmedCenter->getY() - $center->getY())) / 2); + } + } + } + + return 0; + } + + /** + * @return bool true if we have found at least 3 finder patterns that have been detected + * at least #CENTER_QUORUM times each, and, the estimated module size of the + * candidates is "pretty similar" + */ + private function haveMultiplyConfirmedCenters():bool{ + $confirmedCount = 0; + $totalModuleSize = 0.0; + $max = count($this->possibleCenters); + + foreach($this->possibleCenters as $pattern){ + if($pattern->getCount() >= self::CENTER_QUORUM){ + $confirmedCount++; + $totalModuleSize += $pattern->getEstimatedModuleSize(); + } + } + + if($confirmedCount < 3){ + return false; + } + // OK, we have at least 3 confirmed centers, but, it's possible that one is a "false positive" + // and that we need to keep looking. We detect this by asking if the estimated module sizes + // vary too much. We arbitrarily say that when the total deviation from average exceeds + // 5% of the total module size estimates, it's too much. + $average = ($totalModuleSize / (float)$max); + $totalDeviation = 0.0; + + foreach($this->possibleCenters as $pattern){ + $totalDeviation += abs($pattern->getEstimatedModuleSize() - $average); + } + + return $totalDeviation <= (0.05 * $totalModuleSize); + } + + /** + * @return \chillerlan\QRCode\Detector\FinderPattern[] the 3 best FinderPatterns from our list of candidates. The "best" are + * those that have been detected at least #CENTER_QUORUM times, and whose module + * size differs from the average among those patterns the least + * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException if 3 such finder patterns do not exist + */ + private function selectBestPatterns():array{ + $startSize = count($this->possibleCenters); + + if($startSize < 3){ + throw new QRCodeDetectorException('could not find enough finder patterns'); + } + + usort( + $this->possibleCenters, + fn(FinderPattern $a, FinderPattern $b) => ($a->getEstimatedModuleSize() <=> $b->getEstimatedModuleSize()) + ); + + $distortion = PHP_FLOAT_MAX; + $bestPatterns = []; + + for($i = 0; $i < ($startSize - 2); $i++){ + $fpi = $this->possibleCenters[$i]; + $minModuleSize = $fpi->getEstimatedModuleSize(); + + for($j = ($i + 1); $j < ($startSize - 1); $j++){ + $fpj = $this->possibleCenters[$j]; + $squares0 = $fpi->getSquaredDistance($fpj); + + for($k = ($j + 1); $k < $startSize; $k++){ + $fpk = $this->possibleCenters[$k]; + $maxModuleSize = $fpk->getEstimatedModuleSize(); + + // module size is not similar + if($maxModuleSize > ($minModuleSize * 1.4)){ + continue; + } + + $a = $squares0; + $b = $fpj->getSquaredDistance($fpk); + $c = $fpi->getSquaredDistance($fpk); + + // sorts ascending - inlined + if($a < $b){ + if($b > $c){ + if($a < $c){ + $temp = $b; + $b = $c; + $c = $temp; + } + else{ + $temp = $a; + $a = $c; + $c = $b; + $b = $temp; + } + } + } + else{ + if($b < $c){ + if($a < $c){ + $temp = $a; + $a = $b; + $b = $temp; + } + else{ + $temp = $a; + $a = $b; + $b = $c; + $c = $temp; + } + } + else{ + $temp = $a; + $a = $c; + $c = $temp; + } + } + + // a^2 + b^2 = c^2 (Pythagorean theorem), and a = b (isosceles triangle). + // Since any right triangle satisfies the formula c^2 - b^2 - a^2 = 0, + // we need to check both two equal sides separately. + // The value of |c^2 - 2 * b^2| + |c^2 - 2 * a^2| increases as dissimilarity + // from isosceles right triangle. + $d = (abs($c - 2 * $b) + abs($c - 2 * $a)); + + if($d < $distortion){ + $distortion = $d; + $bestPatterns = [$fpi, $fpj, $fpk]; + } + } + } + } + + if($distortion === PHP_FLOAT_MAX){ + throw new QRCodeDetectorException('finder patterns may be too distorted'); + } + + return $bestPatterns; + } + + /** + * Orders an array of three ResultPoints in an order [A,B,C] such that AB is less than AC + * and BC is less than AC, and the angle between BC and BA is less than 180 degrees. + * + * @param \chillerlan\QRCode\Detector\FinderPattern[] $patterns array of three FinderPattern to order + * + * @return \chillerlan\QRCode\Detector\FinderPattern[] + */ + private function orderBestPatterns(array $patterns):array{ + + // Find distances between pattern centers + $zeroOneDistance = $patterns[0]->getDistance($patterns[1]); + $oneTwoDistance = $patterns[1]->getDistance($patterns[2]); + $zeroTwoDistance = $patterns[0]->getDistance($patterns[2]); + + // Assume one closest to other two is B; A and C will just be guesses at first + if($oneTwoDistance >= $zeroOneDistance && $oneTwoDistance >= $zeroTwoDistance){ + [$pointB, $pointA, $pointC] = $patterns; + } + elseif($zeroTwoDistance >= $oneTwoDistance && $zeroTwoDistance >= $zeroOneDistance){ + [$pointA, $pointB, $pointC] = $patterns; + } + else{ + [$pointA, $pointC, $pointB] = $patterns; + } + + // Use cross product to figure out whether A and C are correct or flipped. + // This asks whether BC x BA has a positive z component, which is the arrangement + // we want for A, B, C. If it's negative, then we've got it flipped around and + // should swap A and C. + if($this->crossProductZ($pointA, $pointB, $pointC) < 0.0){ + $temp = $pointA; + $pointA = $pointC; + $pointC = $temp; + } + + return [$pointA, $pointB, $pointC]; + } + + /** + * Returns the z component of the cross product between vectors BC and BA. + */ + private function crossProductZ(FinderPattern $pointA, FinderPattern $pointB, FinderPattern $pointC):float{ + $bX = $pointB->getX(); + $bY = $pointB->getY(); + + return ((($pointC->getX() - $bX) * ($pointA->getY() - $bY)) - (($pointC->getY() - $bY) * ($pointA->getX() - $bX))); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php b/src/vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php new file mode 100644 index 00000000..0d915e3e --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php @@ -0,0 +1,181 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Detector; + +use chillerlan\QRCode\Data\QRMatrix; +use chillerlan\QRCode\Decoder\BitMatrix; +use function array_fill, count, intdiv, sprintf; + +/** + * Implementations of this class can, given locations of finder patterns for a QR code in an + * image, sample the right points in the image to reconstruct the QR code, accounting for + * perspective distortion. It is abstracted since it is relatively expensive and should be allowed + * to take advantage of platform-specific optimized implementations, like Sun's Java Advanced + * Imaging library, but which may not be available in other environments such as J2ME, and vice + * versa. + * + * The implementation used can be controlled by calling #setGridSampler(GridSampler) + * with an instance of a class which implements this interface. + * + * @author Sean Owen + */ +final class GridSampler{ + + private array $points; + + /** + * Checks a set of points that have been transformed to sample points on an image against + * the image's dimensions to see if the point are even within the image. + * + * This method will actually "nudge" the endpoints back onto the image if they are found to be + * barely (less than 1 pixel) off the image. This accounts for imperfect detection of finder + * patterns in an image where the QR Code runs all the way to the image border. + * + * For efficiency, the method will check points from either end of the line until one is found + * to be within the image. Because the set of points are assumed to be linear, this is valid. + * + * @param int $dimension matrix width/height + * + * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException if an endpoint is lies outside the image boundaries + */ + private function checkAndNudgePoints(int $dimension):void{ + $nudged = true; + $max = count($this->points); + + // Check and nudge points from start until we see some that are OK: + for($offset = 0; $offset < $max && $nudged; $offset += 2){ + $x = (int)$this->points[$offset]; + $y = (int)$this->points[($offset + 1)]; + + if($x < -1 || $x > $dimension || $y < -1 || $y > $dimension){ + throw new QRCodeDetectorException(sprintf('checkAndNudgePoints 1, x: %s, y: %s, d: %s', $x, $y, $dimension)); + } + + $nudged = false; + + if($x === -1){ + $this->points[$offset] = 0.0; + $nudged = true; + } + elseif($x === $dimension){ + $this->points[$offset] = ($dimension - 1); + $nudged = true; + } + + if($y === -1){ + $this->points[($offset + 1)] = 0.0; + $nudged = true; + } + elseif($y === $dimension){ + $this->points[($offset + 1)] = ($dimension - 1); + $nudged = true; + } + + } + + // Check and nudge points from end: + $nudged = true; + + for($offset = ($max - 2); $offset >= 0 && $nudged; $offset -= 2){ + $x = (int)$this->points[$offset]; + $y = (int)$this->points[($offset + 1)]; + + if($x < -1 || $x > $dimension || $y < -1 || $y > $dimension){ + throw new QRCodeDetectorException(sprintf('checkAndNudgePoints 2, x: %s, y: %s, d: %s', $x, $y, $dimension)); + } + + $nudged = false; + + if($x === -1){ + $this->points[$offset] = 0.0; + $nudged = true; + } + elseif($x === $dimension){ + $this->points[$offset] = ($dimension - 1); + $nudged = true; + } + + if($y === -1){ + $this->points[($offset + 1)] = 0.0; + $nudged = true; + } + elseif($y === $dimension){ + $this->points[($offset + 1)] = ($dimension - 1); + $nudged = true; + } + + } + + } + + /** + * Samples an image for a rectangular matrix of bits of the given dimension. The sampling + * transformation is determined by the coordinates of 4 points, in the original and transformed + * image space. + * + * @return \chillerlan\QRCode\Decoder\BitMatrix representing a grid of points sampled from the image within a region + * defined by the "from" parameters + * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException if image can't be sampled, for example, if the transformation defined + * by the given points is invalid or results in sampling outside the image boundaries + */ + public function sampleGrid(BitMatrix $matrix, int $dimension, PerspectiveTransform $transform):BitMatrix{ + + if($dimension <= 0){ + throw new QRCodeDetectorException('invalid matrix size'); + } + + $bits = new BitMatrix($dimension); + $this->points = array_fill(0, (2 * $dimension), 0.0); + + for($y = 0; $y < $dimension; $y++){ + $max = count($this->points); + $iValue = ($y + 0.5); + + for($x = 0; $x < $max; $x += 2){ + $this->points[$x] = (($x / 2) + 0.5); + $this->points[($x + 1)] = $iValue; + } + // phpcs:ignore + [$this->points, ] = $transform->transformPoints($this->points); + // Quick check to see if points transformed to something inside the image; + // sufficient to check the endpoints + $this->checkAndNudgePoints($matrix->getSize()); + + // no need to try/catch as QRMatrix::set() will silently discard out of bounds values +# try{ + for($x = 0; $x < $max; $x += 2){ + // Black(-ish) pixel + $bits->set( + intdiv($x, 2), + $y, + $matrix->check((int)$this->points[$x], (int)$this->points[($x + 1)]), + QRMatrix::M_DATA + ); + } +# } +# catch(\Throwable $aioobe){//ArrayIndexOutOfBoundsException + // This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting + // transform gets "twisted" such that it maps a straight line of points to a set of points + // whose endpoints are in bounds, but others are not. There is probably some mathematical + // way to detect this about the transformation that I don't know yet. + // This results in an ugly runtime exception despite our clever checks above -- can't have + // that. We could check each point's coordinates but that feels duplicative. We settle for + // catching and wrapping ArrayIndexOutOfBoundsException. +# throw new QRCodeDetectorException('ArrayIndexOutOfBoundsException'); +# } + + } + + return $bits; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php b/src/vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php new file mode 100644 index 00000000..4f1f6547 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php @@ -0,0 +1,182 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Detector; + +use function count; + +/** + * This class implements a perspective transform in two dimensions. Given four source and four + * destination points, it will compute the transformation implied between them. The code is based + * directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56. + * + * @author Sean Owen + */ +final class PerspectiveTransform{ + + private float $a11; + private float $a12; + private float $a13; + private float $a21; + private float $a22; + private float $a23; + private float $a31; + private float $a32; + private float $a33; + + /** + * + */ + private function set( + float $a11, float $a21, float $a31, + float $a12, float $a22, float $a32, + float $a13, float $a23, float $a33 + ):self{ + $this->a11 = $a11; + $this->a12 = $a12; + $this->a13 = $a13; + $this->a21 = $a21; + $this->a22 = $a22; + $this->a23 = $a23; + $this->a31 = $a31; + $this->a32 = $a32; + $this->a33 = $a33; + + return $this; + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function quadrilateralToQuadrilateral( + float $x0, float $y0, float $x1, float $y1, float $x2, float $y2, float $x3, float $y3, + float $x0p, float $y0p, float $x1p, float $y1p, float $x2p, float $y2p, float $x3p, float $y3p + ):self{ + return (new self) + ->squareToQuadrilateral($x0p, $y0p, $x1p, $y1p, $x2p, $y2p, $x3p, $y3p) + ->times($this->quadrilateralToSquare($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)); + } + + /** + * + */ + private function quadrilateralToSquare( + float $x0, float $y0, float $x1, float $y1, + float $x2, float $y2, float $x3, float $y3 + ):self{ + // Here, the adjoint serves as the inverse: + return $this + ->squareToQuadrilateral($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3) + ->buildAdjoint(); + } + + /** + * + */ + private function buildAdjoint():self{ + // Adjoint is the transpose of the cofactor matrix: + return $this->set( + ($this->a22 * $this->a33 - $this->a23 * $this->a32), + ($this->a23 * $this->a31 - $this->a21 * $this->a33), + ($this->a21 * $this->a32 - $this->a22 * $this->a31), + ($this->a13 * $this->a32 - $this->a12 * $this->a33), + ($this->a11 * $this->a33 - $this->a13 * $this->a31), + ($this->a12 * $this->a31 - $this->a11 * $this->a32), + ($this->a12 * $this->a23 - $this->a13 * $this->a22), + ($this->a13 * $this->a21 - $this->a11 * $this->a23), + ($this->a11 * $this->a22 - $this->a12 * $this->a21) + ); + } + + /** + * + */ + private function squareToQuadrilateral( + float $x0, float $y0, float $x1, float $y1, + float $x2, float $y2, float $x3, float $y3 + ):self{ + $dx3 = ($x0 - $x1 + $x2 - $x3); + $dy3 = ($y0 - $y1 + $y2 - $y3); + + if($dx3 === 0.0 && $dy3 === 0.0){ + // Affine + return $this->set(($x1 - $x0), ($x2 - $x1), $x0, ($y1 - $y0), ($y2 - $y1), $y0, 0.0, 0.0, 1.0); + } + + $dx1 = ($x1 - $x2); + $dx2 = ($x3 - $x2); + $dy1 = ($y1 - $y2); + $dy2 = ($y3 - $y2); + $denominator = ($dx1 * $dy2 - $dx2 * $dy1); + $a13 = (($dx3 * $dy2 - $dx2 * $dy3) / $denominator); + $a23 = (($dx1 * $dy3 - $dx3 * $dy1) / $denominator); + + return $this->set( + ($x1 - $x0 + $a13 * $x1), + ($x3 - $x0 + $a23 * $x3), + $x0, + ($y1 - $y0 + $a13 * $y1), + ($y3 - $y0 + $a23 * $y3), + $y0, + $a13, + $a23, + 1.0 + ); + } + + /** + * + */ + private function times(PerspectiveTransform $other):self{ + return $this->set( + ($this->a11 * $other->a11 + $this->a21 * $other->a12 + $this->a31 * $other->a13), + ($this->a11 * $other->a21 + $this->a21 * $other->a22 + $this->a31 * $other->a23), + ($this->a11 * $other->a31 + $this->a21 * $other->a32 + $this->a31 * $other->a33), + ($this->a12 * $other->a11 + $this->a22 * $other->a12 + $this->a32 * $other->a13), + ($this->a12 * $other->a21 + $this->a22 * $other->a22 + $this->a32 * $other->a23), + ($this->a12 * $other->a31 + $this->a22 * $other->a32 + $this->a32 * $other->a33), + ($this->a13 * $other->a11 + $this->a23 * $other->a12 + $this->a33 * $other->a13), + ($this->a13 * $other->a21 + $this->a23 * $other->a22 + $this->a33 * $other->a23), + ($this->a13 * $other->a31 + $this->a23 * $other->a32 + $this->a33 * $other->a33) + ); + } + + /** + * @return array[] [$xValues, $yValues] + */ + public function transformPoints(array $xValues, array $yValues = null):array{ + $max = count($xValues); + + if($yValues !== null){ // unused + + for($i = 0; $i < $max; $i++){ + $x = $xValues[$i]; + $y = $yValues[$i]; + $denominator = ($this->a13 * $x + $this->a23 * $y + $this->a33); + $xValues[$i] = (($this->a11 * $x + $this->a21 * $y + $this->a31) / $denominator); + $yValues[$i] = (($this->a12 * $x + $this->a22 * $y + $this->a32) / $denominator); + } + + return [$xValues, $yValues]; + } + + for($i = 0; $i < $max; $i += 2){ + $x = $xValues[$i]; + $y = $xValues[($i + 1)]; + $denominator = ($this->a13 * $x + $this->a23 * $y + $this->a33); + $xValues[$i] = (($this->a11 * $x + $this->a21 * $y + $this->a31) / $denominator); + $xValues[($i + 1)] = (($this->a12 * $x + $this->a22 * $y + $this->a32) / $denominator); + } + + return [$xValues, []]; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php b/src/vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php new file mode 100644 index 00000000..2444e193 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php @@ -0,0 +1,20 @@ + + * @copyright 2021 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Detector; + +use chillerlan\QRCode\QRCodeException; + +/** + * An exception container + */ +final class QRCodeDetectorException extends QRCodeException{ + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php b/src/vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php new file mode 100644 index 00000000..92997a74 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php @@ -0,0 +1,73 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Detector; + +use function abs; + +/** + * Encapsulates a point of interest in an image containing a barcode. Typically, this + * would be the location of a finder pattern or the corner of the barcode, for example. + * + * @author Sean Owen + */ +abstract class ResultPoint{ + + protected float $x; + protected float $y; + protected float $estimatedModuleSize; + + /** + * + */ + public function __construct(float $x, float $y, float $estimatedModuleSize){ + $this->x = $x; + $this->y = $y; + $this->estimatedModuleSize = $estimatedModuleSize; + } + + /** + * + */ + public function getX():float{ + return $this->x; + } + + /** + * + */ + public function getY():float{ + return $this->y; + } + + /** + * + */ + public function getEstimatedModuleSize():float{ + return $this->estimatedModuleSize; + } + + /** + * Determines if this finder pattern "about equals" a finder pattern at the stated + * position and size -- meaning, it is at nearly the same center with nearly the same size. + */ + public function aboutEquals(float $moduleSize, float $i, float $j):bool{ + + if(abs($i - $this->y) <= $moduleSize && abs($j - $this->x) <= $moduleSize){ + $moduleSizeDiff = abs($moduleSize - $this->estimatedModuleSize); + + return $moduleSizeDiff <= 1.0 || $moduleSizeDiff <= $this->estimatedModuleSize; + } + + return false; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php new file mode 100644 index 00000000..bf30f1bb --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php @@ -0,0 +1,20 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use chillerlan\QRCode\QRCodeException; + +/** + * An exception container + */ +final class QRCodeOutputException extends QRCodeException{ + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QREps.php b/src/vendor/chillerlan/php-qrcode/src/Output/QREps.php new file mode 100644 index 00000000..a04f20d5 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QREps.php @@ -0,0 +1,173 @@ + + * @copyright 2022 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use function array_values, count, date, implode, is_array, is_numeric, max, min, round, sprintf; + +/** + * Encapsulated Postscript (EPS) output + * + * @see https://github.com/t0k4rt/phpqrcode/blob/bb29e6eb77e0a2a85bb0eb62725e0adc11ff5a90/qrvect.php#L52-L137 + * @see https://web.archive.org/web/20170818010030/http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/postscript/pdfs/5002.EPSF_Spec.pdf + * @see https://web.archive.org/web/20210419003859/https://www.adobe.com/content/dam/acom/en/devnet/actionscript/articles/PLRM.pdf + * @see https://github.com/chillerlan/php-qrcode/discussions/148 + */ +class QREps extends QROutputAbstract{ + + public const MIME_TYPE = 'application/postscript'; + + /** + * @inheritDoc + */ + public static function moduleValueIsValid($value):bool{ + + if(!is_array($value) || count($value) < 3){ + return false; + } + + // check the first values of the array + foreach(array_values($value) as $i => $val){ + + if($i > 3){ + break; + } + + if(!is_numeric($val)){ + return false; + } + + } + + return true; + } + + /** + * @param array $value + * + * @inheritDoc + */ + protected function prepareModuleValue($value):string{ + $values = []; + + foreach(array_values($value) as $i => $val){ + + if($i > 3){ + break; + } + + // clamp value and convert from int 0-255 to float 0-1 RGB/CMYK range + $values[] = round((max(0, min(255, intval($val))) / 255), 6); + } + + return $this->formatColor($values); + } + + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):string{ + return $this->formatColor(($isDark) ? [0.0, 0.0, 0.0] : [1.0, 1.0, 1.0]); + } + + /** + * Set the color format string + * + * 4 values in the color array will be interpreted as CMYK, 3 as RGB + * + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + protected function formatColor(array $values):string{ + $count = count($values); + + if($count < 3){ + throw new QRCodeOutputException('invalid color value'); + } + + $format = ($count === 4) + // CMYK + ? '%f %f %f %f C' + // RGB + :'%f %f %f R'; + + return sprintf($format, ...$values); + } + + /** + * @inheritDoc + */ + public function dump(string $file = null):string{ + [$width, $height] = $this->getOutputDimensions(); + + $eps = [ + // main header + '%!PS-Adobe-3.0 EPSF-3.0', + '%%Creator: php-qrcode (https://github.com/chillerlan/php-qrcode)', + '%%Title: QR Code', + sprintf('%%%%CreationDate: %1$s', date('c')), + '%%DocumentData: Clean7Bit', + '%%LanguageLevel: 3', + sprintf('%%%%BoundingBox: 0 0 %s %s', $width, $height), + '%%EndComments', + // function definitions + '%%BeginProlog', + '/F { rectfill } def', + '/R { setrgbcolor } def', + '/C { setcmykcolor } def', + '%%EndProlog', + ]; + + if($this::moduleValueIsValid($this->options->bgColor)){ + $eps[] = $this->prepareModuleValue($this->options->bgColor); + $eps[] = sprintf('0 0 %s %s F', $width, $height); + } + + // create the path elements + $paths = $this->collectModules(fn(int $x, int $y, int $M_TYPE):string => $this->module($x, $y, $M_TYPE)); + + foreach($paths as $M_TYPE => $path){ + + if(empty($path)){ + continue; + } + + $eps[] = $this->getModuleValue($M_TYPE); + $eps[] = implode("\n", $path); + } + + // end file + $eps[] = '%%EOF'; + + $data = implode("\n", $eps); + + $this->saveToFile($data, $file); + + return $data; + } + + /** + * Returns a path segment for a single module + */ + protected function module(int $x, int $y, int $M_TYPE):string{ + + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ + return ''; + } + + $outputX = ($x * $this->scale); + // Actual size - one block = Topmost y pos. + $top = ($this->length - $this->scale); + // Apparently y-axis is inverted (y0 is at bottom and not top) in EPS, so we have to switch the y-axis here + $outputY = ($top - ($y * $this->scale)); + + return sprintf('%d %d %d %d F', $outputX, $outputY, $this->scale, $this->scale); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php new file mode 100644 index 00000000..a8029541 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php @@ -0,0 +1,177 @@ + $val){ + + if($i > 2){ + break; + } + + if(!is_numeric($val)){ + return false; + } + + } + + return true; + } + + /** + * @param array $value + * + * @inheritDoc + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + protected function prepareModuleValue($value):array{ + $values = []; + + foreach(array_values($value) as $i => $val){ + + if($i > 2){ + break; + } + + $values[] = max(0, min(255, intval($val))); + } + + if(count($values) !== 3){ + throw new QRCodeOutputException('invalid color value'); + } + + return $values; + } + + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):array{ + return ($isDark) ? [0, 0, 0] : [255, 255, 255]; + } + + /** + * Initializes an FPDF instance + */ + protected function initFPDF():FPDF{ + return new FPDF('P', $this->options->fpdfMeasureUnit, $this->getOutputDimensions()); + } + + /** + * @inheritDoc + * + * @return string|\FPDF + */ + public function dump(string $file = null){ + $this->fpdf = $this->initFPDF(); + $this->fpdf->AddPage(); + + if($this::moduleValueIsValid($this->options->bgColor)){ + $bgColor = $this->prepareModuleValue($this->options->bgColor); + [$width, $height] = $this->getOutputDimensions(); + + /** @phan-suppress-next-line PhanParamTooFewUnpack */ + $this->fpdf->SetFillColor(...$bgColor); + $this->fpdf->Rect(0, 0, $width, $height, 'F'); + } + + $this->prevColor = null; + + foreach($this->matrix->getMatrix() as $y => $row){ + foreach($row as $x => $M_TYPE){ + $this->module($x, $y, $M_TYPE); + } + } + + if($this->options->returnResource){ + return $this->fpdf; + } + + $pdfData = $this->fpdf->Output('S'); + + $this->saveToFile($pdfData, $file); + + if($this->options->outputBase64){ + $pdfData = $this->toBase64DataURI($pdfData); + } + + return $pdfData; + } + + /** + * Renders a single module + */ + protected function module(int $x, int $y, int $M_TYPE):void{ + + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ + return; + } + + $color = $this->getModuleValue($M_TYPE); + + if($color !== null && $color !== $this->prevColor){ + /** @phan-suppress-next-line PhanParamTooFewUnpack */ + $this->fpdf->SetFillColor(...$color); + $this->prevColor = $color; + } + + $this->fpdf->Rect(($x * $this->scale), ($y * $this->scale), $this->scale, $this->scale, 'F'); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php new file mode 100644 index 00000000..f3ed6ea9 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php @@ -0,0 +1,400 @@ + + * @copyright 2015 Smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use chillerlan\QRCode\Data\QRMatrix; +use chillerlan\Settings\SettingsContainerInterface; +use ErrorException; +use Throwable; +use function array_values, count, extension_loaded, imagebmp, imagecolorallocate, imagecolortransparent, + imagecreatetruecolor, imagedestroy, imagefilledellipse, imagefilledrectangle, imagegif, imagejpeg, imagepng, + imagescale, imagetypes, imagewebp, intdiv, intval, is_array, is_numeric, max, min, ob_end_clean, ob_get_contents, ob_start, + restore_error_handler, set_error_handler, sprintf; +use const IMG_BMP, IMG_GIF, IMG_JPG, IMG_PNG, IMG_WEBP; + +/** + * Converts the matrix into GD images, raw or base64 output (requires ext-gd) + * + * @see https://php.net/manual/book.image.php + * + * @deprecated 5.0.0 this class will be made abstract in future versions, + * calling it directly is deprecated - use one of the child classes instead + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ +class QRGdImage extends QROutputAbstract{ + + /** + * The GD image resource + * + * @see imagecreatetruecolor() + * @var resource|\GdImage + * + * @todo: add \GdImage type in v6 + */ + protected $image; + + /** + * The allocated background color + * + * @see \imagecolorallocate() + */ + protected int $background; + + /** + * Whether we're running in upscale mode (scale < 20) + * + * @see \chillerlan\QRCode\QROptions::$drawCircularModules + */ + protected bool $upscaled = false; + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + * @noinspection PhpMissingParentConstructorInspection + */ + public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){ + $this->options = $options; + $this->matrix = $matrix; + + $this->checkGD(); + + if($this->options->invertMatrix){ + $this->matrix->invert(); + } + + $this->copyVars(); + $this->setMatrixDimensions(); + } + + /** + * Checks whether GD is installed and if the given mode is supported + * + * @return void + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + * @codeCoverageIgnore + */ + protected function checkGD():void{ + + if(!extension_loaded('gd')){ + throw new QRCodeOutputException('ext-gd not loaded'); + } + + $modes = [ + self::GDIMAGE_BMP => IMG_BMP, + self::GDIMAGE_GIF => IMG_GIF, + self::GDIMAGE_JPG => IMG_JPG, + self::GDIMAGE_PNG => IMG_PNG, + self::GDIMAGE_WEBP => IMG_WEBP, + ]; + + // likely using default or custom output + if(!isset($modes[$this->options->outputType])){ + return; + } + + $mode = $modes[$this->options->outputType]; + + if((imagetypes() & $mode) !== $mode){ + throw new QRCodeOutputException(sprintf('output mode "%s" not supported', $this->options->outputType)); + } + + } + + /** + * @inheritDoc + */ + public static function moduleValueIsValid($value):bool{ + + if(!is_array($value) || count($value) < 3){ + return false; + } + + // check the first 3 values of the array + foreach(array_values($value) as $i => $val){ + + if($i > 2){ + break; + } + + if(!is_numeric($val)){ + return false; + } + + } + + return true; + } + + /** + * @param array $value + * + * @inheritDoc + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + protected function prepareModuleValue($value):int{ + $values = []; + + foreach(array_values($value) as $i => $val){ + + if($i > 2){ + break; + } + + $values[] = max(0, min(255, intval($val))); + } + + /** @phan-suppress-next-line PhanParamTooFewInternalUnpack */ + $color = imagecolorallocate($this->image, ...$values); + + if($color === false){ + throw new QRCodeOutputException('could not set color: imagecolorallocate() error'); + } + + return $color; + } + + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):int{ + return $this->prepareModuleValue(($isDark) ? [0, 0, 0] : [255, 255, 255]); + } + + /** + * @inheritDoc + * + * @return string|resource|\GdImage + * + * @phan-suppress PhanUndeclaredTypeReturnType, PhanTypeMismatchReturn + * @throws \ErrorException + */ + public function dump(string $file = null){ + + set_error_handler(function(int $errno, string $errstr):bool{ + throw new ErrorException($errstr, $errno); + }); + + $this->image = $this->createImage(); + // set module values after image creation because we need the GdImage instance + $this->setModuleValues(); + $this->setBgColor(); + + imagefilledrectangle($this->image, 0, 0, $this->length, $this->length, $this->background); + + $this->drawImage(); + + if($this->upscaled){ + // scale down to the expected size + $this->image = imagescale($this->image, ($this->length / 10), ($this->length / 10)); + $this->upscaled = false; + } + + // set transparency after scaling, otherwise it would be undone + // @see https://www.php.net/manual/en/function.imagecolortransparent.php#77099 + $this->setTransparencyColor(); + + if($this->options->returnResource){ + restore_error_handler(); + + return $this->image; + } + + $imageData = $this->dumpImage(); + + $this->saveToFile($imageData, $file); + + if($this->options->outputBase64){ + // @todo: remove mime parameter in v6 + $imageData = $this->toBase64DataURI($imageData, 'image/'.$this->options->outputType); + } + + restore_error_handler(); + + return $imageData; + } + + /** + * Creates a new GdImage resource and scales it if necessary + * + * we're scaling the image up in order to draw crisp round circles, otherwise they appear square-y on small scales + * + * @see https://github.com/chillerlan/php-qrcode/issues/23 + * + * @return \GdImage|resource + */ + protected function createImage(){ + + if($this->drawCircularModules && $this->options->gdImageUseUpscale && $this->options->scale < 20){ + // increase the initial image size by 10 + $this->length *= 10; + $this->scale *= 10; + $this->upscaled = true; + } + + return imagecreatetruecolor($this->length, $this->length); + } + + /** + * Sets the background color + */ + protected function setBgColor():void{ + + if(isset($this->background)){ + return; + } + + if($this::moduleValueIsValid($this->options->bgColor)){ + $this->background = $this->prepareModuleValue($this->options->bgColor); + + return; + } + + $this->background = $this->prepareModuleValue([255, 255, 255]); + } + + /** + * Sets the transparency color + */ + protected function setTransparencyColor():void{ + + // @todo: the jpg skip can be removed in v6 + if($this->options->outputType === QROutputInterface::GDIMAGE_JPG || !$this->options->imageTransparent){ + return; + } + + $transparencyColor = $this->background; + + if($this::moduleValueIsValid($this->options->transparencyColor)){ + $transparencyColor = $this->prepareModuleValue($this->options->transparencyColor); + } + + imagecolortransparent($this->image, $transparencyColor); + } + + /** + * Draws the QR image + */ + protected function drawImage():void{ + foreach($this->matrix->getMatrix() as $y => $row){ + foreach($row as $x => $M_TYPE){ + $this->module($x, $y, $M_TYPE); + } + } + } + + /** + * Creates a single QR pixel with the given settings + */ + protected function module(int $x, int $y, int $M_TYPE):void{ + + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ + return; + } + + $color = $this->getModuleValue($M_TYPE); + + if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){ + imagefilledellipse( + $this->image, + (($x * $this->scale) + intdiv($this->scale, 2)), + (($y * $this->scale) + intdiv($this->scale, 2)), + (int)($this->circleDiameter * $this->scale), + (int)($this->circleDiameter * $this->scale), + $color + ); + + return; + } + + imagefilledrectangle( + $this->image, + ($x * $this->scale), + ($y * $this->scale), + (($x + 1) * $this->scale), + (($y + 1) * $this->scale), + $color + ); + } + + /** + * Renders the image with the gdimage function for the desired output + * + * @see \imagebmp() + * @see \imagegif() + * @see \imagejpeg() + * @see \imagepng() + * @see \imagewebp() + * + * @todo: v6.0: make abstract and call from child classes + * @see https://github.com/chillerlan/php-qrcode/issues/223 + * @codeCoverageIgnore + */ + protected function renderImage():void{ + + switch($this->options->outputType){ + case QROutputInterface::GDIMAGE_BMP: + imagebmp($this->image, null, ($this->options->quality > 0)); + break; + case QROutputInterface::GDIMAGE_GIF: + imagegif($this->image); + break; + case QROutputInterface::GDIMAGE_JPG: + imagejpeg($this->image, null, max(-1, min(100, $this->options->quality))); + break; + case QROutputInterface::GDIMAGE_WEBP: + imagewebp($this->image, null, max(-1, min(100, $this->options->quality))); + break; + // silently default to png output + case QROutputInterface::GDIMAGE_PNG: + default: + imagepng($this->image, null, max(-1, min(9, $this->options->quality))); + } + + } + + /** + * Creates the final image by calling the desired GD output function + * + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + protected function dumpImage():string{ + $exception = null; + $imageData = null; + + ob_start(); + + try{ + $this->renderImage(); + + $imageData = ob_get_contents(); + imagedestroy($this->image); + } + // not going to cover edge cases + // @codeCoverageIgnoreStart + catch(Throwable $e){ + $exception = $e; + } + // @codeCoverageIgnoreEnd + + ob_end_clean(); + + // throw here in case an exception happened within the output buffer + if($exception instanceof Throwable){ + throw new QRCodeOutputException($exception->getMessage()); + } + + return $imageData; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php new file mode 100644 index 00000000..268ebe7c --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php @@ -0,0 +1,33 @@ + + * @copyright 2023 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function imagebmp; + +/** + * GdImage bmp output + * + * @see \imagebmp() + */ +class QRGdImageBMP extends QRGdImage{ + + public const MIME_TYPE = 'image/bmp'; + + /** + * @inheritDoc + */ + protected function renderImage():void{ + imagebmp($this->image, null, ($this->options->quality > 0)); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php new file mode 100644 index 00000000..a0213090 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php @@ -0,0 +1,33 @@ + + * @copyright 2023 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function imagegif; + +/** + * GdImage gif output + * + * @see \imagegif() + */ +class QRGdImageGIF extends QRGdImage{ + + public const MIME_TYPE = 'image/gif'; + + /** + * @inheritDoc + */ + protected function renderImage():void{ + imagegif($this->image); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php new file mode 100644 index 00000000..6be36e2f --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php @@ -0,0 +1,40 @@ + + * @copyright 2023 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function imagejpeg, max, min; + +/** + * GdImage jpeg output + * + * @see \imagejpeg() + */ +class QRGdImageJPEG extends QRGdImage{ + + public const MIME_TYPE = 'image/jpg'; + + /** + * @inheritDoc + */ + protected function setTransparencyColor():void{ + // noop - transparency is not supported + } + + /** + * @inheritDoc + */ + protected function renderImage():void{ + imagejpeg($this->image, null, max(-1, min(100, $this->options->quality))); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php new file mode 100644 index 00000000..2db3fd5b --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php @@ -0,0 +1,33 @@ + + * @copyright 2023 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function imagepng, max, min; + +/** + * GdImage png output + * + * @see \imagepng() + */ +class QRGdImagePNG extends QRGdImage{ + + public const MIME_TYPE = 'image/png'; + + /** + * @inheritDoc + */ + protected function renderImage():void{ + imagepng($this->image, null, max(-1, min(9, $this->options->quality))); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php new file mode 100644 index 00000000..cf8dfa9a --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php @@ -0,0 +1,33 @@ + + * @copyright 2023 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function imagewebp, max, min; + +/** + * GdImage webp output + * + * @see \imagewebp() + */ +class QRGdImageWEBP extends QRGdImage{ + + public const MIME_TYPE = 'image/webp'; + + /** + * @inheritDoc + */ + protected function renderImage():void{ + imagewebp($this->image, null, max(-1, min(100, $this->options->quality))); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRImage.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRImage.php new file mode 100644 index 00000000..cda496d3 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRImage.php @@ -0,0 +1,19 @@ + + * @copyright 2021 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +/** + * @deprecated 5.0.0 backward compatibility, use QRGdImage instead + * @see \chillerlan\QRCode\Output\QRGdImage + */ +class QRImage extends QRGdImage{ + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php new file mode 100644 index 00000000..cfd3a581 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php @@ -0,0 +1,235 @@ + + * @copyright 2018 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use chillerlan\QRCode\Data\QRMatrix; +use chillerlan\Settings\SettingsContainerInterface; +use finfo, Imagick, ImagickDraw, ImagickPixel; +use function extension_loaded, in_array, is_string, max, min, preg_match, strlen; +use const FILEINFO_MIME_TYPE; + +/** + * ImageMagick output module (requires ext-imagick) + * + * @see https://php.net/manual/book.imagick.php + * @see https://phpimagick.com + */ +class QRImagick extends QROutputAbstract{ + + /** + * The main image instance + */ + protected Imagick $imagick; + + /** + * The main draw instance + */ + protected ImagickDraw $imagickDraw; + + /** + * The allocated background color + */ + protected ImagickPixel $backgroundColor; + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){ + + foreach(['fileinfo', 'imagick'] as $ext){ + if(!extension_loaded($ext)){ + throw new QRCodeOutputException(sprintf('ext-%s not loaded', $ext)); // @codeCoverageIgnore + } + } + + parent::__construct($options, $matrix); + } + + /** + * note: we're not necessarily validating the several values, just checking the general syntax + * + * @see https://www.php.net/manual/imagickpixel.construct.php + * @inheritDoc + */ + public static function moduleValueIsValid($value):bool{ + + if(!is_string($value)){ + return false; + } + + $value = trim($value); + + // hex notation + // #rgb(a) + // #rrggbb(aa) + // #rrrrggggbbbb(aaaa) + // ... + if(preg_match('/^#[a-f\d]+$/i', $value) && in_array((strlen($value) - 1), [3, 4, 6, 8, 9, 12, 16, 24, 32], true)){ + return true; + } + + // css (-like) func(...values) + if(preg_match('#^(graya?|hs(b|la?)|rgba?)\([\d .,%]+\)$#i', $value)){ + return true; + } + + // predefined css color + if(preg_match('/^[a-z]+$/i', $value)){ + return true; + } + + return false; + } + + /** + * @inheritDoc + */ + protected function prepareModuleValue($value):ImagickPixel{ + return new ImagickPixel($value); + } + + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):ImagickPixel{ + return $this->prepareModuleValue(($isDark) ? '#000' : '#fff'); + } + + /** + * @inheritDoc + * + * @return string|\Imagick + */ + public function dump(string $file = null){ + $this->setBgColor(); + + $this->imagick = $this->createImage(); + + $this->drawImage(); + // set transparency color after all operations + $this->setTransparencyColor(); + + if($this->options->returnResource){ + return $this->imagick; + } + + $imageData = $this->imagick->getImageBlob(); + + $this->imagick->destroy(); + + $this->saveToFile($imageData, $file); + + if($this->options->outputBase64){ + $imageData = $this->toBase64DataURI($imageData, (new finfo(FILEINFO_MIME_TYPE))->buffer($imageData)); + } + + return $imageData; + } + + /** + * Sets the background color + */ + protected function setBgColor():void{ + + if($this::moduleValueIsValid($this->options->bgColor)){ + $this->backgroundColor = $this->prepareModuleValue($this->options->bgColor); + + return; + } + + $this->backgroundColor = $this->prepareModuleValue('white'); + } + + /** + * Creates a new Imagick instance + */ + protected function createImage():Imagick{ + $imagick = new Imagick; + [$width, $height] = $this->getOutputDimensions(); + + $imagick->newImage($width, $height, $this->backgroundColor, $this->options->imagickFormat); + + if($this->options->quality > -1){ + $imagick->setImageCompressionQuality(max(0, min(100, $this->options->quality))); + } + + return $imagick; + } + + /** + * Sets the transparency color + */ + protected function setTransparencyColor():void{ + + if(!$this->options->imageTransparent){ + return; + } + + $transparencyColor = $this->backgroundColor; + + if($this::moduleValueIsValid($this->options->transparencyColor)){ + $transparencyColor = $this->prepareModuleValue($this->options->transparencyColor); + } + + $this->imagick->transparentPaintImage($transparencyColor, 0.0, 10, false); + } + + /** + * Creates the QR image via ImagickDraw + */ + protected function drawImage():void{ + $this->imagickDraw = new ImagickDraw; + $this->imagickDraw->setStrokeWidth(0); + + foreach($this->matrix->getMatrix() as $y => $row){ + foreach($row as $x => $M_TYPE){ + $this->module($x, $y, $M_TYPE); + } + } + + $this->imagick->drawImage($this->imagickDraw); + } + + /** + * draws a single pixel at the given position + */ + protected function module(int $x, int $y, int $M_TYPE):void{ + + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ + return; + } + + $this->imagickDraw->setFillColor($this->getModuleValue($M_TYPE)); + + if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){ + $this->imagickDraw->circle( + (($x + 0.5) * $this->scale), + (($y + 0.5) * $this->scale), + (($x + 0.5 + $this->circleRadius) * $this->scale), + (($y + 0.5) * $this->scale) + ); + + return; + } + + $this->imagickDraw->rectangle( + ($x * $this->scale), + ($y * $this->scale), + ((($x + 1) * $this->scale) - 1), + ((($y + 1) * $this->scale) - 1) + ); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php new file mode 100644 index 00000000..22551e3e --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php @@ -0,0 +1,94 @@ + + * @copyright 2016 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use function is_string, preg_match, strip_tags, trim; + +/** + * Abstract for markup types: HTML, SVG, ... XML anyone? + */ +abstract class QRMarkup extends QROutputAbstract{ + + /** + * note: we're not necessarily validating the several values, just checking the general syntax + * note: css4 colors are not included + * + * @todo: XSS proof + * + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/color_value + * @inheritDoc + */ + public static function moduleValueIsValid($value):bool{ + + if(!is_string($value)){ + return false; + } + + $value = trim(strip_tags($value), " '\"\r\n\t"); + + // hex notation + // #rgb(a) + // #rrggbb(aa) + if(preg_match('/^#([\da-f]{3}){1,2}$|^#([\da-f]{4}){1,2}$/i', $value)){ + return true; + } + + // css: hsla/rgba(...values) + if(preg_match('#^(hsla?|rgba?)\([\d .,%/]+\)$#i', $value)){ + return true; + } + + // predefined css color + if(preg_match('/^[a-z]+$/i', $value)){ + return true; + } + + return false; + } + + /** + * @inheritDoc + */ + protected function prepareModuleValue($value):string{ + return trim(strip_tags($value), " '\"\r\n\t"); + } + + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):string{ + return ($isDark) ? '#000' : '#fff'; + } + + /** + * @inheritDoc + */ + public function dump(string $file = null):string{ + $data = $this->createMarkup($file !== null); + + $this->saveToFile($data, $file); + + return $data; + } + + /** + * returns a string with all css classes for the current element + */ + protected function getCssClass(int $M_TYPE = 0):string{ + return $this->options->cssClass; + } + + /** + * returns the fully parsed and rendered markup string for the given input + */ + abstract protected function createMarkup(bool $saveToFile):string; + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php new file mode 100644 index 00000000..65dc49a8 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php @@ -0,0 +1,51 @@ + + * @copyright 2022 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use function implode, sprintf; + +/** + * HTML output (a cheap markup substitute when SVG is not available or not an option) + */ +class QRMarkupHTML extends QRMarkup{ + + public const MIME_TYPE = 'text/html'; + + /** + * @inheritDoc + */ + protected function createMarkup(bool $saveToFile):string{ + $rows = []; + $cssClass = $this->getCssClass(); + + foreach($this->matrix->getMatrix() as $row){ + $element = ''; + $modules = array_map(fn(int $M_TYPE):string => sprintf($element, $this->getModuleValue($M_TYPE)), $row); + + $rows[] = sprintf('
%s
%s', implode('', $modules), $this->eol); + } + + $html = sprintf('
%3$s%2$s
%3$s', $cssClass, implode('', $rows), $this->eol); + + // wrap the snippet into a body when saving to file + if($saveToFile){ + $html = sprintf( + '%2$s%2$s%2$s'. + 'QR Code%2$s%1$s%2$s', + $html, + $this->eol + ); + } + + return $html; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php new file mode 100644 index 00000000..735c4180 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php @@ -0,0 +1,200 @@ + + * @copyright 2022 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use function array_chunk, implode, is_string, preg_match, sprintf, trim; + +/** + * SVG output + * + * @see https://github.com/codemasher/php-qrcode/pull/5 + * @see https://developer.mozilla.org/en-US/docs/Web/SVG + * @see https://www.sarasoueidan.com/demos/interactive-svg-coordinate-system/ + * @see https://lea.verou.me/blog/2019/05/utility-convert-svg-path-to-all-relative-or-all-absolute-commands/ + * @see https://codepen.io/leaverou/full/RmwzKv + * @see https://jakearchibald.github.io/svgomg/ + * @see https://web.archive.org/web/20200220211445/http://apex.infogridpacific.com/SVG/svg-tutorial-contents.html + */ +class QRMarkupSVG extends QRMarkup{ + + public const MIME_TYPE = 'image/svg+xml'; + + /** + * @todo: XSS proof + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill + * @inheritDoc + */ + public static function moduleValueIsValid($value):bool{ + + if(!is_string($value)){ + return false; + } + + $value = trim($value); + + // url(...) + if(preg_match('~^url\([-/#a-z\d]+\)$~i', $value)){ + return true; + } + + // otherwise check for standard css notation + return parent::moduleValueIsValid($value); + } + + /** + * @inheritDoc + */ + protected function getOutputDimensions():array{ + return [$this->moduleCount, $this->moduleCount]; + } + + /** + * @inheritDoc + */ + protected function getCssClass(int $M_TYPE = 0):string{ + return implode(' ', [ + 'qr-'.($this::LAYERNAMES[$M_TYPE] ?? $M_TYPE), + $this->matrix->isDark($M_TYPE) ? 'dark' : 'light', + $this->options->cssClass, + ]); + } + + /** + * @inheritDoc + */ + protected function createMarkup(bool $saveToFile):string{ + $svg = $this->header(); + + if(!empty($this->options->svgDefs)){ + $svg .= sprintf('%1$s%2$s%2$s', $this->options->svgDefs, $this->eol); + } + + $svg .= $this->paths(); + + // close svg + $svg .= sprintf('%1$s%1$s', $this->eol); + + // transform to data URI only when not saving to file + if(!$saveToFile && $this->options->outputBase64){ + $svg = $this->toBase64DataURI($svg); + } + + return $svg; + } + + /** + * returns the value for the SVG viewBox attribute + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox + * @see https://css-tricks.com/scale-svg/#article-header-id-3 + */ + protected function getViewBox():string{ + [$width, $height] = $this->getOutputDimensions(); + + return sprintf('0 0 %s %s', $width, $height); + } + + /** + * returns the header with the given options parsed + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg + */ + protected function header():string{ + + $header = sprintf( + '%4$s', + $this->options->cssClass, + $this->getViewBox(), + $this->options->svgPreserveAspectRatio, + $this->eol + ); + + if($this->options->svgAddXmlHeader){ + $header = sprintf('%s%s', $this->eol, $header); + } + + return $header; + } + + /** + * returns one or more SVG elements + */ + protected function paths():string{ + $paths = $this->collectModules(fn(int $x, int $y, int $M_TYPE):string => $this->module($x, $y, $M_TYPE)); + $svg = []; + + // create the path elements + foreach($paths as $M_TYPE => $modules){ + // limit the total line length + $chunks = array_chunk($modules, 100); + $chonks = []; + + foreach($chunks as $chunk){ + $chonks[] = implode(' ', $chunk); + } + + $path = implode($this->eol, $chonks); + + if(empty($path)){ + continue; + } + + $svg[] = $this->path($path, $M_TYPE); + } + + return implode($this->eol, $svg); + } + + /** + * renders and returns a single element + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path + */ + protected function path(string $path, int $M_TYPE):string{ + + if($this->options->svgUseFillAttributes){ + return sprintf( + '', + $this->getCssClass($M_TYPE), + $this->getModuleValue($M_TYPE), + $path + ); + } + + return sprintf('', $this->getCssClass($M_TYPE), $path); + } + + /** + * returns a path segment for a single module + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d + */ + protected function module(int $x, int $y, int $M_TYPE):string{ + + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ + return ''; + } + + if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){ + // string interpolation: ugly and fast + $ix = ($x + 0.5 - $this->circleRadius); + $iy = ($y + 0.5); + + // phpcs:ignore + return "M$ix $iy a$this->circleRadius $this->circleRadius 0 1 0 $this->circleDiameter 0 a$this->circleRadius $this->circleRadius 0 1 0 -$this->circleDiameter 0Z"; + } + + // phpcs:ignore + return "M$x $y h1 v1 h-1Z"; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php b/src/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php new file mode 100644 index 00000000..7ac64fff --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php @@ -0,0 +1,261 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use chillerlan\QRCode\Data\QRMatrix; +use chillerlan\Settings\SettingsContainerInterface; +use Closure; +use function base64_encode, dirname, file_put_contents, is_writable, ksort, sprintf; + +/** + * common output abstract + */ +abstract class QROutputAbstract implements QROutputInterface{ + + /** + * the current size of the QR matrix + * + * @see \chillerlan\QRCode\Data\QRMatrix::getSize() + */ + protected int $moduleCount; + + /** + * the side length of the QR image (modules * scale) + */ + protected int $length; + + /** + * an (optional) array of color values for the several QR matrix parts + */ + protected array $moduleValues; + + /** + * the (filled) data matrix object + */ + protected QRMatrix $matrix; + + /** + * @var \chillerlan\Settings\SettingsContainerInterface|\chillerlan\QRCode\QROptions + */ + protected SettingsContainerInterface $options; + + /** @see \chillerlan\QRCode\QROptions::$scale */ + protected int $scale; + /** @see \chillerlan\QRCode\QROptions::$connectPaths */ + protected bool $connectPaths; + /** @see \chillerlan\QRCode\QROptions::$excludeFromConnect */ + protected array $excludeFromConnect; + /** @see \chillerlan\QRCode\QROptions::$eol */ + protected string $eol; + /** @see \chillerlan\QRCode\QROptions::$drawLightModules */ + protected bool $drawLightModules; + /** @see \chillerlan\QRCode\QROptions::$drawCircularModules */ + protected bool $drawCircularModules; + /** @see \chillerlan\QRCode\QROptions::$keepAsSquare */ + protected array $keepAsSquare; + /** @see \chillerlan\QRCode\QROptions::$circleRadius */ + protected float $circleRadius; + protected float $circleDiameter; + + /** + * QROutputAbstract constructor. + */ + public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){ + $this->options = $options; + $this->matrix = $matrix; + + if($this->options->invertMatrix){ + $this->matrix->invert(); + } + + $this->copyVars(); + $this->setMatrixDimensions(); + $this->setModuleValues(); + } + + /** + * Creates copies of several QROptions values to avoid calling the magic getters + * in long loops for a significant performance increase. + * + * These variables are usually used in the "module" methods and are called up to 31329 times (at version 40). + */ + protected function copyVars():void{ + + $vars = [ + 'connectPaths', + 'excludeFromConnect', + 'eol', + 'drawLightModules', + 'drawCircularModules', + 'keepAsSquare', + 'circleRadius', + ]; + + foreach($vars as $property){ + $this->{$property} = $this->options->{$property}; + } + + $this->circleDiameter = ($this->circleRadius * 2); + } + + /** + * Sets/updates the matrix dimensions + * + * Call this method if you modify the matrix from within your custom module in case the dimensions have been changed + */ + protected function setMatrixDimensions():void{ + $this->moduleCount = $this->matrix->getSize(); + $this->scale = $this->options->scale; + $this->length = ($this->moduleCount * $this->scale); + } + + /** + * Returns a 2 element array with the current output width and height + * + * The type and units of the values depend on the output class. The default value is the current module count * scale. + */ + protected function getOutputDimensions():array{ + return [$this->length, $this->length]; + } + + /** + * Sets the initial module values + */ + protected function setModuleValues():void{ + + // first fill the map with the default values + foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){ + $this->moduleValues[$M_TYPE] = $this->getDefaultModuleValue($defaultValue); + } + + // now loop over the options values to replace defaults and add extra values + foreach($this->options->moduleValues as $M_TYPE => $value){ + if($this::moduleValueIsValid($value)){ + $this->moduleValues[$M_TYPE] = $this->prepareModuleValue($value); + } + } + + } + + /** + * Prepares the value for the given input (return value depends on the output class) + * + * @param mixed $value + * + * @return mixed|null + */ + abstract protected function prepareModuleValue($value); + + /** + * Returns a default value for either dark or light modules (return value depends on the output class) + * + * @return mixed|null + */ + abstract protected function getDefaultModuleValue(bool $isDark); + + /** + * Returns the prepared value for the given $M_TYPE + * + * @return mixed return value depends on the output class + * @throws \chillerlan\QRCode\Output\QRCodeOutputException if $moduleValues[$M_TYPE] doesn't exist + */ + protected function getModuleValue(int $M_TYPE){ + + if(!isset($this->moduleValues[$M_TYPE])){ + throw new QRCodeOutputException(sprintf('$M_TYPE %012b not found in module values map', $M_TYPE)); + } + + return $this->moduleValues[$M_TYPE]; + } + + /** + * Returns the prepared module value at the given coordinate [$x, $y] (convenience) + * + * @return mixed|null + */ + protected function getModuleValueAt(int $x, int $y){ + return $this->getModuleValue($this->matrix->get($x, $y)); + } + + /** + * Returns a base64 data URI for the given string and mime type + */ + protected function toBase64DataURI(string $data, string $mime = null):string{ + return sprintf('data:%s;base64,%s', ($mime ?? $this::MIME_TYPE), base64_encode($data)); + } + + /** + * Saves the qr $data to a $file. If $file is null, nothing happens. + * + * @see file_put_contents() + * @see \chillerlan\QRCode\QROptions::$cachefile + * + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + protected function saveToFile(string $data, string $file = null):void{ + + if($file === null){ + return; + } + + if(!is_writable(dirname($file))){ + throw new QRCodeOutputException(sprintf('Cannot write data to cache file: %s', $file)); + } + + if(file_put_contents($file, $data) === false){ + throw new QRCodeOutputException(sprintf('Cannot write data to cache file: %s (file_put_contents error)', $file)); + } + } + + /** + * collects the modules per QRMatrix::M_* type and runs a $transform function on each module and + * returns an array with the transformed modules + * + * The transform callback is called with the following parameters: + * + * $x - current column + * $y - current row + * $M_TYPE - field value + * $M_TYPE_LAYER - (possibly modified) field value that acts as layer id + */ + protected function collectModules(Closure $transform):array{ + $paths = []; + + // collect the modules for each type + foreach($this->matrix->getMatrix() as $y => $row){ + foreach($row as $x => $M_TYPE){ + $M_TYPE_LAYER = $M_TYPE; + + if($this->connectPaths && !$this->matrix->checkTypeIn($x, $y, $this->excludeFromConnect)){ + // to connect paths we'll redeclare the $M_TYPE_LAYER to data only + $M_TYPE_LAYER = QRMatrix::M_DATA; + + if($this->matrix->isDark($M_TYPE)){ + $M_TYPE_LAYER = QRMatrix::M_DATA_DARK; + } + } + + // collect the modules per $M_TYPE + $module = $transform($x, $y, $M_TYPE, $M_TYPE_LAYER); + + if(!empty($module)){ + $paths[$M_TYPE_LAYER][] = $module; + } + } + } + + // beautify output + ksort($paths); + + return $paths; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php b/src/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php new file mode 100644 index 00000000..919f1ba7 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php @@ -0,0 +1,226 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use chillerlan\QRCode\Data\QRMatrix; + +/** + * Converts the data matrix into readable output + */ +interface QROutputInterface{ + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const MARKUP_HTML = 'html'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const MARKUP_SVG = 'svg'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const GDIMAGE_BMP = 'bmp'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const GDIMAGE_GIF = 'gif'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const GDIMAGE_JPG = 'jpg'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const GDIMAGE_PNG = 'png'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const GDIMAGE_WEBP = 'webp'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const STRING_JSON = 'json'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const STRING_TEXT = 'text'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const IMAGICK = 'imagick'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const FPDF = 'fpdf'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const EPS = 'eps'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const CUSTOM = 'custom'; + + /** + * Map of built-in output modes => class FQN + * + * @var string[] + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const MODES = [ + self::MARKUP_SVG => QRMarkupSVG::class, + self::MARKUP_HTML => QRMarkupHTML::class, + self::GDIMAGE_BMP => QRGdImageBMP::class, + self::GDIMAGE_GIF => QRGdImageGIF::class, + self::GDIMAGE_JPG => QRGdImageJPEG::class, + self::GDIMAGE_PNG => QRGdImagePNG::class, + self::GDIMAGE_WEBP => QRGdImageWEBP::class, + self::STRING_JSON => QRStringJSON::class, + self::STRING_TEXT => QRStringText::class, + self::IMAGICK => QRImagick::class, + self::FPDF => QRFpdf::class, + self::EPS => QREps::class, + ]; + + /** + * Map of module type => default value + * + * @var bool[] + */ + public const DEFAULT_MODULE_VALUES = [ + // light + QRMatrix::M_NULL => false, + QRMatrix::M_DARKMODULE_LIGHT => false, + QRMatrix::M_DATA => false, + QRMatrix::M_FINDER => false, + QRMatrix::M_SEPARATOR => false, + QRMatrix::M_ALIGNMENT => false, + QRMatrix::M_TIMING => false, + QRMatrix::M_FORMAT => false, + QRMatrix::M_VERSION => false, + QRMatrix::M_QUIETZONE => false, + QRMatrix::M_LOGO => false, + QRMatrix::M_FINDER_DOT_LIGHT => false, + // dark + QRMatrix::M_DARKMODULE => true, + QRMatrix::M_DATA_DARK => true, + QRMatrix::M_FINDER_DARK => true, + QRMatrix::M_SEPARATOR_DARK => true, + QRMatrix::M_ALIGNMENT_DARK => true, + QRMatrix::M_TIMING_DARK => true, + QRMatrix::M_FORMAT_DARK => true, + QRMatrix::M_VERSION_DARK => true, + QRMatrix::M_QUIETZONE_DARK => true, + QRMatrix::M_LOGO_DARK => true, + QRMatrix::M_FINDER_DOT => true, + ]; + + /** + * Map of module type => readable name (for CSS etc.) + * + * @var string[] + */ + public const LAYERNAMES = [ + // light + QRMatrix::M_NULL => 'null', + QRMatrix::M_DARKMODULE_LIGHT => 'darkmodule-light', + QRMatrix::M_DATA => 'data', + QRMatrix::M_FINDER => 'finder', + QRMatrix::M_SEPARATOR => 'separator', + QRMatrix::M_ALIGNMENT => 'alignment', + QRMatrix::M_TIMING => 'timing', + QRMatrix::M_FORMAT => 'format', + QRMatrix::M_VERSION => 'version', + QRMatrix::M_QUIETZONE => 'quietzone', + QRMatrix::M_LOGO => 'logo', + QRMatrix::M_FINDER_DOT_LIGHT => 'finder-dot-light', + // dark + QRMatrix::M_DARKMODULE => 'darkmodule', + QRMatrix::M_DATA_DARK => 'data-dark', + QRMatrix::M_FINDER_DARK => 'finder-dark', + QRMatrix::M_SEPARATOR_DARK => 'separator-dark', + QRMatrix::M_ALIGNMENT_DARK => 'alignment-dark', + QRMatrix::M_TIMING_DARK => 'timing-dark', + QRMatrix::M_FORMAT_DARK => 'format-dark', + QRMatrix::M_VERSION_DARK => 'version-dark', + QRMatrix::M_QUIETZONE_DARK => 'quietzone-dark', + QRMatrix::M_LOGO_DARK => 'logo-dark', + QRMatrix::M_FINDER_DOT => 'finder-dot', + ]; + + /** + * @var string + * @see \chillerlan\QRCode\Output\QROutputAbstract::toBase64DataURI() + * @internal do not call this constant from the interface, but rather from one of the child classes + */ + public const MIME_TYPE = ''; + + /** + * Determines whether the given value is valid + * + * @param mixed $value + */ + public static function moduleValueIsValid($value):bool; + + /** + * Generates the output, optionally dumps it to a file, and returns it + * + * please note that the value of QROptions::$cachefile is already evaluated at this point. + * if the output module is invoked manually, it has no effect at all. + * you need to supply the $file parameter here in that case (or handle the option value in your custom output module). + * + * @see \chillerlan\QRCode\QRCode::renderMatrix() + * + * @return mixed + */ + public function dump(string $file = null); + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRString.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRString.php new file mode 100644 index 00000000..cb70a6cc --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRString.php @@ -0,0 +1,111 @@ + + * @copyright 2015 Smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function implode, is_string, json_encode, max, min, sprintf; +use const JSON_THROW_ON_ERROR; + +/** + * Converts the matrix data into string types + * + * @deprecated 5.0.0 this class will be removed in future versions, use one of QRStringText or QRStringJSON instead + */ +class QRString extends QROutputAbstract{ + + /** + * @inheritDoc + */ + public static function moduleValueIsValid($value):bool{ + return is_string($value); + } + + /** + * @inheritDoc + */ + protected function prepareModuleValue($value):string{ + return $value; + } + + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):string{ + return ($isDark) ? '██' : '░░'; + } + + /** + * @inheritDoc + */ + public function dump(string $file = null):string{ + + switch($this->options->outputType){ + case QROutputInterface::STRING_TEXT: + $data = $this->text(); + break; + case QROutputInterface::STRING_JSON: + default: + $data = $this->json(); + } + + $this->saveToFile($data, $file); + + return $data; + } + + /** + * string output + */ + protected function text():string{ + $lines = []; + $linestart = $this->options->textLineStart; + + for($y = 0; $y < $this->moduleCount; $y++){ + $r = []; + + for($x = 0; $x < $this->moduleCount; $x++){ + $r[] = $this->getModuleValueAt($x, $y); + } + + $lines[] = $linestart.implode('', $r); + } + + return implode($this->eol, $lines); + } + + /** + * JSON output + * + * @throws \JsonException + */ + protected function json():string{ + return json_encode($this->matrix->getMatrix($this->options->jsonAsBooleans), JSON_THROW_ON_ERROR); + } + + // + + /** + * a little helper to create a proper ANSI 8-bit color escape sequence + * + * @see https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit + * @see https://en.wikipedia.org/wiki/Block_Elements + * + * @codeCoverageIgnore + */ + public static function ansi8(string $str, int $color, bool $background = null):string{ + $color = max(0, min($color, 255)); + $background = ($background === true) ? 48 : 38; + + return sprintf("\x1b[%s;5;%sm%s\x1b[0m", $background, $color, $str); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php new file mode 100644 index 00000000..6f2e7d5e --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php @@ -0,0 +1,67 @@ + + * @copyright 2023 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function json_encode; + +/** + * + */ +class QRStringJSON extends QROutputAbstract{ + + public const MIME_TYPE = 'application/json'; + + /** + * @inheritDoc + * @throws \JsonException + */ + public function dump(string $file = null):string{ + $matrix = $this->matrix->getMatrix($this->options->jsonAsBooleans); + $data = json_encode($matrix, $this->options->jsonFlags);; + + $this->saveToFile($data, $file); + + return $data; + } + + /** + * unused - required by interface + * + * @inheritDoc + * @codeCoverageIgnore + */ + protected function prepareModuleValue($value):string{ + return ''; + } + + /** + * unused - required by interface + * + * @inheritDoc + * @codeCoverageIgnore + */ + protected function getDefaultModuleValue(bool $isDark):string{ + return ''; + } + + /** + * unused - required by interface + * + * @inheritDoc + * @codeCoverageIgnore + */ + public static function moduleValueIsValid($value):bool{ + return true; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRStringText.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRStringText.php new file mode 100644 index 00000000..f475a2a6 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRStringText.php @@ -0,0 +1,76 @@ + + * @copyright 2023 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use function array_map, implode, is_string, max, min, sprintf; + +/** + * + */ +class QRStringText extends QROutputAbstract{ + + public const MIME_TYPE = 'text/plain'; + + /** + * @inheritDoc + */ + public static function moduleValueIsValid($value):bool{ + return is_string($value); + } + + /** + * @inheritDoc + */ + protected function prepareModuleValue($value):string{ + return $value; + } + + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):string{ + return ($isDark) ? '██' : '░░'; + } + + /** + * @inheritDoc + */ + public function dump(string $file = null):string{ + $lines = []; + $linestart = $this->options->textLineStart; + + foreach($this->matrix->getMatrix() as $row){ + $lines[] = $linestart.implode('', array_map([$this, 'getModuleValue'], $row)); + } + + $data = implode($this->eol, $lines); + + $this->saveToFile($data, $file); + + return $data; + } + + /** + * a little helper to create a proper ANSI 8-bit color escape sequence + * + * @see https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit + * @see https://en.wikipedia.org/wiki/Block_Elements + * + * @codeCoverageIgnore + */ + public static function ansi8(string $str, int $color, bool $background = null):string{ + $color = max(0, min($color, 255)); + $background = ($background === true) ? 48 : 38; + + return sprintf("\x1b[%s;5;%sm%s\x1b[0m", $background, $color, $str); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/QRCode.php b/src/vendor/chillerlan/php-qrcode/src/QRCode.php new file mode 100644 index 00000000..bca6ac58 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/QRCode.php @@ -0,0 +1,488 @@ + + * @copyright 2015 Smiley + * @license MIT + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ + +namespace chillerlan\QRCode; + +use chillerlan\QRCode\Common\{ + EccLevel, ECICharset, GDLuminanceSource, IMagickLuminanceSource, LuminanceSourceInterface, MaskPattern, Mode, Version +}; +use chillerlan\QRCode\Data\{AlphaNum, Byte, ECI, Hanzi, Kanji, Number, QRData, QRDataModeInterface, QRMatrix}; +use chillerlan\QRCode\Decoder\{Decoder, DecoderResult}; +use chillerlan\QRCode\Output\{QRCodeOutputException, QROutputInterface}; +use chillerlan\Settings\SettingsContainerInterface; +use function class_exists, class_implements, in_array, mb_convert_encoding, mb_internal_encoding; + +/** + * Turns a text string into a Model 2 QR Code + * + * @see https://github.com/kazuhikoarase/qrcode-generator/tree/master/php + * @see https://www.qrcode.com/en/codes/model12.html + * @see https://www.swisseduc.ch/informatik/theoretische_informatik/qr_codes/docs/qr_standard.pdf + * @see https://en.wikipedia.org/wiki/QR_code + * @see https://www.thonky.com/qr-code-tutorial/ + */ +class QRCode{ + + /** + * @deprecated 5.0.0 use Version::AUTO instead + * @see \chillerlan\QRCode\Common\Version::AUTO + * @var int + */ + public const VERSION_AUTO = Version::AUTO; + + /** + * @deprecated 5.0.0 use MaskPattern::AUTO instead + * @see \chillerlan\QRCode\Common\MaskPattern::AUTO + * @var int + */ + public const MASK_PATTERN_AUTO = MaskPattern::AUTO; + + /** + * @deprecated 5.0.0 use EccLevel::L instead + * @see \chillerlan\QRCode\Common\EccLevel::L + * @var int + */ + public const ECC_L = EccLevel::L; + + /** + * @deprecated 5.0.0 use EccLevel::M instead + * @see \chillerlan\QRCode\Common\EccLevel::M + * @var int + */ + public const ECC_M = EccLevel::M; + + /** + * @deprecated 5.0.0 use EccLevel::Q instead + * @see \chillerlan\QRCode\Common\EccLevel::Q + * @var int + */ + public const ECC_Q = EccLevel::Q; + + /** + * @deprecated 5.0.0 use EccLevel::H instead + * @see \chillerlan\QRCode\Common\EccLevel::H + * @var int + */ + public const ECC_H = EccLevel::H; + + /** + * @deprecated 5.0.0 use QROutputInterface::MARKUP_HTML instead + * @see \chillerlan\QRCode\Output\QROutputInterface::MARKUP_HTML + * @var string + */ + public const OUTPUT_MARKUP_HTML = QROutputInterface::MARKUP_HTML; + + /** + * @deprecated 5.0.0 use QROutputInterface::MARKUP_SVG instead + * @see \chillerlan\QRCode\Output\QROutputInterface::MARKUP_SVG + * @var string + */ + public const OUTPUT_MARKUP_SVG = QROutputInterface::MARKUP_SVG; + + /** + * @deprecated 5.0.0 use QROutputInterface::GDIMAGE_PNG instead + * @see \chillerlan\QRCode\Output\QROutputInterface::GDIMAGE_PNG + * @var string + */ + public const OUTPUT_IMAGE_PNG = QROutputInterface::GDIMAGE_PNG; + + /** + * @deprecated 5.0.0 use QROutputInterface::GDIMAGE_JPG instead + * @see \chillerlan\QRCode\Output\QROutputInterface::GDIMAGE_JPG + * @var string + */ + public const OUTPUT_IMAGE_JPG = QROutputInterface::GDIMAGE_JPG; + + /** + * @deprecated 5.0.0 use QROutputInterface::GDIMAGE_GIF instead + * @see \chillerlan\QRCode\Output\QROutputInterface::GDIMAGE_GIF + * @var string + */ + public const OUTPUT_IMAGE_GIF = QROutputInterface::GDIMAGE_GIF; + + /** + * @deprecated 5.0.0 use QROutputInterface::STRING_JSON instead + * @see \chillerlan\QRCode\Output\QROutputInterface::STRING_JSON + * @var string + */ + public const OUTPUT_STRING_JSON = QROutputInterface::STRING_JSON; + + /** + * @deprecated 5.0.0 use QROutputInterface::STRING_TEXT instead + * @see \chillerlan\QRCode\Output\QROutputInterface::STRING_TEXT + * @var string + */ + public const OUTPUT_STRING_TEXT = QROutputInterface::STRING_TEXT; + + /** + * @deprecated 5.0.0 use QROutputInterface::IMAGICK instead + * @see \chillerlan\QRCode\Output\QROutputInterface::IMAGICK + * @var string + */ + public const OUTPUT_IMAGICK = QROutputInterface::IMAGICK; + + /** + * @deprecated 5.0.0 use QROutputInterface::FPDF instead + * @see \chillerlan\QRCode\Output\QROutputInterface::FPDF + * @var string + */ + public const OUTPUT_FPDF = QROutputInterface::FPDF; + + /** + * @deprecated 5.0.0 use QROutputInterface::EPS instead + * @see \chillerlan\QRCode\Output\QROutputInterface::EPS + * @var string + */ + public const OUTPUT_EPS = QROutputInterface::EPS; + + /** + * @deprecated 5.0.0 use QROutputInterface::CUSTOM instead + * @see \chillerlan\QRCode\Output\QROutputInterface::CUSTOM + * @var string + */ + public const OUTPUT_CUSTOM = QROutputInterface::CUSTOM; + + /** + * @deprecated 5.0.0 use QROutputInterface::MODES instead + * @see \chillerlan\QRCode\Output\QROutputInterface::MODES + * @var string[] + */ + public const OUTPUT_MODES = QROutputInterface::MODES; + + /** + * The settings container + * + * @var \chillerlan\QRCode\QROptions|\chillerlan\Settings\SettingsContainerInterface + */ + protected SettingsContainerInterface $options; + + /** + * A collection of one or more data segments of QRDataModeInterface instances to write + * + * @var \chillerlan\QRCode\Data\QRDataModeInterface[] + */ + protected array $dataSegments = []; + + /** + * The luminance source for the reader + */ + protected string $luminanceSourceFQN = GDLuminanceSource::class; + + /** + * QRCode constructor. + * + * PHP8: accept iterable + */ + public function __construct(SettingsContainerInterface $options = null){ + $this->setOptions(($options ?? new QROptions)); + } + + /** + * Sets an options instance + */ + public function setOptions(SettingsContainerInterface $options):self{ + $this->options = $options; + + if($this->options->readerUseImagickIfAvailable){ + $this->luminanceSourceFQN = IMagickLuminanceSource::class; + } + + return $this; + } + + /** + * Renders a QR Code for the given $data and QROptions, saves $file optionally + * + * Note: it is possible to add several data segments before calling this method with a valid $data string + * which will result in a mixed-mode QR Code with the given parameter as last element. + * + * @see https://github.com/chillerlan/php-qrcode/issues/246 + * + * @return mixed + */ + public function render(string $data = null, string $file = null){ + + if($data !== null){ + /** @var \chillerlan\QRCode\Data\QRDataModeInterface $dataInterface */ + foreach(Mode::INTERFACES as $dataInterface){ + + if($dataInterface::validateString($data)){ + $this->addSegment(new $dataInterface($data)); + + break; + } + } + } + + return $this->renderMatrix($this->getQRMatrix(), $file); + } + + /** + * Renders a QR Code for the given QRMatrix and QROptions, saves $file optionally + * + * @return mixed + */ + public function renderMatrix(QRMatrix $matrix, string $file = null){ + return $this->initOutputInterface($matrix)->dump($file ?? $this->options->cachefile); + } + + /** + * Returns a QRMatrix object for the given $data and current QROptions + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function getQRMatrix():QRMatrix{ + $matrix = (new QRData($this->options, $this->dataSegments))->writeMatrix(); + + $maskPattern = $this->options->maskPattern === MaskPattern::AUTO + ? MaskPattern::getBestPattern($matrix) + : new MaskPattern($this->options->maskPattern); + + $matrix->setFormatInfo($maskPattern)->mask($maskPattern); + + return $this->addMatrixModifications($matrix); + } + + /** + * add matrix modifications after mask pattern evaluation and before handing over to output + */ + protected function addMatrixModifications(QRMatrix $matrix):QRMatrix{ + + if($this->options->addLogoSpace){ + // check whether one of the dimensions was omitted + $logoSpaceWidth = ($this->options->logoSpaceWidth ?? $this->options->logoSpaceHeight ?? 0); + $logoSpaceHeight = ($this->options->logoSpaceHeight ?? $logoSpaceWidth); + + $matrix->setLogoSpace( + $logoSpaceWidth, + $logoSpaceHeight, + $this->options->logoSpaceStartX, + $this->options->logoSpaceStartY + ); + } + + if($this->options->addQuietzone){ + $matrix->setQuietZone($this->options->quietzoneSize); + } + + return $matrix; + } + + /** + * @deprecated 5.0.0 use QRCode::getQRMatrix() instead + * @see \chillerlan\QRCode\QRCode::getQRMatrix() + * @codeCoverageIgnore + */ + public function getMatrix():QRMatrix{ + return $this->getQRMatrix(); + } + + /** + * initializes a fresh built-in or custom QROutputInterface + * + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + protected function initOutputInterface(QRMatrix $matrix):QROutputInterface{ + // @todo: remove custom invocation in v6 + $outputInterface = (QROutputInterface::MODES[$this->options->outputType] ?? null); + + if($this->options->outputType === QROutputInterface::CUSTOM){ + $outputInterface = $this->options->outputInterface; + } + + if(!$outputInterface || !class_exists($outputInterface)){ + throw new QRCodeOutputException('invalid output module'); + } + + if(!in_array(QROutputInterface::class, class_implements($outputInterface))){ + throw new QRCodeOutputException('output module does not implement QROutputInterface'); + } + + return new $outputInterface($this->options, $matrix); + } + + /** + * checks if a string qualifies as numeric (convenience method) + * + * @deprecated 5.0.0 use Number::validateString() instead + * @see \chillerlan\QRCode\Data\Number::validateString() + * @codeCoverageIgnore + */ + public function isNumber(string $string):bool{ + return Number::validateString($string); + } + + /** + * checks if a string qualifies as alphanumeric (convenience method) + * + * @deprecated 5.0.0 use AlphaNum::validateString() instead + * @see \chillerlan\QRCode\Data\AlphaNum::validateString() + * @codeCoverageIgnore + */ + public function isAlphaNum(string $string):bool{ + return AlphaNum::validateString($string); + } + + /** + * checks if a string qualifies as Kanji (convenience method) + * + * @deprecated 5.0.0 use Kanji::validateString() instead + * @see \chillerlan\QRCode\Data\Kanji::validateString() + * @codeCoverageIgnore + */ + public function isKanji(string $string):bool{ + return Kanji::validateString($string); + } + + /** + * a dummy (convenience method) + * + * @deprecated 5.0.0 use Byte::validateString() instead + * @see \chillerlan\QRCode\Data\Byte::validateString() + * @codeCoverageIgnore + */ + public function isByte(string $string):bool{ + return Byte::validateString($string); + } + + /** + * Adds a data segment + * + * ISO/IEC 18004:2000 8.3.6 - Mixing modes + * ISO/IEC 18004:2000 Annex H - Optimisation of bit stream length + */ + public function addSegment(QRDataModeInterface $segment):self{ + $this->dataSegments[] = $segment; + + return $this; + } + + /** + * Clears the data segments array + * + * @codeCoverageIgnore + */ + public function clearSegments():self{ + $this->dataSegments = []; + + return $this; + } + + /** + * Adds a numeric data segment + * + * ISO/IEC 18004:2000 8.3.2 - Numeric Mode + */ + public function addNumericSegment(string $data):self{ + return $this->addSegment(new Number($data)); + } + + /** + * Adds an alphanumeric data segment + * + * ISO/IEC 18004:2000 8.3.3 - Alphanumeric Mode + */ + public function addAlphaNumSegment(string $data):self{ + return $this->addSegment(new AlphaNum($data)); + } + + /** + * Adds a Kanji data segment (Japanese 13-bit double-byte characters, Shift-JIS) + * + * ISO/IEC 18004:2000 8.3.5 - Kanji Mode + */ + public function addKanjiSegment(string $data):self{ + return $this->addSegment(new Kanji($data)); + } + + /** + * Adds a Hanzi data segment (simplified Chinese 13-bit double-byte characters, GB2312/GB18030) + * + * GBT18284-2000 Hanzi Mode + */ + public function addHanziSegment(string $data):self{ + return $this->addSegment(new Hanzi($data)); + } + + /** + * Adds an 8-bit byte data segment + * + * ISO/IEC 18004:2000 8.3.4 - 8-bit Byte Mode + */ + public function addByteSegment(string $data):self{ + return $this->addSegment(new Byte($data)); + } + + /** + * Adds a standalone ECI designator + * + * The ECI designator must be followed by a Byte segment that contains the string encoded according to the given ECI charset + * + * ISO/IEC 18004:2000 8.3.1 - Extended Channel Interpretation (ECI) Mode + */ + public function addEciDesignator(int $encoding):self{ + return $this->addSegment(new ECI($encoding)); + } + + /** + * Adds an ECI data segment (including designator) + * + * The given string will be encoded from mb_internal_encoding() to the given ECI character set + * + * I hate this somehow, but I'll leave it for now + * + * @throws \chillerlan\QRCode\QRCodeException + */ + public function addEciSegment(int $encoding, string $data):self{ + // validate the encoding id + $eciCharset = new ECICharset($encoding); + // get charset name + $eciCharsetName = $eciCharset->getName(); + // convert the string to the given charset + if($eciCharsetName !== null){ + $data = mb_convert_encoding($data, $eciCharsetName, mb_internal_encoding()); + + return $this + ->addEciDesignator($eciCharset->getID()) + ->addByteSegment($data) + ; + } + + throw new QRCodeException('unable to add ECI segment'); + } + + /** + * Reads a QR Code from a given file + * + * @noinspection PhpUndefinedMethodInspection + */ + public function readFromFile(string $path):DecoderResult{ + return $this->readFromSource($this->luminanceSourceFQN::fromFile($path, $this->options)); + } + + /** + * Reads a QR Code from the given data blob + * + * @noinspection PhpUndefinedMethodInspection + */ + public function readFromBlob(string $blob):DecoderResult{ + return $this->readFromSource($this->luminanceSourceFQN::fromBlob($blob, $this->options)); + } + + /** + * Reads a QR Code from the given luminance source + */ + public function readFromSource(LuminanceSourceInterface $source):DecoderResult{ + return (new Decoder)->decode($source); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/QRCodeException.php b/src/vendor/chillerlan/php-qrcode/src/QRCodeException.php new file mode 100644 index 00000000..600ce50e --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/QRCodeException.php @@ -0,0 +1,20 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode; + +use Exception; + +/** + * An exception container + */ +class QRCodeException extends Exception{ + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/QROptions.php b/src/vendor/chillerlan/php-qrcode/src/QROptions.php new file mode 100644 index 00000000..91b5b456 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/QROptions.php @@ -0,0 +1,20 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode; + +use chillerlan\Settings\SettingsContainerAbstract; + +/** + * The QRCode settings container + */ +class QROptions extends SettingsContainerAbstract{ + use QROptionsTrait; +} diff --git a/src/vendor/chillerlan/php-qrcode/src/QROptionsTrait.php b/src/vendor/chillerlan/php-qrcode/src/QROptionsTrait.php new file mode 100644 index 00000000..d2bc8c2c --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/QROptionsTrait.php @@ -0,0 +1,729 @@ + + * @copyright 2018 smiley + * @license MIT + * + * @noinspection PhpUnused, PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode; + +use chillerlan\QRCode\Output\QROutputInterface; +use chillerlan\QRCode\Common\{EccLevel, MaskPattern, Version}; +use function extension_loaded, in_array, max, min, strtolower; +use const JSON_THROW_ON_ERROR, PHP_EOL; + +/** + * The QRCode plug-in settings & setter functionality + */ +trait QROptionsTrait{ + + /* + * QR Code specific settings + */ + + /** + * QR Code version number + * + * `1 ... 40` or `Version::AUTO` (default) + * + * @see \chillerlan\QRCode\Common\Version + */ + protected int $version = Version::AUTO; + + /** + * Minimum QR version + * + * if `QROptions::$version` is set to `Version::AUTO` (default: 1) + */ + protected int $versionMin = 1; + + /** + * Maximum QR version + * + * if `QROptions::$version` is set to `Version::AUTO` (default: 40) + */ + protected int $versionMax = 40; + + /** + * Error correct level + * + * `EccLevel::X` where `X` is: + * + * - `L` => 7% (default) + * - `M` => 15% + * - `Q` => 25% + * - `H` => 30% + * + * @todo: accept string values (PHP8+) + * @see \chillerlan\QRCode\Common\EccLevel + * @see https://github.com/chillerlan/php-qrcode/discussions/160 + */ + protected int $eccLevel = EccLevel::L; + + /** + * Mask Pattern to use (no value in using, mostly for unit testing purposes) + * + * `0 ... 7` or `MaskPattern::PATTERN_AUTO` (default) + * + * @see \chillerlan\QRCode\Common\MaskPattern + */ + protected int $maskPattern = MaskPattern::AUTO; + + /** + * Add a "quiet zone" (margin) according to the QR code spec + * + * @see https://www.qrcode.com/en/howto/code.html + */ + protected bool $addQuietzone = true; + + /** + * Size of the quiet zone + * + * internally clamped to `0 ... $moduleCount / 2` (default: 4) + */ + protected int $quietzoneSize = 4; + + + /* + * General output settings + */ + + /** + * The built-in output type + * + * - `QROutputInterface::MARKUP_SVG` (default) + * - `QROutputInterface::MARKUP_HTML` + * - `QROutputInterface::GDIMAGE_BMP` + * - `QROutputInterface::GDIMAGE_GIF` + * - `QROutputInterface::GDIMAGE_JPG` + * - `QROutputInterface::GDIMAGE_PNG` + * - `QROutputInterface::GDIMAGE_WEBP` + * - `QROutputInterface::STRING_TEXT` + * - `QROutputInterface::STRING_JSON` + * - `QROutputInterface::IMAGICK` + * - `QROutputInterface::EPS` + * - `QROutputInterface::FPDF` + * - `QROutputInterface::CUSTOM` + * + * @see \chillerlan\QRCode\Output\QREps + * @see \chillerlan\QRCode\Output\QRFpdf + * @see \chillerlan\QRCode\Output\QRGdImage + * @see \chillerlan\QRCode\Output\QRImagick + * @see \chillerlan\QRCode\Output\QRMarkupHTML + * @see \chillerlan\QRCode\Output\QRMarkupSVG + * @see \chillerlan\QRCode\Output\QRString + * @see https://github.com/chillerlan/php-qrcode/issues/223 + * + * @deprecated 5.0.0 see issue #223 + */ + protected string $outputType = QROutputInterface::MARKUP_SVG; + + /** + * The FQCN of the custom `QROutputInterface` + * + * if `QROptions::$outputType` is set to `QROutputInterface::CUSTOM` (default: `null`) + * + * @deprecated 5.0.0 the nullable type will be removed in future versions + * and the default value will be set to `QRMarkupSVG::class` + */ + protected ?string $outputInterface = null; + + /** + * Return the image resource instead of a render if applicable. + * + * - `QRGdImage`: `resource` (PHP < 8), `GdImage` + * - `QRImagick`: `Imagick` + * - `QRFpdf`: `FPDF` + * + * This option overrides/ignores other output settings, such as `QROptions::$cachefile` + * and `QROptions::$outputBase64`. (default: `false`) + * + * @see \chillerlan\QRCode\Output\QROutputInterface::dump() + */ + protected bool $returnResource = false; + + /** + * Optional cache file path `/path/to/cache.file` + * + * Please note that the `$file` parameter in `QRCode::render()` and `QRCode::renderMatrix()` + * takes precedence over the `QROptions::$cachefile` value. (default: `null`) + * + * @see \chillerlan\QRCode\QRCode::render() + * @see \chillerlan\QRCode\QRCode::renderMatrix() + */ + protected ?string $cachefile = null; + + /** + * Toggle base64 data URI or raw data output (if applicable) + * + * (default: `true`) + * + * @see \chillerlan\QRCode\Output\QROutputAbstract::toBase64DataURI() + */ + protected bool $outputBase64 = true; + + /** + * Newline string + * + * (default: `PHP_EOL`) + */ + protected string $eol = PHP_EOL; + + /* + * Common visual modifications + */ + + /** + * Sets the image background color (if applicable) + * + * - `QRImagick`: defaults to `"white"` + * - `QRGdImage`: defaults to `[255, 255, 255]` + * - `QRFpdf`: defaults to blank internally (white page) + * + * @var mixed|null + */ + protected $bgColor = null; + + /** + * Whether to invert the matrix (reflectance reversal) + * + * (default: `false`) + * + * @see \chillerlan\QRCode\Data\QRMatrix::invert() + */ + protected bool $invertMatrix = false; + + /** + * Whether to draw the light (false) modules + * + * (default: `true`) + */ + protected bool $drawLightModules = true; + + /** + * Specify whether to draw the modules as filled circles + * + * a note for `GdImage` output: + * + * if `QROptions::$scale` is less than 20, the image will be upscaled internally, then the modules will be drawn + * using `imagefilledellipse()` and then scaled back to the expected size + * + * No effect in: `QREps`, `QRFpdf`, `QRMarkupHTML` + * + * @see \imagefilledellipse() + * @see https://github.com/chillerlan/php-qrcode/issues/23 + * @see https://github.com/chillerlan/php-qrcode/discussions/122 + */ + protected bool $drawCircularModules = false; + + /** + * Specifies the radius of the modules when `QROptions::$drawCircularModules` is set to `true` + * + * (default: 0.45) + */ + protected float $circleRadius = 0.45; + + /** + * Specifies which module types to exclude when `QROptions::$drawCircularModules` is set to `true` + * + * (default: `[]`) + */ + protected array $keepAsSquare = []; + + /** + * Whether to connect the paths for the several module types to avoid weird glitches when using gradients etc. + * + * This option is exclusive to output classes that use the module collector `QROutputAbstract::collectModules()`, + * which converts the `$M_TYPE` of all modules to `QRMatrix::M_DATA` and `QRMatrix::M_DATA_DARK` respectively. + * + * Module types that should not be added to the connected path can be excluded via `QROptions::$excludeFromConnect`. + * + * Currentty used in `QREps` and `QRMarkupSVG`. + * + * @see \chillerlan\QRCode\Output\QROutputAbstract::collectModules() + * @see \chillerlan\QRCode\QROptionsTrait::$excludeFromConnect + * @see https://github.com/chillerlan/php-qrcode/issues/57 + */ + protected bool $connectPaths = false; + + /** + * Specify which paths/patterns to exclude from connecting if `QROptions::$connectPaths` is set to `true` + * + * @see \chillerlan\QRCode\QROptionsTrait::$connectPaths + */ + protected array $excludeFromConnect = []; + + /** + * Module values map + * + * - `QRImagick`, `QRMarkupHTML`, `QRMarkupSVG`: #ABCDEF, cssname, rgb(), rgba()... + * - `QREps`, `QRFpdf`, `QRGdImage`: `[R, G, B]` // 0-255 + * - `QREps`: `[C, M, Y, K]` // 0-255 + * + * @see \chillerlan\QRCode\Output\QROutputAbstract::setModuleValues() + */ + protected array $moduleValues = []; + + /** + * Toggles logo space creation + * + * @see \chillerlan\QRCode\QRCode::addMatrixModifications() + * @see \chillerlan\QRCode\Data\QRMatrix::setLogoSpace() + */ + protected bool $addLogoSpace = false; + + /** + * Width of the logo space + * + * if only `QROptions::$logoSpaceWidth` is given, the logo space is assumed a square of that size + */ + protected ?int $logoSpaceWidth = null; + + /** + * Height of the logo space + * + * if only `QROptions::$logoSpaceHeight` is given, the logo space is assumed a square of that size + */ + protected ?int $logoSpaceHeight = null; + + /** + * Optional horizontal start position of the logo space (top left corner) + */ + protected ?int $logoSpaceStartX = null; + + /** + * Optional vertical start position of the logo space (top left corner) + */ + protected ?int $logoSpaceStartY = null; + + + /* + * Common raster image settings (QRGdImage, QRImagick) + */ + + /** + * Pixel size of a QR code module + */ + protected int $scale = 5; + + /** + * Toggle transparency + * + * - `QRGdImage` and `QRImagick`: the given `QROptions::$transparencyColor` is set as transparent + * + * @see https://github.com/chillerlan/php-qrcode/discussions/121 + */ + protected bool $imageTransparent = false; + + /** + * Sets a transparency color for when `QROptions::$imageTransparent` is set to `true`. + * + * Defaults to `QROptions::$bgColor`. + * + * - `QRGdImage`: `[R, G, B]`, this color is set as transparent in `imagecolortransparent()` + * - `QRImagick`: `"color_str"`, this color is set in `Imagick::transparentPaintImage()` + * + * @see \imagecolortransparent() + * @see \Imagick::transparentPaintImage() + * + * @var mixed|null + */ + protected $transparencyColor = null; + + /** + * Compression quality + * + * The given value depends on the used output type: + * + * - `QRGdImageBMP`: `[0...1]` + * - `QRGdImageJPEG`: `[0...100]` + * - `QRGdImageWEBP`: `[0...9]` + * - `QRGdImagePNG`: `[0...100]` + * - `QRImagick`: `[0...100]` + * + * @see \imagebmp() + * @see \imagejpeg() + * @see \imagepng() + * @see \imagewebp() + * @see \Imagick::setImageCompressionQuality() + */ + protected int $quality = -1; + + /* + * QRGdImage settings + */ + + /** + * Toggles the usage of internal upscaling when `QROptions::$drawCircularModules` is set to `true` and + * `QROptions::$scale` is less than 20 + * + * @see \chillerlan\QRCode\Output\QRGdImage::createImage() + * @see https://github.com/chillerlan/php-qrcode/issues/23 + */ + protected bool $gdImageUseUpscale = true; + + /* + * QRImagick settings + */ + + /** + * Imagick output format + * + * @see \Imagick::setImageFormat() + * @see https://www.imagemagick.org/script/formats.php + */ + protected string $imagickFormat = 'png32'; + + + /* + * Common markup output settings (QRMarkupSVG, QRMarkupHTML) + */ + + /** + * A common css class + */ + protected string $cssClass = 'qrcode'; + + /* + * QRMarkupSVG settings + */ + + /** + * Whether to add an XML header line or not, e.g. to embed the SVG directly in HTML + * + * `` + */ + protected bool $svgAddXmlHeader = true; + + /** + * Anything in the SVG `` tag + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs + */ + protected string $svgDefs = ''; + + /** + * Sets the value for the "preserveAspectRatio" on the `` element + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio + */ + protected string $svgPreserveAspectRatio = 'xMidYMid'; + + /** + * Whether to use the SVG `fill` attributes + * + * If set to `true` (default), the `fill` attribute will be set with the module value for the `` element's `$M_TYPE`. + * When set to `false`, the module values map will be ignored and the QR Code may be styled via CSS. + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill + */ + protected bool $svgUseFillAttributes = true; + + /* + * QRStringText settings + */ + + /** + * An optional line prefix, e.g. empty space to align the QR Code in a console + */ + protected string $textLineStart = ''; + + /* + * QRStringJSON settings + */ + + /** + * Sets the flags to use for the `json_encode()` call + * + * @see https://www.php.net/manual/json.constants.php + */ + protected int $jsonFlags = JSON_THROW_ON_ERROR; + + /** + * Whether to return matrix values in JSON as booleans or `$M_TYPE` integers + */ + protected bool $jsonAsBooleans = false; + + /* + * QRFpdf settings + */ + + /** + * Measurement unit for `FPDF` output: `pt`, `mm`, `cm`, `in` (default: `pt`) + * + * @see FPDF::__construct() + */ + protected string $fpdfMeasureUnit = 'pt'; + + + /* + * QR Code reader settings + */ + + /** + * Use Imagick (if available) when reading QR Codes + */ + protected bool $readerUseImagickIfAvailable = false; + + /** + * Grayscale the image before reading + */ + protected bool $readerGrayscale = false; + + /** + * Invert the colors of the image + */ + protected bool $readerInvertColors = false; + + /** + * Increase the contrast before reading + * + * note that applying contrast works different in GD and Imagick, so mileage may vary + */ + protected bool $readerIncreaseContrast = false; + + + /** + * clamp min/max version number + */ + protected function setMinMaxVersion(int $versionMin, int $versionMax):void{ + $min = max(1, min(40, $versionMin)); + $max = max(1, min(40, $versionMax)); + + $this->versionMin = min($min, $max); + $this->versionMax = max($min, $max); + } + + /** + * sets the minimum version number + */ + protected function set_versionMin(int $version):void{ + $this->setMinMaxVersion($version, $this->versionMax); + } + + /** + * sets the maximum version number + */ + protected function set_versionMax(int $version):void{ + $this->setMinMaxVersion($this->versionMin, $version); + } + + /** + * sets/clamps the version number + */ + protected function set_version(int $version):void{ + $this->version = ($version !== Version::AUTO) ? max(1, min(40, $version)) : Version::AUTO; + } + + /** + * sets/clamps the quiet zone size + */ + protected function set_quietzoneSize(int $quietzoneSize):void{ + $this->quietzoneSize = max(0, min($quietzoneSize, 75)); + } + + /** + * sets the FPDF measurement unit + * + * @codeCoverageIgnore + */ + protected function set_fpdfMeasureUnit(string $unit):void{ + $unit = strtolower($unit); + + if(in_array($unit, ['cm', 'in', 'mm', 'pt'], true)){ + $this->fpdfMeasureUnit = $unit; + } + + // @todo throw or ignore silently? + } + + /** + * enables Imagick for the QR Code reader if the extension is available + */ + protected function set_readerUseImagickIfAvailable(bool $useImagickIfAvailable):void{ + $this->readerUseImagickIfAvailable = ($useImagickIfAvailable && extension_loaded('imagick')); + } + + /** + * clamp the logo space values between 0 and maximum length (177 modules at version 40) + */ + protected function clampLogoSpaceValue(?int $value):?int{ + + if($value === null){ + return null; + } + + return (int)max(0, min(177, $value)); + } + + /** + * clamp/set logo space width + */ + protected function set_logoSpaceWidth(?int $value):void{ + $this->logoSpaceWidth = $this->clampLogoSpaceValue($value); + } + + /** + * clamp/set logo space height + */ + protected function set_logoSpaceHeight(?int $value):void{ + $this->logoSpaceHeight = $this->clampLogoSpaceValue($value); + } + + /** + * clamp/set horizontal logo space start + */ + protected function set_logoSpaceStartX(?int $value):void{ + $this->logoSpaceStartX = $this->clampLogoSpaceValue($value); + } + + /** + * clamp/set vertical logo space start + */ + protected function set_logoSpaceStartY(?int $value):void{ + $this->logoSpaceStartY = $this->clampLogoSpaceValue($value); + } + + /** + * clamp/set SVG circle radius + */ + protected function set_circleRadius(float $circleRadius):void{ + $this->circleRadius = max(0.1, min(0.75, $circleRadius)); + } + + /* + * redirect calls of deprecated variables to new/renamed property + */ + + /** + * @deprecated 5.0.0 use QROptions::$outputBase64 instead + * @see \chillerlan\QRCode\QROptions::$outputBase64 + */ + protected bool $imageBase64; + + /** + * redirect call to the new variable + * + * @deprecated 5.0.0 use QROptions::$outputBase64 instead + * @see \chillerlan\QRCode\QROptions::$outputBase64 + * @codeCoverageIgnore + */ + protected function set_imageBase64(bool $imageBase64):void{ + $this->outputBase64 = $imageBase64; + } + + /** + * redirect call to the new variable + * + * @deprecated 5.0.0 use QROptions::$outputBase64 instead + * @see \chillerlan\QRCode\QROptions::$outputBase64 + * @codeCoverageIgnore + */ + protected function get_imageBase64():bool{ + return $this->outputBase64; + } + + /** + * @deprecated 5.0.0 use QROptions::$quality instead + * @see \chillerlan\QRCode\QROptions::$quality + */ + protected int $jpegQuality; + + /** + * @deprecated 5.0.0 use QROptions::$quality instead + * @see \chillerlan\QRCode\QROptions::$quality + * @codeCoverageIgnore + */ + protected function set_jpegQuality(int $jpegQuality):void{ + $this->quality = $jpegQuality; + } + + /** + * @deprecated 5.0.0 use QROptions::$quality instead + * @see \chillerlan\QRCode\QROptions::$quality + * @codeCoverageIgnore + */ + protected function get_jpegQuality():int{ + return $this->quality; + } + + /** + * @deprecated 5.0.0 use QROptions::$quality instead + * @see \chillerlan\QRCode\QROptions::$quality + */ + protected int $pngCompression; + + /** + * @deprecated 5.0.0 use QROptions::$quality instead + * @see \chillerlan\QRCode\QROptions::$quality + * @codeCoverageIgnore + */ + protected function set_pngCompression(int $pngCompression):void{ + $this->quality = $pngCompression; + } + + /** + * @deprecated 5.0.0 use QROptions::$quality instead + * @see \chillerlan\QRCode\QROptions::$quality + * @codeCoverageIgnore + */ + protected function get_pngCompression():int{ + return $this->quality; + } + + /** + * @deprecated 5.0.0 use QROptions::$transparencyColor instead + * @see \chillerlan\QRCode\QROptions::$transparencyColor + */ + protected array $imageTransparencyBG; + + /** + * @deprecated 5.0.0 use QROptions::$transparencyColor instead + * @see \chillerlan\QRCode\QROptions::$transparencyColor + * @codeCoverageIgnore + */ + protected function set_imageTransparencyBG(?array $imageTransparencyBG):void{ + $this->transparencyColor = $imageTransparencyBG; + } + + /** + * @deprecated 5.0.0 use QROptions::$transparencyColor instead + * @see \chillerlan\QRCode\QROptions::$transparencyColor + * @codeCoverageIgnore + */ + protected function get_imageTransparencyBG():?array{ + return $this->transparencyColor; + } + + /** + * @deprecated 5.0.0 use QROptions::$bgColor instead + * @see \chillerlan\QRCode\QROptions::$bgColor + */ + protected string $imagickBG; + + /** + * @deprecated 5.0.0 use QROptions::$bgColor instead + * @see \chillerlan\QRCode\QROptions::$bgColor + * @codeCoverageIgnore + */ + protected function set_imagickBG(?string $imagickBG):void{ + $this->bgColor = $imagickBG; + } + + /** + * @deprecated 5.0.0 use QROptions::$bgColor instead + * @see \chillerlan\QRCode\QROptions::$bgColor + * @codeCoverageIgnore + */ + protected function get_imagickBG():?string{ + return $this->bgColor; + } + +} diff --git a/src/vendor/chillerlan/php-settings-container/LICENSE b/src/vendor/chillerlan/php-settings-container/LICENSE new file mode 100644 index 00000000..25d371fc --- /dev/null +++ b/src/vendor/chillerlan/php-settings-container/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Smiley + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/vendor/chillerlan/php-settings-container/README.md b/src/vendor/chillerlan/php-settings-container/README.md new file mode 100644 index 00000000..32f606fb --- /dev/null +++ b/src/vendor/chillerlan/php-settings-container/README.md @@ -0,0 +1,164 @@ +# chillerlan/php-settings-container + +A container class for settings objects - decouple configuration logic from your application! Not a DI container. +- [`SettingsContainerInterface`](https://github.com/chillerlan/php-settings-container/blob/main/src/SettingsContainerInterface.php) provides immutable properties with magic getter & setter and some fancy. + +[![PHP Version Support][php-badge]][php] +[![version][packagist-badge]][packagist] +[![license][license-badge]][license] +[![Continuous Integration][gh-action-badge]][gh-action] +[![Coverage][coverage-badge]][coverage] +[![Codacy][codacy-badge]][codacy] +[![Packagist downloads][downloads-badge]][downloads] + +[php-badge]: https://img.shields.io/packagist/php-v/chillerlan/php-settings-container?logo=php&color=8892BF +[php]: https://www.php.net/supported-versions.php +[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-settings-container.svg?logo=packagist +[packagist]: https://packagist.org/packages/chillerlan/php-settings-container +[license-badge]: https://img.shields.io/github/license/chillerlan/php-settings-container.svg +[license]: https://github.com/chillerlan/php-settings-container/blob/main/LICENSE +[coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-settings-container.svg?logo=codecov +[coverage]: https://codecov.io/github/chillerlan/php-settings-container +[codacy-badge]: https://img.shields.io/codacy/grade/bd2467799e2943d2853ce3ebad5af490/main?logo=codacy +[codacy]: https://www.codacy.com/gh/chillerlan/php-settings-container/dashboard?branch=main +[downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-settings-container.svg?logo=packagist +[downloads]: https://packagist.org/packages/chillerlan/php-settings-container/stats +[gh-action-badge]: https://img.shields.io/github/actions/workflow/status/chillerlan/php-settings-container/ci.yml?branch=main&logo=github +[gh-action]: https://github.com/chillerlan/php-settings-container/actions/workflows/ci.yml?query=branch%3Amain + +## Documentation + +### Installation +**requires [composer](https://getcomposer.org)** + +*composer.json* (note: replace `dev-main` with a [version constraint](https://getcomposer.org/doc/articles/versions.md#writing-version-constraints), e.g. `^3.0` - see [releases](https://github.com/chillerlan/php-settings-container/releases) for valid versions) +```json +{ + "require": { + "php": "^8.1", + "chillerlan/php-settings-container": "dev-main" + } +} +``` + +Profit! + +## Usage + +The `SettingsContainerInterface` (wrapped in`SettingsContainerAbstract`) provides plug-in functionality for immutable object properties and adds some fancy, like loading/saving JSON, arrays etc. +It takes an `iterable` as the only constructor argument and calls a method with the trait's name on invocation (`MyTrait::MyTrait()`) for each used trait. + +### Simple usage +```php +class MyContainer extends SettingsContainerAbstract{ + protected string $foo; + protected string $bar; +} +``` + +```php +// use it just like a \stdClass (except the properties are fixed) +$container = new MyContainer; +$container->foo = 'what'; +$container->bar = 'foo'; + +// which is equivalent to +$container = new MyContainer(['bar' => 'foo', 'foo' => 'what']); +// ...or try +$container->fromJSON('{"foo": "what", "bar": "foo"}'); + + +// fetch all properties as array +$container->toArray(); // -> ['foo' => 'what', 'bar' => 'foo'] +// or JSON +$container->toJSON(); // -> {"foo": "what", "bar": "foo"} +// JSON via JsonSerializable +$json = json_encode($container); // -> {"foo": "what", "bar": "foo"} + +//non-existing properties will be ignored: +$container->nope = 'what'; + +var_dump($container->nope); // -> null +``` + +### Advanced usage +```php +// from library 1 +trait SomeOptions{ + protected string $foo; + protected string $what; + + // this method will be called in SettingsContainerAbstract::construct() + // after the properties have been set + protected function SomeOptions():void{ + // just some constructor stuff... + $this->foo = strtoupper($this->foo); + } + + /* + * special prefixed magic setters & getters + */ + + // this method will be called from __set() when property $what is set + protected function set_what(string $value):void{ + $this->what = md5($value); + } + + // this method is called on __get() for the property $what + protected function get_what():string{ + return 'hash: '.$this->what; + } +} + +// from library 2 +trait MoreOptions{ + protected string $bar = 'whatever'; // provide default values +} +``` + +```php +$commonOptions = [ + // SomeOptions + 'foo' => 'whatever', + // MoreOptions + 'bar' => 'nothing', +]; + +// now plug the several library options together to a single object +$container = new class ($commonOptions) extends SettingsContainerAbstract{ + use SomeOptions, MoreOptions; +}; + +var_dump($container->foo); // -> WHATEVER (constructor ran strtoupper on the value) +var_dump($container->bar); // -> nothing + +$container->what = 'some value'; +var_dump($container->what); // -> hash: 5946210c9e93ae37891dfe96c3e39614 (custom getter added "hash: ") +``` + +### API + +#### [`SettingsContainerAbstract`](https://github.com/chillerlan/php-settings-container/blob/main/src/SettingsContainerAbstract.php) + +| method | return | info | +|--------------------------------------------|------------------------------|---------------------------------------------------------------------------------------------------------------------| +| `__construct(iterable $properties = null)` | - | calls `construct()` internally after the properties have been set | +| (protected) `construct()` | void | calls a method with trait name as replacement constructor for each used trait | +| `__get(string $property)` | mixed | calls `$this->{'get_'.$property}()` if such a method exists | +| `__set(string $property, $value)` | void | calls `$this->{'set_'.$property}($value)` if such a method exists | +| `__isset(string $property)` | bool | | +| `__unset(string $property)` | void | | +| `__toString()` | string | a JSON string | +| `toArray()` | array | | +| `fromIterable(iterable $properties)` | `SettingsContainerInterface` | | +| `toJSON(int $jsonOptions = null)` | string | accepts [JSON options constants](http://php.net/manual/json.constants.php) | +| `fromJSON(string $json)` | `SettingsContainerInterface` | | +| `jsonSerialize()` | mixed | implements the [`JsonSerializable`](https://www.php.net/manual/en/jsonserializable.jsonserialize.php) interface | +| `serialize()` | string | implements the [`Serializable`](https://www.php.net/manual/en/serializable.serialize.php) interface | +| `unserialize(string $data)` | void | implements the [`Serializable`](https://www.php.net/manual/en/serializable.unserialize.php) interface | +| `__serialize()` | array | implements the [`Serializable`](https://www.php.net/manual/en/language.oop5.magic.php#object.serialize) interface | +| `__unserialize(array $data)` | void | implements the [`Serializable`](https://www.php.net/manual/en/language.oop5.magic.php#object.unserialize) interface | + +## Disclaimer +This might be either an utterly genius or completely stupid idea - you decide. However, i like it and it works. +Also, this is not a dependency injection container. Stop using DI containers FFS. diff --git a/src/vendor/chillerlan/php-settings-container/composer.json b/src/vendor/chillerlan/php-settings-container/composer.json new file mode 100644 index 00000000..8d19b8db --- /dev/null +++ b/src/vendor/chillerlan/php-settings-container/composer.json @@ -0,0 +1,51 @@ +{ + "name": "chillerlan/php-settings-container", + "description": "A container class for immutable settings objects. Not a DI container.", + "homepage": "https://github.com/chillerlan/php-settings-container", + "license": "MIT", + "type": "library", + "minimum-stability": "stable", + "keywords": [ + "helper", "container", "settings", "configuration" + ], + "authors": [ + { + "name": "Smiley", + "email": "smiley@chillerlan.net", + "homepage": "https://github.com/codemasher" + } + ], + "support": { + "issues": "https://github.com/chillerlan/php-settings-container/issues", + "source": "https://github.com/chillerlan/php-settings-container" + }, + "require": { + "php": "^8.1", + "ext-json": "*" + }, + "require-dev": { + "phan/phan": "^5.4", + "phpmd/phpmd": "^2.15", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^3.9" + }, + "autoload": { + "psr-4": { + "chillerlan\\Settings\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "chillerlan\\SettingsTest\\": "tests/" + } + }, + "scripts": { + "phpunit": "@php vendor/bin/phpunit", + "phan": "@php vendor/bin/phan" + }, + "config": { + "lock": false, + "sort-packages": true, + "platform-check": true + } +} diff --git a/src/vendor/chillerlan/php-settings-container/src/SettingsContainerAbstract.php b/src/vendor/chillerlan/php-settings-container/src/SettingsContainerAbstract.php new file mode 100644 index 00000000..b54ca909 --- /dev/null +++ b/src/vendor/chillerlan/php-settings-container/src/SettingsContainerAbstract.php @@ -0,0 +1,240 @@ + + * @copyright 2018 Smiley + * @license MIT + */ +declare(strict_types=1); + +namespace chillerlan\Settings; + +use InvalidArgumentException, ReflectionClass, ReflectionProperty; +use function array_keys, get_object_vars, is_object, json_decode, + json_encode, method_exists, property_exists, serialize, unserialize; +use const JSON_THROW_ON_ERROR; + +abstract class SettingsContainerAbstract implements SettingsContainerInterface{ + + /** + * SettingsContainerAbstract constructor. + */ + public function __construct(iterable|null $properties = null){ + + if(!empty($properties)){ + $this->fromIterable($properties); + } + + $this->construct(); + } + + /** + * calls a method with trait name as replacement constructor for each used trait + * (remember pre-php5 classname constructors? yeah, basically this.) + */ + protected function construct():void{ + $traits = (new ReflectionClass($this))->getTraits(); + + foreach($traits as $trait){ + $method = $trait->getShortName(); + + if(method_exists($this, $method)){ + $this->{$method}(); + } + } + + } + + /** + * @inheritdoc + */ + public function __get(string $property):mixed{ + + if(!property_exists($this, $property) || $this->isPrivate($property)){ + return null; + } + + $method = 'get_'.$property; + + if(method_exists($this, $method)){ + return $this->{$method}(); + } + + return $this->{$property}; + } + + /** + * @inheritdoc + */ + public function __set(string $property, mixed $value):void{ + + if(!property_exists($this, $property) || $this->isPrivate($property)){ + return; + } + + $method = 'set_'.$property; + + if(method_exists($this, $method)){ + $this->{$method}($value); + + return; + } + + $this->{$property} = $value; + } + + /** + * @inheritdoc + */ + public function __isset(string $property):bool{ + return isset($this->{$property}) && !$this->isPrivate($property); + } + + /** + * @internal Checks if a property is private + */ + protected function isPrivate(string $property):bool{ + return (new ReflectionProperty($this, $property))->isPrivate(); + } + + /** + * @inheritdoc + */ + public function __unset(string $property):void{ + + if($this->__isset($property)){ + unset($this->{$property}); + } + + } + + /** + * @inheritdoc + */ + public function __toString():string{ + return $this->toJSON(); + } + + /** + * @inheritdoc + */ + public function toArray():array{ + $properties = []; + + foreach(array_keys(get_object_vars($this)) as $key){ + $properties[$key] = $this->__get($key); + } + + return $properties; + } + + /** + * @inheritdoc + */ + public function fromIterable(iterable $properties):static{ + + foreach($properties as $key => $value){ + $this->__set($key, $value); + } + + return $this; + } + + /** + * @inheritdoc + */ + public function toJSON(int|null $jsonOptions = null):string{ + return json_encode($this, ($jsonOptions ?? 0)); + } + + /** + * @inheritdoc + */ + public function fromJSON(string $json):static{ + $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR); + + return $this->fromIterable($data); + } + + /** + * @inheritdoc + */ + public function jsonSerialize():array{ + return $this->toArray(); + } + + /** + * Returns a serialized string representation of the object in its current state (except static/readonly properties) + * + * @inheritdoc + * @see \chillerlan\Settings\SettingsContainerInterface::toArray() + */ + public function serialize():string{ + return serialize($this); + } + + /** + * Restores the data (except static/readonly properties) from the given serialized object to the current instance + * + * @inheritdoc + * @see \chillerlan\Settings\SettingsContainerInterface::fromIterable() + */ + public function unserialize(string $data):void{ + $obj = unserialize($data); + + if($obj === false || !is_object($obj)){ + throw new InvalidArgumentException('The given serialized string is invalid'); + } + + $reflection = new ReflectionClass($obj); + + if(!$reflection->isInstance($this)){ + throw new InvalidArgumentException('The unserialized object does not match the class of this container'); + } + + $properties = $reflection->getProperties(~(ReflectionProperty::IS_STATIC | ReflectionProperty::IS_READONLY)); + + foreach($properties as $reflectionProperty){ + $this->{$reflectionProperty->name} = $reflectionProperty->getValue($obj); + } + + } + + /** + * Returns a serialized string representation of the object in its current state (except static/readonly properties) + * + * @inheritdoc + * @see \chillerlan\Settings\SettingsContainerInterface::toArray() + */ + public function __serialize():array{ + + $properties = (new ReflectionClass($this)) + ->getProperties(~(ReflectionProperty::IS_STATIC | ReflectionProperty::IS_READONLY)) + ; + + $data = []; + + foreach($properties as $reflectionProperty){ + $data[$reflectionProperty->name] = $reflectionProperty->getValue($this); + } + + return $data; + } + + /** + * Restores the data from the given array to the current instance + * + * @inheritdoc + * @see \chillerlan\Settings\SettingsContainerInterface::fromIterable() + */ + public function __unserialize(array $data):void{ + + foreach($data as $key => $value){ + $this->{$key} = $value; + } + + } + +} diff --git a/src/vendor/chillerlan/php-settings-container/src/SettingsContainerInterface.php b/src/vendor/chillerlan/php-settings-container/src/SettingsContainerInterface.php new file mode 100644 index 00000000..1c9c12a7 --- /dev/null +++ b/src/vendor/chillerlan/php-settings-container/src/SettingsContainerInterface.php @@ -0,0 +1,78 @@ + + * @copyright 2018 Smiley + * @license MIT + */ +declare(strict_types=1); + +namespace chillerlan\Settings; + +use JsonSerializable, Serializable; + +/** + * a generic container with magic getter and setter + */ +interface SettingsContainerInterface extends JsonSerializable, Serializable{ + + /** + * Retrieve the value of $property + * + * @return mixed|null + */ + public function __get(string $property):mixed; + + /** + * Set $property to $value while avoiding private and non-existing properties + */ + public function __set(string $property, mixed $value):void; + + /** + * Checks if $property is set (aka. not null), excluding private properties + */ + public function __isset(string $property):bool; + + /** + * Unsets $property while avoiding private and non-existing properties + */ + public function __unset(string $property):void; + + /** + * @see \chillerlan\Settings\SettingsContainerInterface::toJSON() + */ + public function __toString():string; + + /** + * Returns an array representation of the settings object + * + * The values will be run through the magic __get(), which may also call custom getters. + */ + public function toArray():array; + + /** + * Sets properties from a given iterable + * + * The values will be run through the magic __set(), which may also call custom setters. + */ + public function fromIterable(iterable $properties):static; + + /** + * Returns a JSON representation of the settings object + * @see \json_encode() + * @see \chillerlan\Settings\SettingsContainerInterface::toArray() + */ + public function toJSON(int|null $jsonOptions = null):string; + + /** + * Sets properties from a given JSON string + * + * @throws \Exception + * @throws \JsonException + * @see \chillerlan\Settings\SettingsContainerInterface::fromIterable() + */ + public function fromJSON(string $json):static; + +} diff --git a/src/vendor/composer/ClassLoader.php b/src/vendor/composer/ClassLoader.php new file mode 100644 index 00000000..7824d8f7 --- /dev/null +++ b/src/vendor/composer/ClassLoader.php @@ -0,0 +1,579 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/src/vendor/composer/InstalledVersions.php b/src/vendor/composer/InstalledVersions.php new file mode 100644 index 00000000..51e734a7 --- /dev/null +++ b/src/vendor/composer/InstalledVersions.php @@ -0,0 +1,359 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + $installed[] = self::$installedByVendor[$vendorDir] = $required; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/LICENSE b/src/vendor/composer/LICENSE similarity index 87% rename from LICENSE rename to src/vendor/composer/LICENSE index 5285f420..f27399a0 100644 --- a/LICENSE +++ b/src/vendor/composer/LICENSE @@ -1,13 +1,12 @@ -MIT License -Copyright (c) 2017 +Copyright (c) Nils Adermann, Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. @@ -17,5 +16,6 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/src/vendor/composer/autoload_classmap.php b/src/vendor/composer/autoload_classmap.php new file mode 100644 index 00000000..0fb0a2c1 --- /dev/null +++ b/src/vendor/composer/autoload_classmap.php @@ -0,0 +1,10 @@ + $vendorDir . '/composer/InstalledVersions.php', +); diff --git a/src/vendor/composer/autoload_namespaces.php b/src/vendor/composer/autoload_namespaces.php new file mode 100644 index 00000000..15a2ff3a --- /dev/null +++ b/src/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/chillerlan/php-settings-container/src'), + 'chillerlan\\QRCode\\' => array($vendorDir . '/chillerlan/php-qrcode/src'), + 'Zoeyvid\\Npmplus\\' => array($baseDir . '/src'), + 'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'), +); diff --git a/src/vendor/composer/autoload_real.php b/src/vendor/composer/autoload_real.php new file mode 100644 index 00000000..d7cf9918 --- /dev/null +++ b/src/vendor/composer/autoload_real.php @@ -0,0 +1,38 @@ +register(true); + + return $loader; + } +} diff --git a/src/vendor/composer/autoload_static.php b/src/vendor/composer/autoload_static.php new file mode 100644 index 00000000..9ee731fc --- /dev/null +++ b/src/vendor/composer/autoload_static.php @@ -0,0 +1,57 @@ + + array ( + 'chillerlan\\Settings\\' => 20, + 'chillerlan\\QRCode\\' => 18, + ), + 'Z' => + array ( + 'Zoeyvid\\Npmplus\\' => 16, + ), + 'P' => + array ( + 'PHPMailer\\PHPMailer\\' => 20, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'chillerlan\\Settings\\' => + array ( + 0 => __DIR__ . '/..' . '/chillerlan/php-settings-container/src', + ), + 'chillerlan\\QRCode\\' => + array ( + 0 => __DIR__ . '/..' . '/chillerlan/php-qrcode/src', + ), + 'Zoeyvid\\Npmplus\\' => + array ( + 0 => __DIR__ . '/../..' . '/src', + ), + 'PHPMailer\\PHPMailer\\' => + array ( + 0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit0e5e06a8da6fe6041233a057fffa913a::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit0e5e06a8da6fe6041233a057fffa913a::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit0e5e06a8da6fe6041233a057fffa913a::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/src/vendor/composer/installed.json b/src/vendor/composer/installed.json new file mode 100644 index 00000000..e7e6f375 --- /dev/null +++ b/src/vendor/composer/installed.json @@ -0,0 +1,255 @@ +{ + "packages": [ + { + "name": "chillerlan/php-qrcode", + "version": "5.0.2", + "version_normalized": "5.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/chillerlan/php-qrcode.git", + "reference": "da5bdb82c8755f54de112b271b402aaa8df53269" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/da5bdb82c8755f54de112b271b402aaa8df53269", + "reference": "da5bdb82c8755f54de112b271b402aaa8df53269", + "shasum": "" + }, + "require": { + "chillerlan/php-settings-container": "^2.1.4 || ^3.1", + "ext-mbstring": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "chillerlan/php-authenticator": "^4.1 || ^5.1", + "phan/phan": "^5.4", + "phpmd/phpmd": "^2.15", + "phpunit/phpunit": "^9.6", + "setasign/fpdf": "^1.8.2", + "squizlabs/php_codesniffer": "^3.8" + }, + "suggest": { + "chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.", + "setasign/fpdf": "Required to use the QR FPDF output.", + "simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code" + }, + "time": "2024-02-27T14:37:26+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "chillerlan\\QRCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT", + "Apache-2.0" + ], + "authors": [ + { + "name": "Kazuhiko Arase", + "homepage": "https://github.com/kazuhikoarase/qrcode-generator" + }, + { + "name": "ZXing Authors", + "homepage": "https://github.com/zxing/zxing" + }, + { + "name": "Ashot Khanamiryan", + "homepage": "https://github.com/khanamiryan/php-qrcode-detector-decoder" + }, + { + "name": "Smiley", + "email": "smiley@chillerlan.net", + "homepage": "https://github.com/codemasher" + }, + { + "name": "Contributors", + "homepage": "https://github.com/chillerlan/php-qrcode/graphs/contributors" + } + ], + "description": "A QR code generator and reader with a user friendly API. PHP 7.4+", + "homepage": "https://github.com/chillerlan/php-qrcode", + "keywords": [ + "phpqrcode", + "qr", + "qr code", + "qr-reader", + "qrcode", + "qrcode-generator", + "qrcode-reader" + ], + "support": { + "docs": "https://php-qrcode.readthedocs.io", + "issues": "https://github.com/chillerlan/php-qrcode/issues", + "source": "https://github.com/chillerlan/php-qrcode" + }, + "funding": [ + { + "url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4", + "type": "custom" + }, + { + "url": "https://ko-fi.com/codemasher", + "type": "ko_fi" + } + ], + "install-path": "../chillerlan/php-qrcode" + }, + { + "name": "chillerlan/php-settings-container", + "version": "3.2.0", + "version_normalized": "3.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/chillerlan/php-settings-container.git", + "reference": "8f93648fac8e6bacac8e00a8d325eba4950295e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/8f93648fac8e6bacac8e00a8d325eba4950295e6", + "reference": "8f93648fac8e6bacac8e00a8d325eba4950295e6", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^8.1" + }, + "require-dev": { + "phan/phan": "^5.4", + "phpmd/phpmd": "^2.15", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^3.9" + }, + "time": "2024-03-02T20:07:15+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "chillerlan\\Settings\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Smiley", + "email": "smiley@chillerlan.net", + "homepage": "https://github.com/codemasher" + } + ], + "description": "A container class for immutable settings objects. Not a DI container.", + "homepage": "https://github.com/chillerlan/php-settings-container", + "keywords": [ + "Settings", + "configuration", + "container", + "helper" + ], + "support": { + "issues": "https://github.com/chillerlan/php-settings-container/issues", + "source": "https://github.com/chillerlan/php-settings-container" + }, + "funding": [ + { + "url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4", + "type": "custom" + }, + { + "url": "https://ko-fi.com/codemasher", + "type": "ko_fi" + } + ], + "install-path": "../chillerlan/php-settings-container" + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.9.1", + "version_normalized": "6.9.1.0", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "039de174cd9c17a8389754d3b877a2ed22743e18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18", + "reference": "039de174cd9c17a8389754d3b877a2ed22743e18", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "time": "2023-11-25T22:23:28+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.1" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "install-path": "../phpmailer/phpmailer" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/src/vendor/composer/installed.php b/src/vendor/composer/installed.php new file mode 100644 index 00000000..902165e1 --- /dev/null +++ b/src/vendor/composer/installed.php @@ -0,0 +1,50 @@ + array( + 'name' => 'zoeyvid/npmplus', + 'pretty_version' => 'dev-develop', + 'version' => 'dev-develop', + 'reference' => '18ae902a0459ac0ee25f1e25e658ba34ad0a6ca3', + 'type' => 'project', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + 'chillerlan/php-qrcode' => array( + 'pretty_version' => '5.0.2', + 'version' => '5.0.2.0', + 'reference' => 'da5bdb82c8755f54de112b271b402aaa8df53269', + 'type' => 'library', + 'install_path' => __DIR__ . '/../chillerlan/php-qrcode', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'chillerlan/php-settings-container' => array( + 'pretty_version' => '3.2.0', + 'version' => '3.2.0.0', + 'reference' => '8f93648fac8e6bacac8e00a8d325eba4950295e6', + 'type' => 'library', + 'install_path' => __DIR__ . '/../chillerlan/php-settings-container', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'phpmailer/phpmailer' => array( + 'pretty_version' => 'v6.9.1', + 'version' => '6.9.1.0', + 'reference' => '039de174cd9c17a8389754d3b877a2ed22743e18', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpmailer/phpmailer', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'zoeyvid/npmplus' => array( + 'pretty_version' => 'dev-develop', + 'version' => 'dev-develop', + 'reference' => '18ae902a0459ac0ee25f1e25e658ba34ad0a6ca3', + 'type' => 'project', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/src/vendor/composer/platform_check.php b/src/vendor/composer/platform_check.php new file mode 100644 index 00000000..4c3a5d68 --- /dev/null +++ b/src/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 80100)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/src/vendor/phpmailer/phpmailer/.editorconfig b/src/vendor/phpmailer/phpmailer/.editorconfig new file mode 100644 index 00000000..a7c44ddb --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 diff --git a/src/vendor/phpmailer/phpmailer/COMMITMENT b/src/vendor/phpmailer/phpmailer/COMMITMENT new file mode 100644 index 00000000..a687e0dd --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/COMMITMENT @@ -0,0 +1,46 @@ +GPL Cooperation Commitment +Version 1.0 + +Before filing or continuing to prosecute any legal proceeding or claim +(other than a Defensive Action) arising from termination of a Covered +License, we commit to extend to the person or entity ('you') accused +of violating the Covered License the following provisions regarding +cure and reinstatement, taken from GPL version 3. As used here, the +term 'this License' refers to the specific Covered License being +enforced. + + However, if you cease all violation of this License, then your + license from a particular copyright holder is reinstated (a) + provisionally, unless and until the copyright holder explicitly + and finally terminates your license, and (b) permanently, if the + copyright holder fails to notify you of the violation by some + reasonable means prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is + reinstated permanently if the copyright holder notifies you of the + violation by some reasonable means, this is the first time you + have received notice of violation of this License (for any work) + from that copyright holder, and you cure the violation prior to 30 + days after your receipt of the notice. + +We intend this Commitment to be irrevocable, and binding and +enforceable against us and assignees of or successors to our +copyrights. + +Definitions + +'Covered License' means the GNU General Public License, version 2 +(GPLv2), the GNU Lesser General Public License, version 2.1 +(LGPLv2.1), or the GNU Library General Public License, version 2 +(LGPLv2), all as published by the Free Software Foundation. + +'Defensive Action' means a legal proceeding or claim that We bring +against you in response to a prior proceeding or claim initiated by +you or your affiliate. + +'We' means each contributor to this repository as of the date of +inclusion of this file, including subsidiaries of a corporate +contributor. + +This work is available under a Creative Commons Attribution-ShareAlike +4.0 International license (https://creativecommons.org/licenses/by-sa/4.0/). diff --git a/src/vendor/phpmailer/phpmailer/LICENSE b/src/vendor/phpmailer/phpmailer/LICENSE new file mode 100644 index 00000000..f166cc57 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/LICENSE @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! \ No newline at end of file diff --git a/src/vendor/phpmailer/phpmailer/README.md b/src/vendor/phpmailer/phpmailer/README.md new file mode 100644 index 00000000..e3e4ecff --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/README.md @@ -0,0 +1,231 @@ +[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://supportukrainenow.org/) + +![PHPMailer](https://raw.github.com/PHPMailer/PHPMailer/master/examples/images/phpmailer.png) + +# PHPMailer – A full-featured email creation and transfer class for PHP + +[![Test status](https://github.com/PHPMailer/PHPMailer/workflows/Tests/badge.svg)](https://github.com/PHPMailer/PHPMailer/actions) +[![codecov.io](https://codecov.io/gh/PHPMailer/PHPMailer/branch/master/graph/badge.svg?token=iORZpwmYmM)](https://codecov.io/gh/PHPMailer/PHPMailer) +[![Latest Stable Version](https://poser.pugx.org/phpmailer/phpmailer/v/stable.svg)](https://packagist.org/packages/phpmailer/phpmailer) +[![Total Downloads](https://poser.pugx.org/phpmailer/phpmailer/downloads)](https://packagist.org/packages/phpmailer/phpmailer) +[![License](https://poser.pugx.org/phpmailer/phpmailer/license.svg)](https://packagist.org/packages/phpmailer/phpmailer) +[![API Docs](https://github.com/phpmailer/phpmailer/workflows/Docs/badge.svg)](https://phpmailer.github.io/PHPMailer/) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/PHPMailer/PHPMailer/badge)](https://api.securityscorecards.dev/projects/github.com/PHPMailer/PHPMailer) + +## Features +- Probably the world's most popular code for sending email from PHP! +- Used by many open-source projects: WordPress, Drupal, 1CRM, SugarCRM, Yii, Joomla! and many more +- Integrated SMTP support – send without a local mail server +- Send emails with multiple To, CC, BCC, and Reply-to addresses +- Multipart/alternative emails for mail clients that do not read HTML email +- Add attachments, including inline +- Support for UTF-8 content and 8bit, base64, binary, and quoted-printable encodings +- SMTP authentication with LOGIN, PLAIN, CRAM-MD5, and XOAUTH2 mechanisms over SMTPS and SMTP+STARTTLS transports +- Validates email addresses automatically +- Protects against header injection attacks +- Error messages in over 50 languages! +- DKIM and S/MIME signing support +- Compatible with PHP 5.5 and later, including PHP 8.2 +- Namespaced to prevent name clashes +- Much more! + +## Why you might need it +Many PHP developers need to send email from their code. The only PHP function that supports this directly is [`mail()`](https://www.php.net/manual/en/function.mail.php). However, it does not provide any assistance for making use of popular features such as encryption, authentication, HTML messages, and attachments. + +Formatting email correctly is surprisingly difficult. There are myriad overlapping (and conflicting) standards, requiring tight adherence to horribly complicated formatting and encoding rules – the vast majority of code that you'll find online that uses the `mail()` function directly is just plain wrong, if not unsafe! + +The PHP `mail()` function usually sends via a local mail server, typically fronted by a `sendmail` binary on Linux, BSD, and macOS platforms, however, Windows usually doesn't include a local mail server; PHPMailer's integrated SMTP client allows email sending on all platforms without needing a local mail server. Be aware though, that the `mail()` function should be avoided when possible; it's both faster and [safer](https://exploitbox.io/paper/Pwning-PHP-Mail-Function-For-Fun-And-RCE.html) to use SMTP to localhost. + +*Please* don't be tempted to do it yourself – if you don't use PHPMailer, there are many other excellent libraries that +you should look at before rolling your own. Try [SwiftMailer](https://swiftmailer.symfony.com/) +, [Laminas/Mail](https://docs.laminas.dev/laminas-mail/), [ZetaComponents](https://github.com/zetacomponents/Mail), etc. + +## License +This software is distributed under the [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) license, along with the [GPL Cooperation Commitment](https://gplcc.github.io/gplcc/). Please read [LICENSE](https://github.com/PHPMailer/PHPMailer/blob/master/LICENSE) for information on the software availability and distribution. + +## Installation & loading +PHPMailer is available on [Packagist](https://packagist.org/packages/phpmailer/phpmailer) (using semantic versioning), and installation via [Composer](https://getcomposer.org) is the recommended way to install PHPMailer. Just add this line to your `composer.json` file: + +```json +"phpmailer/phpmailer": "^6.9.1" +``` + +or run + +```sh +composer require phpmailer/phpmailer +``` + +Note that the `vendor` folder and the `vendor/autoload.php` script are generated by Composer; they are not part of PHPMailer. + +If you want to use XOAUTH2 authentication, you will also need to add a dependency on the `league/oauth2-client` and appropriate service adapters package in your `composer.json`, or take a look at +by @decomplexity's [SendOauth2 wrapper](https://github.com/decomplexity/SendOauth2), especially if you're using Microsoft services. + +Alternatively, if you're not using Composer, you +can [download PHPMailer as a zip file](https://github.com/PHPMailer/PHPMailer/archive/master.zip), (note that docs and examples are not included in the zip file), then copy the contents of the PHPMailer folder into one of the `include_path` directories specified in your PHP configuration and load each class file manually: + +```php +SMTPDebug = SMTP::DEBUG_SERVER; //Enable verbose debug output + $mail->isSMTP(); //Send using SMTP + $mail->Host = 'smtp.example.com'; //Set the SMTP server to send through + $mail->SMTPAuth = true; //Enable SMTP authentication + $mail->Username = 'user@example.com'; //SMTP username + $mail->Password = 'secret'; //SMTP password + $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; //Enable implicit TLS encryption + $mail->Port = 465; //TCP port to connect to; use 587 if you have set `SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS` + + //Recipients + $mail->setFrom('from@example.com', 'Mailer'); + $mail->addAddress('joe@example.net', 'Joe User'); //Add a recipient + $mail->addAddress('ellen@example.com'); //Name is optional + $mail->addReplyTo('info@example.com', 'Information'); + $mail->addCC('cc@example.com'); + $mail->addBCC('bcc@example.com'); + + //Attachments + $mail->addAttachment('/var/tmp/file.tar.gz'); //Add attachments + $mail->addAttachment('/tmp/image.jpg', 'new.jpg'); //Optional name + + //Content + $mail->isHTML(true); //Set email format to HTML + $mail->Subject = 'Here is the subject'; + $mail->Body = 'This is the HTML message body in bold!'; + $mail->AltBody = 'This is the body in plain text for non-HTML mail clients'; + + $mail->send(); + echo 'Message has been sent'; +} catch (Exception $e) { + echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}"; +} +``` + +You'll find plenty to play with in the [examples](https://github.com/PHPMailer/PHPMailer/tree/master/examples) folder, which covers many common scenarios including sending through Gmail, building contact forms, sending to mailing lists, and more. + +If you are re-using the instance (e.g. when sending to a mailing list), you may need to clear the recipient list to avoid sending duplicate messages. See [the mailing list example](https://github.com/PHPMailer/PHPMailer/blob/master/examples/mailing_list.phps) for further guidance. + +That's it. You should now be ready to use PHPMailer! + +## Localization +PHPMailer defaults to English, but in the [language](https://github.com/PHPMailer/PHPMailer/tree/master/language/) folder, you'll find many translations for PHPMailer error messages that you may encounter. Their filenames contain [ISO 639-1](http://en.wikipedia.org/wiki/ISO_639-1) language code for the translations, for example `fr` for French. To specify a language, you need to tell PHPMailer which one to use, like this: + +```php +//To load the French version +$mail->setLanguage('fr', '/optional/path/to/language/directory/'); +``` + +We welcome corrections and new languages – if you're looking for corrections, run the [Language/TranslationCompletenessTest.php](https://github.com/PHPMailer/PHPMailer/blob/master/test/Language/TranslationCompletenessTest.php) script in the tests folder and it will show any missing translations. + +## Documentation +Start reading at the [GitHub wiki](https://github.com/PHPMailer/PHPMailer/wiki). If you're having trouble, head for [the troubleshooting guide](https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting) as it's frequently updated. + +Examples of how to use PHPMailer for common scenarios can be found in the [examples](https://github.com/PHPMailer/PHPMailer/tree/master/examples) folder. If you're looking for a good starting point, we recommend you start with [the Gmail example](https://github.com/PHPMailer/PHPMailer/tree/master/examples/gmail.phps). + +To reduce PHPMailer's deployed code footprint, examples are not included if you load PHPMailer via Composer or via [GitHub's zip file download](https://github.com/PHPMailer/PHPMailer/archive/master.zip), so you'll need to either clone the git repository or use the above links to get to the examples directly. + +Complete generated API documentation is [available online](https://phpmailer.github.io/PHPMailer/). + +You can generate complete API-level documentation by running `phpdoc` in the top-level folder, and documentation will appear in the `docs` folder, though you'll need to have [PHPDocumentor](http://www.phpdoc.org) installed. You may find [the unit tests](https://github.com/PHPMailer/PHPMailer/blob/master/test/PHPMailerTest.php) a good reference for how to do various operations such as encryption. + +If the documentation doesn't cover what you need, search the [many questions on Stack Overflow](http://stackoverflow.com/questions/tagged/phpmailer), and before you ask a question about "SMTP Error: Could not connect to SMTP host.", [read the troubleshooting guide](https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting). + +## Tests +[PHPMailer tests](https://github.com/PHPMailer/PHPMailer/tree/master/test/) use PHPUnit 9, with [a polyfill](https://github.com/Yoast/PHPUnit-Polyfills) to let 9-style tests run on older PHPUnit and PHP versions. + +[![Test status](https://github.com/PHPMailer/PHPMailer/workflows/Tests/badge.svg)](https://github.com/PHPMailer/PHPMailer/actions) + +If this isn't passing, is there something you can do to help? + +## Security +Please disclose any vulnerabilities found responsibly – report security issues to the maintainers privately. + +See [SECURITY](https://github.com/PHPMailer/PHPMailer/tree/master/SECURITY.md) and [PHPMailer's security advisories on GitHub](https://github.com/PHPMailer/PHPMailer/security). + +## Contributing +Please submit bug reports, suggestions, and pull requests to the [GitHub issue tracker](https://github.com/PHPMailer/PHPMailer/issues). + +We're particularly interested in fixing edge cases, expanding test coverage, and updating translations. + +If you found a mistake in the docs, or want to add something, go ahead and amend the wiki – anyone can edit it. + +If you have git clones from prior to the move to the PHPMailer GitHub organisation, you'll need to update any remote URLs referencing the old GitHub location with a command like this from within your clone: + +```sh +git remote set-url upstream https://github.com/PHPMailer/PHPMailer.git +``` + +Please *don't* use the SourceForge or Google Code projects any more; they are obsolete and no longer maintained. + +## Sponsorship +Development time and resources for PHPMailer are provided by [Smartmessages.net](https://info.smartmessages.net/), the world's only privacy-first email marketing system. + +Smartmessages.net privacy-first email marketing logo + +Donations are very welcome, whether in beer 🍺, T-shirts 👕, or cold, hard cash 💰. Sponsorship through GitHub is a simple and convenient way to say "thank you" to PHPMailer's maintainers and contributors – just click the "Sponsor" button [on the project page](https://github.com/PHPMailer/PHPMailer). If your company uses PHPMailer, consider taking part in Tidelift's enterprise support programme. + +## PHPMailer For Enterprise + +Available as part of the Tidelift Subscription. + +The maintainers of PHPMailer and thousands of other packages are working with Tidelift to deliver commercial +support and maintenance for the open-source packages you use to build your applications. Save time, reduce risk, and +improve code health, while paying the maintainers of the exact packages you +use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-phpmailer-phpmailer?utm_source=packagist-phpmailer-phpmailer&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) + +## Changelog +See [changelog](changelog.md). + +## History +- PHPMailer was originally written in 2001 by Brent R. Matzelle as a [SourceForge project](http://sourceforge.net/projects/phpmailer/). +- [Marcus Bointon](https://github.com/Synchro) (`coolbru` on SF) and Andy Prevost (`codeworxtech`) took over the project in 2004. +- Became an Apache incubator project on Google Code in 2010, managed by Jim Jagielski. +- Marcus created [his fork on GitHub](https://github.com/Synchro/PHPMailer) in 2008. +- Jim and Marcus decide to join forces and use GitHub as the canonical and official repo for PHPMailer in 2013. +- PHPMailer moves to [the PHPMailer organisation](https://github.com/PHPMailer) on GitHub in 2013. + +### What's changed since moving from SourceForge? +- Official successor to the SourceForge and Google Code projects. +- Test suite. +- Continuous integration with GitHub Actions. +- Composer support. +- Public development. +- Additional languages and language strings. +- CRAM-MD5 authentication support. +- Preserves full repo history of authors, commits, and branches from the original SourceForge project. diff --git a/src/vendor/phpmailer/phpmailer/SECURITY.md b/src/vendor/phpmailer/phpmailer/SECURITY.md new file mode 100644 index 00000000..035a87f7 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/SECURITY.md @@ -0,0 +1,37 @@ +# Security notices relating to PHPMailer + +Please disclose any security issues or vulnerabilities found through [Tidelift's coordinated disclosure system](https://tidelift.com/security) or to the maintainers privately. + +PHPMailer 6.4.1 and earlier contain a vulnerability that can result in untrusted code being called (if such code is injected into the host project's scope by other means). If the `$patternselect` parameter to `validateAddress()` is set to `'php'` (the default, defined by `PHPMailer::$validator`), and the global namespace contains a function called `php`, it will be called in preference to the built-in validator of the same name. Mitigated in PHPMailer 6.5.0 by denying the use of simple strings as validator function names. Recorded as [CVE-2021-3603](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2021-3603). Reported by [Vikrant Singh Chauhan](mailto:vi@hackberry.xyz) via [huntr.dev](https://www.huntr.dev/). + +PHPMailer versions 6.4.1 and earlier contain a possible remote code execution vulnerability through the `$lang_path` parameter of the `setLanguage()` method. If the `$lang_path` parameter is passed unfiltered from user input, it can be set to [a UNC path](https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats#unc-paths), and if an attacker is also able to persuade the server to load a file from that UNC path, a script file under their control may be executed. This vulnerability only applies to systems that resolve UNC paths, typically only Microsoft Windows. +PHPMailer 6.5.0 mitigates this by no longer treating translation files as PHP code, but by parsing their text content directly. This approach avoids the possibility of executing unknown code while retaining backward compatibility. This isn't ideal, so the current translation format is deprecated and will be replaced in the next major release. Recorded as [CVE-2021-34551](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2021-34551). Reported by [Jilin Diting Information Technology Co., Ltd](https://listensec.com) via Tidelift. + +PHPMailer versions between 6.1.8 and 6.4.0 contain a regression of the earlier CVE-2018-19296 object injection vulnerability as a result of [a fix for Windows UNC paths in 6.1.8](https://github.com/PHPMailer/PHPMailer/commit/e2e07a355ee8ff36aba21d0242c5950c56e4c6f9). Recorded as [CVE-2020-36326](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2020-36326). Reported by Fariskhi Vidyan via Tidelift. 6.4.1 fixes this issue, and also enforces stricter checks for URL schemes in local path contexts. + +PHPMailer versions 6.1.5 and earlier contain an output escaping bug that occurs in `Content-Type` and `Content-Disposition` when filenames passed into `addAttachment` and other methods that accept attachment names contain double quote characters, in contravention of RFC822 3.4.1. No specific vulnerability has been found relating to this, but it could allow file attachments to bypass attachment filters that are based on matching filename extensions. Recorded as [CVE-2020-13625](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2020-13625). Reported by Elar Lang of Clarified Security. + +PHPMailer versions prior to 6.0.6 and 5.2.27 are vulnerable to an object injection attack by passing `phar://` paths into `addAttachment()` and other functions that may receive unfiltered local paths, possibly leading to RCE. Recorded as [CVE-2018-19296](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2018-19296). See [this article](https://knasmueller.net/5-answers-about-php-phar-exploitation) for more info on this type of vulnerability. Mitigated by blocking the use of paths containing URL-protocol style prefixes such as `phar://`. Reported by Sehun Oh of cyberone.kr. + +PHPMailer versions prior to 5.2.24 (released July 26th 2017) have an XSS vulnerability in one of the code examples, [CVE-2017-11503](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-11503). The `code_generator.phps` example did not filter user input prior to output. This file is distributed with a `.phps` extension, so it it not normally executable unless it is explicitly renamed, and the file is not included when PHPMailer is loaded through composer, so it is safe by default. There was also an undisclosed potential XSS vulnerability in the default exception handler (unused by default). Patches for both issues kindly provided by Patrick Monnerat of the Fedora Project. + +PHPMailer versions prior to 5.2.22 (released January 9th 2017) have a local file disclosure vulnerability, [CVE-2017-5223](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-5223). If content passed into `msgHTML()` is sourced from unfiltered user input, relative paths can map to absolute local file paths and added as attachments. Also note that `addAttachment` (just like `file_get_contents`, `passthru`, `unlink`, etc) should not be passed user-sourced params either! Reported by Yongxiang Li of Asiasecurity. + +PHPMailer versions prior to 5.2.20 (released December 28th 2016) are vulnerable to [CVE-2016-10045](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-10045) a remote code execution vulnerability, responsibly reported by [Dawid Golunski](https://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10045-Vuln-Patch-Bypass.html), and patched by Paul Buonopane (@Zenexer). + +PHPMailer versions prior to 5.2.18 (released December 2016) are vulnerable to [CVE-2016-10033](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-10033) a remote code execution vulnerability, responsibly reported by [Dawid Golunski](http://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10033-Vuln.html). + +PHPMailer versions prior to 5.2.14 (released November 2015) are vulnerable to [CVE-2015-8476](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-8476) an SMTP CRLF injection bug permitting arbitrary message sending. + +PHPMailer versions prior to 5.2.10 (released May 2015) are vulnerable to [CVE-2008-5619](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2008-5619), a remote code execution vulnerability in the bundled html2text library. This file was removed in 5.2.10, so if you are using a version prior to that and make use of the html2text function, it's vitally important that you upgrade and remove this file. + +PHPMailer versions prior to 2.0.7 and 2.2.1 are vulnerable to [CVE-2012-0796](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2012-0796), an email header injection attack. + +Joomla 1.6.0 uses PHPMailer in an unsafe way, allowing it to reveal local file paths, reported in [CVE-2011-3747](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2011-3747). + +PHPMailer didn't sanitise the `$lang_path` parameter in `SetLanguage`. This wasn't a problem in itself, but some apps (PHPClassifieds, ATutor) also failed to sanitise user-provided parameters passed to it, permitting semi-arbitrary local file inclusion, reported in [CVE-2010-4914](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2010-4914), [CVE-2007-2021](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2007-2021) and [CVE-2006-5734](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2006-5734). + +PHPMailer 1.7.2 and earlier contained a possible DDoS vulnerability reported in [CVE-2005-1807](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2005-1807). + +PHPMailer 1.7 and earlier (June 2003) have a possible vulnerability in the `SendmailSend` method where shell commands may not be sanitised. Reported in [CVE-2007-3215](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2007-3215). + diff --git a/src/vendor/phpmailer/phpmailer/VERSION b/src/vendor/phpmailer/phpmailer/VERSION new file mode 100644 index 00000000..dc3829f5 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/VERSION @@ -0,0 +1 @@ +6.9.1 diff --git a/src/vendor/phpmailer/phpmailer/composer.json b/src/vendor/phpmailer/phpmailer/composer.json new file mode 100644 index 00000000..fa170a0b --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/composer.json @@ -0,0 +1,79 @@ +{ + "name": "phpmailer/phpmailer", + "type": "library", + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "require": { + "php": ">=5.5.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)" + }, + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "PHPMailer\\Test\\": "test/" + } + }, + "license": "LGPL-2.1-only", + "scripts": { + "check": "./vendor/bin/phpcs", + "test": "./vendor/bin/phpunit --no-coverage", + "coverage": "./vendor/bin/phpunit", + "lint": [ + "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . --show-deprecated -e php,phps --exclude vendor --exclude .git --exclude build" + ] + } +} diff --git a/src/vendor/phpmailer/phpmailer/get_oauth_token.php b/src/vendor/phpmailer/phpmailer/get_oauth_token.php new file mode 100644 index 00000000..cda0445c --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/get_oauth_token.php @@ -0,0 +1,182 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +/** + * Get an OAuth2 token from an OAuth2 provider. + * * Install this script on your server so that it's accessible + * as [https/http]:////get_oauth_token.php + * e.g.: http://localhost/phpmailer/get_oauth_token.php + * * Ensure dependencies are installed with 'composer install' + * * Set up an app in your Google/Yahoo/Microsoft account + * * Set the script address as the app's redirect URL + * If no refresh token is obtained when running this file, + * revoke access to your app and run the script again. + */ + +namespace PHPMailer\PHPMailer; + +/** + * Aliases for League Provider Classes + * Make sure you have added these to your composer.json and run `composer install` + * Plenty to choose from here: + * @see http://oauth2-client.thephpleague.com/providers/thirdparty/ + */ +//@see https://github.com/thephpleague/oauth2-google +use League\OAuth2\Client\Provider\Google; +//@see https://packagist.org/packages/hayageek/oauth2-yahoo +use Hayageek\OAuth2\Client\Provider\Yahoo; +//@see https://github.com/stevenmaguire/oauth2-microsoft +use Stevenmaguire\OAuth2\Client\Provider\Microsoft; +//@see https://github.com/greew/oauth2-azure-provider +use Greew\OAuth2\Client\Provider\Azure; + +if (!isset($_GET['code']) && !isset($_POST['provider'])) { + ?> + + +
+

Select Provider

+ +
+ +
+ +
+ +
+

Enter id and secret

+

These details are obtained by setting up an app in your provider's developer console. +

+

ClientId:

+

ClientSecret:

+

TenantID (only relevant for Azure):

+ +
+ + + $clientId, + 'clientSecret' => $clientSecret, + 'redirectUri' => $redirectUri, + 'accessType' => 'offline' +]; + +$options = []; +$provider = null; + +switch ($providerName) { + case 'Google': + $provider = new Google($params); + $options = [ + 'scope' => [ + 'https://mail.google.com/' + ] + ]; + break; + case 'Yahoo': + $provider = new Yahoo($params); + break; + case 'Microsoft': + $provider = new Microsoft($params); + $options = [ + 'scope' => [ + 'wl.imap', + 'wl.offline_access' + ] + ]; + break; + case 'Azure': + $params['tenantId'] = $tenantId; + + $provider = new Azure($params); + $options = [ + 'scope' => [ + 'https://outlook.office.com/SMTP.Send', + 'offline_access' + ] + ]; + break; +} + +if (null === $provider) { + exit('Provider missing'); +} + +if (!isset($_GET['code'])) { + //If we don't have an authorization code then get one + $authUrl = $provider->getAuthorizationUrl($options); + $_SESSION['oauth2state'] = $provider->getState(); + header('Location: ' . $authUrl); + exit; + //Check given state against previously stored one to mitigate CSRF attack +} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { + unset($_SESSION['oauth2state']); + unset($_SESSION['provider']); + exit('Invalid state'); +} else { + unset($_SESSION['provider']); + //Try to get an access token (using the authorization code grant) + $token = $provider->getAccessToken( + 'authorization_code', + [ + 'code' => $_GET['code'] + ] + ); + //Use this to interact with an API on the users behalf + //Use this to get a new access token if the old one expires + echo 'Refresh Token: ', $token->getRefreshToken(); +} diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php new file mode 100644 index 00000000..0b2a72d5 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php @@ -0,0 +1,26 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'خطأ SMTP : لا يمكن تأكيد الهوية.'; +$PHPMAILER_LANG['connect_host'] = 'خطأ SMTP: لا يمكن الاتصال بالخادم SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'خطأ SMTP: لم يتم قبول المعلومات .'; +$PHPMAILER_LANG['empty_message'] = 'نص الرسالة فارغ'; +$PHPMAILER_LANG['encoding'] = 'ترميز غير معروف: '; +$PHPMAILER_LANG['execute'] = 'لا يمكن تنفيذ : '; +$PHPMAILER_LANG['file_access'] = 'لا يمكن الوصول للملف: '; +$PHPMAILER_LANG['file_open'] = 'خطأ في الملف: لا يمكن فتحه: '; +$PHPMAILER_LANG['from_failed'] = 'خطأ على مستوى عنوان المرسل : '; +$PHPMAILER_LANG['instantiate'] = 'لا يمكن توفير خدمة البريد.'; +$PHPMAILER_LANG['invalid_address'] = 'الإرسال غير ممكن لأن عنوان البريد الإلكتروني غير صالح: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' برنامج الإرسال غير مدعوم.'; +$PHPMAILER_LANG['provide_address'] = 'يجب توفير عنوان البريد الإلكتروني لمستلم واحد على الأقل.'; +$PHPMAILER_LANG['recipients_failed'] = 'خطأ SMTP: الأخطاء التالية فشل في الارسال لكل من : '; +$PHPMAILER_LANG['signing'] = 'خطأ في التوقيع: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() غير ممكن.'; +$PHPMAILER_LANG['smtp_error'] = 'خطأ على مستوى الخادم SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'لا يمكن تعيين أو إعادة تعيين متغير: '; +$PHPMAILER_LANG['extension_missing'] = 'الإضافة غير موجودة: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php new file mode 100644 index 00000000..327dfbaf --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php @@ -0,0 +1,35 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP ত্ৰুটি: প্ৰমাণীকৰণ কৰিব নোৱাৰি'; +$PHPMAILER_LANG['buggy_php'] = 'আপোনাৰ PHP সংস্কৰণ এটা বাগৰ দ্বাৰা প্ৰভাৱিত হয় যাৰ ফলত নষ্ট বাৰ্তা হব পাৰে । ইয়াক সমাধান কৰিবলে, প্ৰেৰণ কৰিবলে SMTP ব্যৱহাৰ কৰক, আপোনাৰ php.ini ত mail.add_x_header বিকল্প নিষ্ক্ৰিয় কৰক, MacOS বা Linux লৈ সলনি কৰক, বা আপোনাৰ PHP সংস্কৰণ 7.0.17+ বা 7.1.3+ লৈ সলনি কৰক ।'; +$PHPMAILER_LANG['connect_host'] = 'SMTP ত্ৰুটি: SMTP চাৰ্ভাৰৰ সৈতে সংযোগ কৰিবলে অক্ষম'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP ত্ৰুটি: তথ্য গ্ৰহণ কৰা হোৱা নাই'; +$PHPMAILER_LANG['empty_message'] = 'বাৰ্তাৰ মূখ্য অংশ খালী।'; +$PHPMAILER_LANG['encoding'] = 'অজ্ঞাত এনকোডিং: '; +$PHPMAILER_LANG['execute'] = 'এক্সিকিউট কৰিব নোৱাৰি: '; +$PHPMAILER_LANG['extension_missing'] = 'সম্প্ৰসাৰণ নোহোৱা হৈছে: '; +$PHPMAILER_LANG['file_access'] = 'ফাইল অভিগম কৰিবলে অক্ষম: '; +$PHPMAILER_LANG['file_open'] = 'ফাইল ত্ৰুটি: ফাইল খোলিবলৈ অক্ষম: '; +$PHPMAILER_LANG['from_failed'] = 'নিম্নলিখিত প্ৰেৰকৰ ঠিকনা(সমূহ) ব্যৰ্থ: '; +$PHPMAILER_LANG['instantiate'] = 'মেইল ফাংচনৰ এটা উদাহৰণ সৃষ্টি কৰিবলে অক্ষম'; +$PHPMAILER_LANG['invalid_address'] = 'প্ৰেৰণ কৰিব নোৱাৰি: অবৈধ ইমেইল ঠিকনা: '; +$PHPMAILER_LANG['invalid_header'] = 'অবৈধ হেডাৰৰ নাম বা মান'; +$PHPMAILER_LANG['invalid_hostentry'] = 'অবৈধ হোষ্টেন্ট্ৰি: '; +$PHPMAILER_LANG['invalid_host'] = 'অবৈধ হস্ট:'; +$PHPMAILER_LANG['mailer_not_supported'] = 'মেইলাৰ সমৰ্থিত নহয়।'; +$PHPMAILER_LANG['provide_address'] = 'আপুনি অন্ততঃ এটা গন্তব্য ইমেইল ঠিকনা দিব লাগিব'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP ত্ৰুটি: নিম্নলিখিত গন্তব্যস্থানসমূহ ব্যৰ্থ: '; +$PHPMAILER_LANG['signing'] = 'স্বাক্ষৰ কৰাত ব্যৰ্থ: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP কড: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'অতিৰিক্ত SMTP তথ্য: '; +$PHPMAILER_LANG['smtp_detail'] = 'বিৱৰণ:'; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP সংযোগ() ব্যৰ্থ'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP চাৰ্ভাৰৰ ত্ৰুটি: '; +$PHPMAILER_LANG['variable_set'] = 'চলক নিৰ্ধাৰণ কৰিব পৰা নগল: '; +$PHPMAILER_LANG['extension_missing'] = 'অনুপস্থিত সম্প্ৰসাৰণ: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php new file mode 100644 index 00000000..552167ef --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Greška: Neuspjela prijava.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Greška: Nije moguće spojiti se sa SMTP serverom.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Greška: Podatci nisu prihvaćeni.'; +$PHPMAILER_LANG['empty_message'] = 'Sadržaj poruke je prazan.'; +$PHPMAILER_LANG['encoding'] = 'Nepoznata kriptografija: '; +$PHPMAILER_LANG['execute'] = 'Nije moguće izvršiti naredbu: '; +$PHPMAILER_LANG['file_access'] = 'Nije moguće pristupiti datoteci: '; +$PHPMAILER_LANG['file_open'] = 'Nije moguće otvoriti datoteku: '; +$PHPMAILER_LANG['from_failed'] = 'SMTP Greška: Slanje sa navedenih e-mail adresa nije uspjelo: '; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Greška: Slanje na navedene e-mail adrese nije uspjelo: '; +$PHPMAILER_LANG['instantiate'] = 'Ne mogu pokrenuti mail funkcionalnost.'; +$PHPMAILER_LANG['invalid_address'] = 'E-mail nije poslan. Neispravna e-mail adresa: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer nije podržan.'; +$PHPMAILER_LANG['provide_address'] = 'Definišite barem jednu adresu primaoca.'; +$PHPMAILER_LANG['signing'] = 'Greška prilikom prijave: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Spajanje na SMTP server nije uspjelo.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP greška: '; +$PHPMAILER_LANG['variable_set'] = 'Nije moguće postaviti varijablu ili je vratiti nazad: '; +$PHPMAILER_LANG['extension_missing'] = 'Nedostaje ekstenzija: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php new file mode 100644 index 00000000..9e92ddaa --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Памылка SMTP: памылка ідэнтыфікацыі.'; +$PHPMAILER_LANG['connect_host'] = 'Памылка SMTP: нельга ўстанавіць сувязь з SMTP-серверам.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Памылка SMTP: звесткі непрынятыя.'; +$PHPMAILER_LANG['empty_message'] = 'Пустое паведамленне.'; +$PHPMAILER_LANG['encoding'] = 'Невядомая кадыроўка тэксту: '; +$PHPMAILER_LANG['execute'] = 'Нельга выканаць каманду: '; +$PHPMAILER_LANG['file_access'] = 'Няма доступу да файла: '; +$PHPMAILER_LANG['file_open'] = 'Нельга адкрыць файл: '; +$PHPMAILER_LANG['from_failed'] = 'Няправільны адрас адпраўніка: '; +$PHPMAILER_LANG['instantiate'] = 'Нельга прымяніць функцыю mail().'; +$PHPMAILER_LANG['invalid_address'] = 'Нельга даслаць паведамленне, няправільны email атрымальніка: '; +$PHPMAILER_LANG['provide_address'] = 'Запоўніце, калі ласка, правільны email атрымальніка.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' - паштовы сервер не падтрымліваецца.'; +$PHPMAILER_LANG['recipients_failed'] = 'Памылка SMTP: няправільныя атрымальнікі: '; +$PHPMAILER_LANG['signing'] = 'Памылка подпісу паведамлення: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Памылка сувязі з SMTP-серверам.'; +$PHPMAILER_LANG['smtp_error'] = 'Памылка SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Нельга ўстанавіць або перамяніць значэнне пераменнай: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php new file mode 100644 index 00000000..c41f675d --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP грешка: Не може да се удостовери пред сървъра.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP грешка: Не може да се свърже с SMTP хоста.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP грешка: данните не са приети.'; +$PHPMAILER_LANG['empty_message'] = 'Съдържанието на съобщението е празно'; +$PHPMAILER_LANG['encoding'] = 'Неизвестно кодиране: '; +$PHPMAILER_LANG['execute'] = 'Не може да се изпълни: '; +$PHPMAILER_LANG['file_access'] = 'Няма достъп до файл: '; +$PHPMAILER_LANG['file_open'] = 'Файлова грешка: Не може да се отвори файл: '; +$PHPMAILER_LANG['from_failed'] = 'Следните адреси за подател са невалидни: '; +$PHPMAILER_LANG['instantiate'] = 'Не може да се инстанцира функцията mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Невалиден адрес: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' - пощенски сървър не се поддържа.'; +$PHPMAILER_LANG['provide_address'] = 'Трябва да предоставите поне един email адрес за получател.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP грешка: Следните адреси за Получател са невалидни: '; +$PHPMAILER_LANG['signing'] = 'Грешка при подписване: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP провален connect().'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP сървърна грешка: '; +$PHPMAILER_LANG['variable_set'] = 'Не може да се установи или възстанови променлива: '; +$PHPMAILER_LANG['extension_missing'] = 'Липсва разширение: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php new file mode 100644 index 00000000..47365108 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php @@ -0,0 +1,35 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP ত্রুটি: প্রমাণীকরণ করতে অক্ষম৷'; +$PHPMAILER_LANG['buggy_php'] = 'আপনার PHP সংস্করণ একটি বাগ দ্বারা প্রভাবিত হয় যার ফলে দূষিত বার্তা হতে পারে। এটি ঠিক করতে, পাঠাতে SMTP ব্যবহার করুন, আপনার php.ini এ mail.add_x_header বিকল্পটি নিষ্ক্রিয় করুন, MacOS বা Linux-এ স্যুইচ করুন, অথবা আপনার PHP সংস্করণকে 7.0.17+ বা 7.1.3+ এ পরিবর্তন করুন।'; +$PHPMAILER_LANG['connect_host'] = 'SMTP ত্রুটি: SMTP সার্ভারের সাথে সংযোগ করতে অক্ষম৷'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP ত্রুটি: ডেটা গ্রহণ করা হয়নি৷'; +$PHPMAILER_LANG['empty_message'] = 'বার্তার অংশটি খালি।'; +$PHPMAILER_LANG['encoding'] = 'অজানা এনকোডিং: '; +$PHPMAILER_LANG['execute'] = 'নির্বাহ করতে অক্ষম: '; +$PHPMAILER_LANG['extension_missing'] = 'এক্সটেনশন অনুপস্থিত:'; +$PHPMAILER_LANG['file_access'] = 'ফাইল অ্যাক্সেস করতে অক্ষম: '; +$PHPMAILER_LANG['file_open'] = 'ফাইল ত্রুটি: ফাইল খুলতে অক্ষম: '; +$PHPMAILER_LANG['from_failed'] = 'নিম্নলিখিত প্রেরকের ঠিকানা(গুলি) ব্যর্থ হয়েছে: '; +$PHPMAILER_LANG['instantiate'] = 'মেল ফাংশনের একটি উদাহরণ তৈরি করতে অক্ষম৷'; +$PHPMAILER_LANG['invalid_address'] = 'পাঠাতে অক্ষম: অবৈধ ইমেল ঠিকানা: '; +$PHPMAILER_LANG['invalid_header'] = 'অবৈধ হেডার নাম বা মান'; +$PHPMAILER_LANG['invalid_hostentry'] = 'অবৈধ হোস্টেন্ট্রি: '; +$PHPMAILER_LANG['invalid_host'] = 'অবৈধ হোস্ট:'; +$PHPMAILER_LANG['mailer_not_supported'] = 'মেইলার সমর্থিত নয়।'; +$PHPMAILER_LANG['provide_address'] = 'আপনাকে অবশ্যই অন্তত একটি গন্তব্য ইমেল ঠিকানা প্রদান করতে হবে৷'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP ত্রুটি: নিম্নলিখিত গন্তব্যগুলি ব্যর্থ হয়েছে: '; +$PHPMAILER_LANG['signing'] = 'স্বাক্ষর করতে ব্যর্থ হয়েছে: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP কোড: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'অতিরিক্ত SMTP তথ্য:'; +$PHPMAILER_LANG['smtp_detail'] = 'বর্ণনা: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP সংযোগ() ব্যর্থ হয়েছে৷'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP সার্ভার ত্রুটি: '; +$PHPMAILER_LANG['variable_set'] = 'পরিবর্তনশীল সেট করা যায়নি: '; +$PHPMAILER_LANG['extension_missing'] = 'অনুপস্থিত এক্সটেনশন: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php new file mode 100644 index 00000000..34684855 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Error SMTP: No s’ha pogut autenticar.'; +$PHPMAILER_LANG['connect_host'] = 'Error SMTP: No es pot connectar al servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Error SMTP: Dades no acceptades.'; +$PHPMAILER_LANG['empty_message'] = 'El cos del missatge està buit.'; +$PHPMAILER_LANG['encoding'] = 'Codificació desconeguda: '; +$PHPMAILER_LANG['execute'] = 'No es pot executar: '; +$PHPMAILER_LANG['file_access'] = 'No es pot accedir a l’arxiu: '; +$PHPMAILER_LANG['file_open'] = 'Error d’Arxiu: No es pot obrir l’arxiu: '; +$PHPMAILER_LANG['from_failed'] = 'La(s) següent(s) adreces de remitent han fallat: '; +$PHPMAILER_LANG['instantiate'] = 'No s’ha pogut crear una instància de la funció Mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Adreça d’email invalida: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer no està suportat'; +$PHPMAILER_LANG['provide_address'] = 'S’ha de proveir almenys una adreça d’email com a destinatari.'; +$PHPMAILER_LANG['recipients_failed'] = 'Error SMTP: Els següents destinataris han fallat: '; +$PHPMAILER_LANG['signing'] = 'Error al signar: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Ha fallat el SMTP Connect().'; +$PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'No s’ha pogut establir o restablir la variable: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php new file mode 100644 index 00000000..e770a1a2 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php @@ -0,0 +1,28 @@ + + * Rewrite and extension of the work by Mikael Stokkebro + * + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP fejl: Login mislykkedes.'; +$PHPMAILER_LANG['buggy_php'] = 'Din version af PHP er berørt af en fejl, som gør at dine beskeder muligvis vises forkert. For at rette dette kan du skifte til SMTP, slå mail.add_x_header headeren i din php.ini fil fra, skifte til MacOS eller Linux eller opgradere din version af PHP til 7.0.17+ eller 7.1.3+.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP fejl: Forbindelse til SMTP serveren kunne ikke oprettes.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP fejl: Data blev ikke accepteret.'; +$PHPMAILER_LANG['empty_message'] = 'Meddelelsen er uden indhold'; +$PHPMAILER_LANG['encoding'] = 'Ukendt encode-format: '; +$PHPMAILER_LANG['execute'] = 'Kunne ikke afvikle: '; +$PHPMAILER_LANG['extension_missing'] = 'Udvidelse mangler: '; +$PHPMAILER_LANG['file_access'] = 'Kunne ikke tilgå filen: '; +$PHPMAILER_LANG['file_open'] = 'Fil fejl: Kunne ikke åbne filen: '; +$PHPMAILER_LANG['from_failed'] = 'Følgende afsenderadresse er forkert: '; +$PHPMAILER_LANG['instantiate'] = 'Email funktionen kunne ikke initialiseres.'; +$PHPMAILER_LANG['invalid_address'] = 'Udgyldig adresse: '; +$PHPMAILER_LANG['invalid_header'] = 'Ugyldig header navn eller værdi'; +$PHPMAILER_LANG['invalid_hostentry'] = 'Ugyldig hostentry: '; +$PHPMAILER_LANG['invalid_host'] = 'Ugyldig vært: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer understøttes ikke.'; +$PHPMAILER_LANG['provide_address'] = 'Indtast mindst en modtagers email adresse.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP fejl: Følgende modtagere fejlede: '; +$PHPMAILER_LANG['signing'] = 'Signeringsfejl: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP kode: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Yderligere SMTP info: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() fejlede.'; +$PHPMAILER_LANG['smtp_detail'] = 'Detalje: '; +$PHPMAILER_LANG['smtp_error'] = 'SMTP server fejl: '; +$PHPMAILER_LANG['variable_set'] = 'Kunne ikke definere eller nulstille variablen: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php new file mode 100644 index 00000000..e7e59d2b --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php @@ -0,0 +1,28 @@ + + * @author Crystopher Glodzienski Cardoso + */ + +$PHPMAILER_LANG['authenticate'] = 'Error SMTP: Imposible autentificar.'; +$PHPMAILER_LANG['connect_host'] = 'Error SMTP: Imposible conectar al servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Error SMTP: Datos no aceptados.'; +$PHPMAILER_LANG['empty_message'] = 'El cuerpo del mensaje está vacío.'; +$PHPMAILER_LANG['encoding'] = 'Codificación desconocida: '; +$PHPMAILER_LANG['execute'] = 'Imposible ejecutar: '; +$PHPMAILER_LANG['file_access'] = 'Imposible acceder al archivo: '; +$PHPMAILER_LANG['file_open'] = 'Error de Archivo: Imposible abrir el archivo: '; +$PHPMAILER_LANG['from_failed'] = 'La(s) siguiente(s) direcciones de remitente fallaron: '; +$PHPMAILER_LANG['instantiate'] = 'Imposible crear una instancia de la función Mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Imposible enviar: dirección de email inválido: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer no está soportado.'; +$PHPMAILER_LANG['provide_address'] = 'Debe proporcionar al menos una dirección de email de destino.'; +$PHPMAILER_LANG['recipients_failed'] = 'Error SMTP: Los siguientes destinos fallaron: '; +$PHPMAILER_LANG['signing'] = 'Error al firmar: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falló.'; +$PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'No se pudo configurar la variable: '; +$PHPMAILER_LANG['extension_missing'] = 'Extensión faltante: '; +$PHPMAILER_LANG['smtp_code'] = 'Código del servidor SMTP: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Información adicional del servidor SMTP: '; +$PHPMAILER_LANG['invalid_header'] = 'Nombre o valor de encabezado no válido'; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php new file mode 100644 index 00000000..93addc9e --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php @@ -0,0 +1,28 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Viga: Autoriseerimise viga.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Viga: Ei õnnestunud luua ühendust SMTP serveriga.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Viga: Vigased andmed.'; +$PHPMAILER_LANG['empty_message'] = 'Tühi kirja sisu'; +$PHPMAILER_LANG["encoding"] = 'Tundmatu kodeering: '; +$PHPMAILER_LANG['execute'] = 'Tegevus ebaõnnestus: '; +$PHPMAILER_LANG['file_access'] = 'Pole piisavalt õiguseid järgneva faili avamiseks: '; +$PHPMAILER_LANG['file_open'] = 'Faili Viga: Faili avamine ebaõnnestus: '; +$PHPMAILER_LANG['from_failed'] = 'Järgnev saatja e-posti aadress on vigane: '; +$PHPMAILER_LANG['instantiate'] = 'mail funktiooni käivitamine ebaõnnestus.'; +$PHPMAILER_LANG['invalid_address'] = 'Saatmine peatatud, e-posti address vigane: '; +$PHPMAILER_LANG['provide_address'] = 'Te peate määrama vähemalt ühe saaja e-posti aadressi.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' maileri tugi puudub.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Viga: Järgnevate saajate e-posti aadressid on vigased: '; +$PHPMAILER_LANG["signing"] = 'Viga allkirjastamisel: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() ebaõnnestus.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP serveri viga: '; +$PHPMAILER_LANG['variable_set'] = 'Ei õnnestunud määrata või lähtestada muutujat: '; +$PHPMAILER_LANG['extension_missing'] = 'Nõutud laiendus on puudu: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php new file mode 100644 index 00000000..295a47f9 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php @@ -0,0 +1,28 @@ + + * @author Mohammad Hossein Mojtahedi + */ + +$PHPMAILER_LANG['authenticate'] = 'خطای SMTP: احراز هویت با شکست مواجه شد.'; +$PHPMAILER_LANG['connect_host'] = 'خطای SMTP: اتصال به سرور SMTP برقرار نشد.'; +$PHPMAILER_LANG['data_not_accepted'] = 'خطای SMTP: داده‌ها نا‌درست هستند.'; +$PHPMAILER_LANG['empty_message'] = 'بخش متن پیام خالی است.'; +$PHPMAILER_LANG['encoding'] = 'کد‌گذاری نا‌شناخته: '; +$PHPMAILER_LANG['execute'] = 'امکان اجرا وجود ندارد: '; +$PHPMAILER_LANG['file_access'] = 'امکان دسترسی به فایل وجود ندارد: '; +$PHPMAILER_LANG['file_open'] = 'خطای File: امکان بازکردن فایل وجود ندارد: '; +$PHPMAILER_LANG['from_failed'] = 'آدرس فرستنده اشتباه است: '; +$PHPMAILER_LANG['instantiate'] = 'امکان معرفی تابع ایمیل وجود ندارد.'; +$PHPMAILER_LANG['invalid_address'] = 'آدرس ایمیل معتبر نیست: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer پشتیبانی نمی‌شود.'; +$PHPMAILER_LANG['provide_address'] = 'باید حداقل یک آدرس گیرنده وارد کنید.'; +$PHPMAILER_LANG['recipients_failed'] = 'خطای SMTP: ارسال به آدرس گیرنده با خطا مواجه شد: '; +$PHPMAILER_LANG['signing'] = 'خطا در امضا: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'خطا در اتصال به SMTP.'; +$PHPMAILER_LANG['smtp_error'] = 'خطا در SMTP Server: '; +$PHPMAILER_LANG['variable_set'] = 'امکان ارسال یا ارسال مجدد متغیر‌ها وجود ندارد: '; +$PHPMAILER_LANG['extension_missing'] = 'افزونه موجود نیست: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php new file mode 100644 index 00000000..6d1e6373 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP feilur: Kundi ikki góðkenna.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP feilur: Kundi ikki knýta samband við SMTP vert.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP feilur: Data ikki góðkent.'; +//$PHPMAILER_LANG['empty_message'] = 'Message body empty'; +$PHPMAILER_LANG['encoding'] = 'Ókend encoding: '; +$PHPMAILER_LANG['execute'] = 'Kundi ikki útføra: '; +$PHPMAILER_LANG['file_access'] = 'Kundi ikki tilganga fílu: '; +$PHPMAILER_LANG['file_open'] = 'Fílu feilur: Kundi ikki opna fílu: '; +$PHPMAILER_LANG['from_failed'] = 'fylgjandi Frá/From adressa miseydnaðist: '; +$PHPMAILER_LANG['instantiate'] = 'Kuni ikki instantiera mail funktión.'; +//$PHPMAILER_LANG['invalid_address'] = 'Invalid address: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' er ikki supporterað.'; +$PHPMAILER_LANG['provide_address'] = 'Tú skal uppgeva minst móttakara-emailadressu(r).'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Feilur: Fylgjandi móttakarar miseydnaðust: '; +//$PHPMAILER_LANG['signing'] = 'Signing Error: '; +//$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; +//$PHPMAILER_LANG['smtp_error'] = 'SMTP server error: '; +//$PHPMAILER_LANG['variable_set'] = 'Cannot set or reset variable: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php new file mode 100644 index 00000000..0d367fcf --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php @@ -0,0 +1,37 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Erro SMTP: Non puido ser autentificado.'; +$PHPMAILER_LANG['connect_host'] = 'Erro SMTP: Non puido conectar co servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Erro SMTP: Datos non aceptados.'; +$PHPMAILER_LANG['empty_message'] = 'Corpo da mensaxe vacía'; +$PHPMAILER_LANG['encoding'] = 'Codificación descoñecida: '; +$PHPMAILER_LANG['execute'] = 'Non puido ser executado: '; +$PHPMAILER_LANG['file_access'] = 'Nob puido acceder ó arquivo: '; +$PHPMAILER_LANG['file_open'] = 'Erro de Arquivo: No puido abrir o arquivo: '; +$PHPMAILER_LANG['from_failed'] = 'A(s) seguinte(s) dirección(s) de remitente(s) deron erro: '; +$PHPMAILER_LANG['instantiate'] = 'Non puido crear unha instancia da función Mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Non puido envia-lo correo: dirección de email inválida: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer non está soportado.'; +$PHPMAILER_LANG['provide_address'] = 'Debe engadir polo menos unha dirección de email coma destino.'; +$PHPMAILER_LANG['recipients_failed'] = 'Erro SMTP: Os seguintes destinos fallaron: '; +$PHPMAILER_LANG['signing'] = 'Erro ó firmar: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() fallou.'; +$PHPMAILER_LANG['smtp_error'] = 'Erro do servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Non puidemos axustar ou reaxustar a variábel: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php new file mode 100644 index 00000000..b123aa5f --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'שגיאת SMTP: פעולת האימות נכשלה.'; +$PHPMAILER_LANG['connect_host'] = 'שגיאת SMTP: לא הצלחתי להתחבר לשרת SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'שגיאת SMTP: מידע לא התקבל.'; +$PHPMAILER_LANG['empty_message'] = 'גוף ההודעה ריק'; +$PHPMAILER_LANG['invalid_address'] = 'כתובת שגויה: '; +$PHPMAILER_LANG['encoding'] = 'קידוד לא מוכר: '; +$PHPMAILER_LANG['execute'] = 'לא הצלחתי להפעיל את: '; +$PHPMAILER_LANG['file_access'] = 'לא ניתן לגשת לקובץ: '; +$PHPMAILER_LANG['file_open'] = 'שגיאת קובץ: לא ניתן לגשת לקובץ: '; +$PHPMAILER_LANG['from_failed'] = 'כתובות הנמענים הבאות נכשלו: '; +$PHPMAILER_LANG['instantiate'] = 'לא הצלחתי להפעיל את פונקציית המייל.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' אינה נתמכת.'; +$PHPMAILER_LANG['provide_address'] = 'חובה לספק לפחות כתובת אחת של מקבל המייל.'; +$PHPMAILER_LANG['recipients_failed'] = 'שגיאת SMTP: הנמענים הבאים נכשלו: '; +$PHPMAILER_LANG['signing'] = 'שגיאת חתימה: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; +$PHPMAILER_LANG['smtp_error'] = 'שגיאת שרת SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'לא ניתן לקבוע או לשנות את המשתנה: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php new file mode 100644 index 00000000..d2856e05 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php @@ -0,0 +1,35 @@ + + * Rewrite and extension of the work by Jayanti Suthar + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP त्रुटि: प्रामाणिकता की जांच नहीं हो सका। '; +$PHPMAILER_LANG['buggy_php'] = 'PHP का आपका संस्करण एक बग से प्रभावित है जिसके परिणामस्वरूप संदेश दूषित हो सकते हैं. इसे ठीक करने हेतु, भेजने के लिए SMTP का उपयोग करे, अपने php.ini में mail.add_x_header विकल्प को अक्षम करें, MacOS या Linux पर जाए, या अपने PHP संस्करण को 7.0.17+ या 7.1.3+ बदले.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP त्रुटि: SMTP सर्वर से कनेक्ट नहीं हो सका। '; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP त्रुटि: डेटा स्वीकार नहीं किया जाता है। '; +$PHPMAILER_LANG['empty_message'] = 'संदेश खाली है। '; +$PHPMAILER_LANG['encoding'] = 'अज्ञात एन्कोडिंग प्रकार। '; +$PHPMAILER_LANG['execute'] = 'आदेश को निष्पादित करने में विफल। '; +$PHPMAILER_LANG['extension_missing'] = 'एक्सटेन्षन गायब है: '; +$PHPMAILER_LANG['file_access'] = 'फ़ाइल उपलब्ध नहीं है। '; +$PHPMAILER_LANG['file_open'] = 'फ़ाइल त्रुटि: फाइल को खोला नहीं जा सका। '; +$PHPMAILER_LANG['from_failed'] = 'प्रेषक का पता गलत है। '; +$PHPMAILER_LANG['instantiate'] = 'मेल फ़ंक्शन कॉल नहीं कर सकता है।'; +$PHPMAILER_LANG['invalid_address'] = 'पता गलत है। '; +$PHPMAILER_LANG['invalid_header'] = 'अमान्य हेडर नाम या मान'; +$PHPMAILER_LANG['invalid_hostentry'] = 'अमान्य hostentry: '; +$PHPMAILER_LANG['invalid_host'] = 'अमान्य होस्ट: '; +$PHPMAILER_LANG['mailer_not_supported'] = 'मेल सर्वर के साथ काम नहीं करता है। '; +$PHPMAILER_LANG['provide_address'] = 'आपको कम से कम एक प्राप्तकर्ता का ई-मेल पता प्रदान करना होगा।'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP त्रुटि: निम्न प्राप्तकर्ताओं को पते भेजने में विफल। '; +$PHPMAILER_LANG['signing'] = 'साइनअप त्रुटि: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP कोड: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'अतिरिक्त SMTP जानकारी: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP का connect () फ़ंक्शन विफल हुआ। '; +$PHPMAILER_LANG['smtp_detail'] = 'विवरण: '; +$PHPMAILER_LANG['smtp_error'] = 'SMTP सर्वर त्रुटि। '; +$PHPMAILER_LANG['variable_set'] = 'चर को बना या संशोधित नहीं किया जा सकता। '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php new file mode 100644 index 00000000..cacb6c37 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Greška: Neuspjela autentikacija.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Greška: Ne mogu se spojiti na SMTP poslužitelj.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Greška: Podatci nisu prihvaćeni.'; +$PHPMAILER_LANG['empty_message'] = 'Sadržaj poruke je prazan.'; +$PHPMAILER_LANG['encoding'] = 'Nepoznati encoding: '; +$PHPMAILER_LANG['execute'] = 'Nije moguće izvršiti naredbu: '; +$PHPMAILER_LANG['file_access'] = 'Nije moguće pristupiti datoteci: '; +$PHPMAILER_LANG['file_open'] = 'Nije moguće otvoriti datoteku: '; +$PHPMAILER_LANG['from_failed'] = 'SMTP Greška: Slanje s navedenih e-mail adresa nije uspjelo: '; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Greška: Slanje na navedenih e-mail adresa nije uspjelo: '; +$PHPMAILER_LANG['instantiate'] = 'Ne mogu pokrenuti mail funkcionalnost.'; +$PHPMAILER_LANG['invalid_address'] = 'E-mail nije poslan. Neispravna e-mail adresa: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer nije podržan.'; +$PHPMAILER_LANG['provide_address'] = 'Definirajte barem jednu adresu primatelja.'; +$PHPMAILER_LANG['signing'] = 'Greška prilikom prijave: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Spajanje na SMTP poslužitelj nije uspjelo.'; +$PHPMAILER_LANG['smtp_error'] = 'Greška SMTP poslužitelja: '; +$PHPMAILER_LANG['variable_set'] = 'Ne mogu postaviti varijablu niti ju vratiti nazad: '; +$PHPMAILER_LANG['extension_missing'] = 'Nedostaje proširenje: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php new file mode 100644 index 00000000..e6b58b0d --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP -ի սխալ: չհաջողվեց ստուգել իսկությունը.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP -ի սխալ: չհաջողվեց կապ հաստատել SMTP սերվերի հետ.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP -ի սխալ: տվյալները ընդունված չեն.'; +$PHPMAILER_LANG['empty_message'] = 'Հաղորդագրությունը դատարկ է'; +$PHPMAILER_LANG['encoding'] = 'Կոդավորման անհայտ տեսակ: '; +$PHPMAILER_LANG['execute'] = 'Չհաջողվեց իրականացնել հրամանը: '; +$PHPMAILER_LANG['file_access'] = 'Ֆայլը հասանելի չէ: '; +$PHPMAILER_LANG['file_open'] = 'Ֆայլի սխալ: ֆայլը չհաջողվեց բացել: '; +$PHPMAILER_LANG['from_failed'] = 'Ուղարկողի հետևյալ հասցեն սխալ է: '; +$PHPMAILER_LANG['instantiate'] = 'Հնարավոր չէ կանչել mail ֆունկցիան.'; +$PHPMAILER_LANG['invalid_address'] = 'Հասցեն սխալ է: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' փոստային սերվերի հետ չի աշխատում.'; +$PHPMAILER_LANG['provide_address'] = 'Անհրաժեշտ է տրամադրել գոնե մեկ ստացողի e-mail հասցե.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP -ի սխալ: չի հաջողվել ուղարկել հետևյալ ստացողների հասցեներին: '; +$PHPMAILER_LANG['signing'] = 'Ստորագրման սխալ: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP -ի connect() ֆունկցիան չի հաջողվել'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP սերվերի սխալ: '; +$PHPMAILER_LANG['variable_set'] = 'Չի հաջողվում ստեղծել կամ վերափոխել փոփոխականը: '; +$PHPMAILER_LANG['extension_missing'] = 'Հավելվածը բացակայում է: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php new file mode 100644 index 00000000..212a11f1 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php @@ -0,0 +1,31 @@ + + * @author @januridp + * @author Ian Mustafa + */ + +$PHPMAILER_LANG['authenticate'] = 'Kesalahan SMTP: Tidak dapat mengotentikasi.'; +$PHPMAILER_LANG['connect_host'] = 'Kesalahan SMTP: Tidak dapat terhubung ke host SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Kesalahan SMTP: Data tidak diterima.'; +$PHPMAILER_LANG['empty_message'] = 'Isi pesan kosong'; +$PHPMAILER_LANG['encoding'] = 'Pengkodean karakter tidak dikenali: '; +$PHPMAILER_LANG['execute'] = 'Tidak dapat menjalankan proses: '; +$PHPMAILER_LANG['file_access'] = 'Tidak dapat mengakses berkas: '; +$PHPMAILER_LANG['file_open'] = 'Kesalahan Berkas: Berkas tidak dapat dibuka: '; +$PHPMAILER_LANG['from_failed'] = 'Alamat pengirim berikut mengakibatkan kesalahan: '; +$PHPMAILER_LANG['instantiate'] = 'Tidak dapat menginisialisasi fungsi surel.'; +$PHPMAILER_LANG['invalid_address'] = 'Gagal terkirim, alamat surel tidak sesuai: '; +$PHPMAILER_LANG['invalid_hostentry'] = 'Gagal terkirim, entri host tidak sesuai: '; +$PHPMAILER_LANG['invalid_host'] = 'Gagal terkirim, host tidak sesuai: '; +$PHPMAILER_LANG['provide_address'] = 'Harus tersedia minimal satu alamat tujuan'; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer tidak didukung'; +$PHPMAILER_LANG['recipients_failed'] = 'Kesalahan SMTP: Alamat tujuan berikut menyebabkan kesalahan: '; +$PHPMAILER_LANG['signing'] = 'Kesalahan dalam penandatangan SSL: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() gagal.'; +$PHPMAILER_LANG['smtp_error'] = 'Kesalahan pada pelayan SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Tidak dapat mengatur atau mengatur ulang variabel: '; +$PHPMAILER_LANG['extension_missing'] = 'Ekstensi PHP tidak tersedia: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php new file mode 100644 index 00000000..08a6b733 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php @@ -0,0 +1,28 @@ + + * @author Stefano Sabatini + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Error: Impossibile autenticarsi.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Impossibile connettersi all\'host SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Error: Dati non accettati dal server.'; +$PHPMAILER_LANG['empty_message'] = 'Il corpo del messaggio è vuoto'; +$PHPMAILER_LANG['encoding'] = 'Codifica dei caratteri sconosciuta: '; +$PHPMAILER_LANG['execute'] = 'Impossibile eseguire l\'operazione: '; +$PHPMAILER_LANG['file_access'] = 'Impossibile accedere al file: '; +$PHPMAILER_LANG['file_open'] = 'File Error: Impossibile aprire il file: '; +$PHPMAILER_LANG['from_failed'] = 'I seguenti indirizzi mittenti hanno generato errore: '; +$PHPMAILER_LANG['instantiate'] = 'Impossibile istanziare la funzione mail'; +$PHPMAILER_LANG['invalid_address'] = 'Impossibile inviare, l\'indirizzo email non è valido: '; +$PHPMAILER_LANG['provide_address'] = 'Deve essere fornito almeno un indirizzo ricevente'; +$PHPMAILER_LANG['mailer_not_supported'] = 'Mailer non supportato'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: I seguenti indirizzi destinatari hanno generato un errore: '; +$PHPMAILER_LANG['signing'] = 'Errore nella firma: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() fallita.'; +$PHPMAILER_LANG['smtp_error'] = 'Errore del server SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Impossibile impostare o resettare la variabile: '; +$PHPMAILER_LANG['extension_missing'] = 'Estensione mancante: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php new file mode 100644 index 00000000..c76f5264 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php @@ -0,0 +1,29 @@ + + * @author Yoshi Sakai + * @author Arisophy + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTPエラー: 認証できませんでした。'; +$PHPMAILER_LANG['connect_host'] = 'SMTPエラー: SMTPホストに接続できませんでした。'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTPエラー: データが受け付けられませんでした。'; +$PHPMAILER_LANG['empty_message'] = 'メール本文が空です。'; +$PHPMAILER_LANG['encoding'] = '不明なエンコーディング: '; +$PHPMAILER_LANG['execute'] = '実行できませんでした: '; +$PHPMAILER_LANG['file_access'] = 'ファイルにアクセスできません: '; +$PHPMAILER_LANG['file_open'] = 'ファイルエラー: ファイルを開けません: '; +$PHPMAILER_LANG['from_failed'] = 'Fromアドレスを登録する際にエラーが発生しました: '; +$PHPMAILER_LANG['instantiate'] = 'メール関数が正常に動作しませんでした。'; +$PHPMAILER_LANG['invalid_address'] = '不正なメールアドレス: '; +$PHPMAILER_LANG['provide_address'] = '少なくとも1つメールアドレスを 指定する必要があります。'; +$PHPMAILER_LANG['mailer_not_supported'] = ' メーラーがサポートされていません。'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTPエラー: 次の受信者アドレスに 間違いがあります: '; +$PHPMAILER_LANG['signing'] = '署名エラー: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP接続に失敗しました。'; +$PHPMAILER_LANG['smtp_error'] = 'SMTPサーバーエラー: '; +$PHPMAILER_LANG['variable_set'] = '変数が存在しません: '; +$PHPMAILER_LANG['extension_missing'] = '拡張機能が見つかりません: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php new file mode 100644 index 00000000..51fe403b --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP შეცდომა: ავტორიზაცია შეუძლებელია.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP შეცდომა: SMTP სერვერთან დაკავშირება შეუძლებელია.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP შეცდომა: მონაცემები არ იქნა მიღებული.'; +$PHPMAILER_LANG['encoding'] = 'კოდირების უცნობი ტიპი: '; +$PHPMAILER_LANG['execute'] = 'შეუძლებელია შემდეგი ბრძანების შესრულება: '; +$PHPMAILER_LANG['file_access'] = 'შეუძლებელია წვდომა ფაილთან: '; +$PHPMAILER_LANG['file_open'] = 'ფაილური სისტემის შეცდომა: არ იხსნება ფაილი: '; +$PHPMAILER_LANG['from_failed'] = 'გამგზავნის არასწორი მისამართი: '; +$PHPMAILER_LANG['instantiate'] = 'mail ფუნქციის გაშვება ვერ ხერხდება.'; +$PHPMAILER_LANG['provide_address'] = 'გთხოვთ მიუთითოთ ერთი ადრესატის e-mail მისამართი მაინც.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' - საფოსტო სერვერის მხარდაჭერა არ არის.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP შეცდომა: შემდეგ მისამართებზე გაგზავნა ვერ მოხერხდა: '; +$PHPMAILER_LANG['empty_message'] = 'შეტყობინება ცარიელია'; +$PHPMAILER_LANG['invalid_address'] = 'არ გაიგზავნა, e-mail მისამართის არასწორი ფორმატი: '; +$PHPMAILER_LANG['signing'] = 'ხელმოწერის შეცდომა: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'შეცდომა SMTP სერვერთან დაკავშირებისას'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP სერვერის შეცდომა: '; +$PHPMAILER_LANG['variable_set'] = 'შეუძლებელია შემდეგი ცვლადის შექმნა ან შეცვლა: '; +$PHPMAILER_LANG['extension_missing'] = 'ბიბლიოთეკა არ არსებობს: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php new file mode 100644 index 00000000..8c97dd94 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP 오류: 인증할 수 없습니다.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP 오류: SMTP 호스트에 접속할 수 없습니다.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 오류: 데이터가 받아들여지지 않았습니다.'; +$PHPMAILER_LANG['empty_message'] = '메세지 내용이 없습니다'; +$PHPMAILER_LANG['encoding'] = '알 수 없는 인코딩: '; +$PHPMAILER_LANG['execute'] = '실행 불가: '; +$PHPMAILER_LANG['file_access'] = '파일 접근 불가: '; +$PHPMAILER_LANG['file_open'] = '파일 오류: 파일을 열 수 없습니다: '; +$PHPMAILER_LANG['from_failed'] = '다음 From 주소에서 오류가 발생했습니다: '; +$PHPMAILER_LANG['instantiate'] = 'mail 함수를 인스턴스화할 수 없습니다'; +$PHPMAILER_LANG['invalid_address'] = '잘못된 주소: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' 메일러는 지원되지 않습니다.'; +$PHPMAILER_LANG['provide_address'] = '적어도 한 개 이상의 수신자 메일 주소를 제공해야 합니다.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP 오류: 다음 수신자에서 오류가 발생했습니다: '; +$PHPMAILER_LANG['signing'] = '서명 오류: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP 연결을 실패하였습니다.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP 서버 오류: '; +$PHPMAILER_LANG['variable_set'] = '변수 설정 및 초기화 불가: '; +$PHPMAILER_LANG['extension_missing'] = '확장자 없음: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php new file mode 100644 index 00000000..4f115b1c --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP klaida: autentifikacija nepavyko.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP klaida: nepavyksta prisijungti prie SMTP stoties.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP klaida: duomenys nepriimti.'; +$PHPMAILER_LANG['empty_message'] = 'Laiško turinys tuščias'; +$PHPMAILER_LANG['encoding'] = 'Neatpažinta koduotė: '; +$PHPMAILER_LANG['execute'] = 'Nepavyko įvykdyti komandos: '; +$PHPMAILER_LANG['file_access'] = 'Byla nepasiekiama: '; +$PHPMAILER_LANG['file_open'] = 'Bylos klaida: Nepavyksta atidaryti: '; +$PHPMAILER_LANG['from_failed'] = 'Neteisingas siuntėjo adresas: '; +$PHPMAILER_LANG['instantiate'] = 'Nepavyko paleisti mail funkcijos.'; +$PHPMAILER_LANG['invalid_address'] = 'Neteisingas adresas: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' pašto stotis nepalaikoma.'; +$PHPMAILER_LANG['provide_address'] = 'Nurodykite bent vieną gavėjo adresą.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP klaida: nepavyko išsiųsti šiems gavėjams: '; +$PHPMAILER_LANG['signing'] = 'Prisijungimo klaida: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP susijungimo klaida'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP stoties klaida: '; +$PHPMAILER_LANG['variable_set'] = 'Nepavyko priskirti reikšmės kintamajam: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php new file mode 100644 index 00000000..679b18cf --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP kļūda: Autorizācija neizdevās.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Kļūda: Nevar izveidot savienojumu ar SMTP serveri.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Kļūda: Nepieņem informāciju.'; +$PHPMAILER_LANG['empty_message'] = 'Ziņojuma teksts ir tukšs'; +$PHPMAILER_LANG['encoding'] = 'Neatpazīts kodējums: '; +$PHPMAILER_LANG['execute'] = 'Neizdevās izpildīt komandu: '; +$PHPMAILER_LANG['file_access'] = 'Fails nav pieejams: '; +$PHPMAILER_LANG['file_open'] = 'Faila kļūda: Nevar atvērt failu: '; +$PHPMAILER_LANG['from_failed'] = 'Nepareiza sūtītāja adrese: '; +$PHPMAILER_LANG['instantiate'] = 'Nevar palaist sūtīšanas funkciju.'; +$PHPMAILER_LANG['invalid_address'] = 'Nepareiza adrese: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' sūtītājs netiek atbalstīts.'; +$PHPMAILER_LANG['provide_address'] = 'Lūdzu, norādiet vismaz vienu adresātu.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP kļūda: neizdevās nosūtīt šādiem saņēmējiem: '; +$PHPMAILER_LANG['signing'] = 'Autorizācijas kļūda: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP savienojuma kļūda'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP servera kļūda: '; +$PHPMAILER_LANG['variable_set'] = 'Nevar piešķirt mainīgā vērtību: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php new file mode 100644 index 00000000..8a94f6a0 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Hadisoana SMTP: Tsy nahomby ny fanamarinana.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Tsy afaka mampifandray amin\'ny mpampiantrano SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP diso: tsy voarakitra ny angona.'; +$PHPMAILER_LANG['empty_message'] = 'Tsy misy ny votoaty mailaka.'; +$PHPMAILER_LANG['encoding'] = 'Tsy fantatra encoding: '; +$PHPMAILER_LANG['execute'] = 'Tsy afaka manatanteraka ity baiko manaraka ity: '; +$PHPMAILER_LANG['file_access'] = 'Tsy nahomby ny fidirana amin\'ity rakitra ity: '; +$PHPMAILER_LANG['file_open'] = 'Hadisoana diso: Tsy afaka nanokatra ity file manaraka ity: '; +$PHPMAILER_LANG['from_failed'] = 'Ny adiresy iraka manaraka dia diso: '; +$PHPMAILER_LANG['instantiate'] = 'Tsy afaka nanomboka ny hetsika mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Tsy mety ny adiresy: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer tsy manohana.'; +$PHPMAILER_LANG['provide_address'] = 'Alefaso azafady iray adiresy iray farafahakeliny.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: Tsy mety ireo mpanaraka ireto: '; +$PHPMAILER_LANG['signing'] = 'Error nandritra ny sonia:'; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Tsy nahomby ny fifandraisana tamin\'ny server SMTP.'; +$PHPMAILER_LANG['smtp_error'] = 'Fahadisoana tamin\'ny server SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Tsy azo atao ny mametraka na mamerina ny variable: '; +$PHPMAILER_LANG['extension_missing'] = 'Tsy hita ny ampahany: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php new file mode 100644 index 00000000..04d262c7 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Ralat SMTP: Tidak dapat pengesahan.'; +$PHPMAILER_LANG['connect_host'] = 'Ralat SMTP: Tidak dapat menghubungi hos pelayan SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Ralat SMTP: Data tidak diterima oleh pelayan.'; +$PHPMAILER_LANG['empty_message'] = 'Tiada isi untuk mesej'; +$PHPMAILER_LANG['encoding'] = 'Pengekodan tidak diketahui: '; +$PHPMAILER_LANG['execute'] = 'Tidak dapat melaksanakan: '; +$PHPMAILER_LANG['file_access'] = 'Tidak dapat mengakses fail: '; +$PHPMAILER_LANG['file_open'] = 'Ralat Fail: Tidak dapat membuka fail: '; +$PHPMAILER_LANG['from_failed'] = 'Berikut merupakan ralat dari alamat e-mel: '; +$PHPMAILER_LANG['instantiate'] = 'Tidak dapat memberi contoh fungsi e-mel.'; +$PHPMAILER_LANG['invalid_address'] = 'Alamat emel tidak sah: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' jenis penghantar emel tidak disokong.'; +$PHPMAILER_LANG['provide_address'] = 'Anda perlu menyediakan sekurang-kurangnya satu alamat e-mel penerima.'; +$PHPMAILER_LANG['recipients_failed'] = 'Ralat SMTP: Penerima e-mel berikut telah gagal: '; +$PHPMAILER_LANG['signing'] = 'Ralat pada tanda tangan: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() telah gagal.'; +$PHPMAILER_LANG['smtp_error'] = 'Ralat pada pelayan SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Tidak boleh menetapkan atau menetapkan semula pembolehubah: '; +$PHPMAILER_LANG['extension_missing'] = 'Sambungan hilang: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php new file mode 100644 index 00000000..c9621a16 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php @@ -0,0 +1,33 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP-fout: authenticatie mislukt.'; +$PHPMAILER_LANG['buggy_php'] = 'PHP versie gededecteerd die onderhavig is aan een bug die kan resulteren in gecorrumpeerde berichten. Om dit te voorkomen, gebruik SMTP voor het verzenden van berichten, zet de mail.add_x_header optie in uw php.ini file uit, gebruik MacOS of Linux, of pas de gebruikte PHP versie aan naar versie 7.0.17+ or 7.1.3+.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP-fout: kon niet verbinden met SMTP-host.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP-fout: data niet geaccepteerd.'; +$PHPMAILER_LANG['empty_message'] = 'Berichttekst is leeg'; +$PHPMAILER_LANG['encoding'] = 'Onbekende codering: '; +$PHPMAILER_LANG['execute'] = 'Kon niet uitvoeren: '; +$PHPMAILER_LANG['extension_missing'] = 'Extensie afwezig: '; +$PHPMAILER_LANG['file_access'] = 'Kreeg geen toegang tot bestand: '; +$PHPMAILER_LANG['file_open'] = 'Bestandsfout: kon bestand niet openen: '; +$PHPMAILER_LANG['from_failed'] = 'Het volgende afzendersadres is mislukt: '; +$PHPMAILER_LANG['instantiate'] = 'Kon mailfunctie niet initialiseren.'; +$PHPMAILER_LANG['invalid_address'] = 'Ongeldig adres: '; +$PHPMAILER_LANG['invalid_header'] = 'Ongeldige header naam of waarde'; +$PHPMAILER_LANG['invalid_hostentry'] = 'Ongeldige hostentry: '; +$PHPMAILER_LANG['invalid_host'] = 'Ongeldige host: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer wordt niet ondersteund.'; +$PHPMAILER_LANG['provide_address'] = 'Er moet minstens één ontvanger worden opgegeven.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP-fout: de volgende ontvangers zijn mislukt: '; +$PHPMAILER_LANG['signing'] = 'Signeerfout: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP code: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Aanvullende SMTP informatie: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Verbinding mislukt.'; +$PHPMAILER_LANG['smtp_detail'] = 'Detail: '; +$PHPMAILER_LANG['smtp_error'] = 'SMTP-serverfout: '; +$PHPMAILER_LANG['variable_set'] = 'Kan de volgende variabele niet instellen of resetten: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php new file mode 100644 index 00000000..cb7b2c21 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php @@ -0,0 +1,33 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Erro do SMTP: Não foi possível realizar a autenticação.'; +$PHPMAILER_LANG['connect_host'] = 'Erro do SMTP: Não foi possível realizar ligação com o servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Erro do SMTP: Os dados foram rejeitados.'; +$PHPMAILER_LANG['empty_message'] = 'A mensagem no e-mail está vazia.'; +$PHPMAILER_LANG['encoding'] = 'Codificação desconhecida: '; +$PHPMAILER_LANG['execute'] = 'Não foi possível executar: '; +$PHPMAILER_LANG['file_access'] = 'Não foi possível aceder o ficheiro: '; +$PHPMAILER_LANG['file_open'] = 'Abertura do ficheiro: Não foi possível abrir o ficheiro: '; +$PHPMAILER_LANG['from_failed'] = 'Ocorreram falhas nos endereços dos seguintes remententes: '; +$PHPMAILER_LANG['instantiate'] = 'Não foi possível iniciar uma instância da função mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Não foi enviado nenhum e-mail para o endereço de e-mail inválido: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer não é suportado.'; +$PHPMAILER_LANG['provide_address'] = 'Tem de fornecer pelo menos um endereço como destinatário do e-mail.'; +$PHPMAILER_LANG['recipients_failed'] = 'Erro do SMTP: O endereço do seguinte destinatário falhou: '; +$PHPMAILER_LANG['signing'] = 'Erro ao assinar: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falhou.'; +$PHPMAILER_LANG['smtp_error'] = 'Erro de servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Não foi possível definir ou redefinir a variável: '; +$PHPMAILER_LANG['extension_missing'] = 'Extensão em falta: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php new file mode 100644 index 00000000..5239865a --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php @@ -0,0 +1,38 @@ + + * @author Lucas Guimarães + * @author Phelipe Alves + * @author Fabio Beneditto + * @author Geidson Benício Coelho + */ + +$PHPMAILER_LANG['authenticate'] = 'Erro de SMTP: Não foi possível autenticar.'; +$PHPMAILER_LANG['buggy_php'] = 'Sua versão do PHP é afetada por um bug que por resultar em messagens corrompidas. Para corrigir, mude para enviar usando SMTP, desative a opção mail.add_x_header em seu php.ini, mude para MacOS ou Linux, ou atualize seu PHP para versão 7.0.17+ ou 7.1.3+ '; +$PHPMAILER_LANG['connect_host'] = 'Erro de SMTP: Não foi possível conectar ao servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Erro de SMTP: Dados rejeitados.'; +$PHPMAILER_LANG['empty_message'] = 'Mensagem vazia'; +$PHPMAILER_LANG['encoding'] = 'Codificação desconhecida: '; +$PHPMAILER_LANG['execute'] = 'Não foi possível executar: '; +$PHPMAILER_LANG['extension_missing'] = 'Extensão não existe: '; +$PHPMAILER_LANG['file_access'] = 'Não foi possível acessar o arquivo: '; +$PHPMAILER_LANG['file_open'] = 'Erro de Arquivo: Não foi possível abrir o arquivo: '; +$PHPMAILER_LANG['from_failed'] = 'Os seguintes remetentes falharam: '; +$PHPMAILER_LANG['instantiate'] = 'Não foi possível instanciar a função mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Endereço de e-mail inválido: '; +$PHPMAILER_LANG['invalid_header'] = 'Nome ou valor de cabeçalho inválido'; +$PHPMAILER_LANG['invalid_hostentry'] = 'hostentry inválido: '; +$PHPMAILER_LANG['invalid_host'] = 'host inválido: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer não é suportado.'; +$PHPMAILER_LANG['provide_address'] = 'Você deve informar pelo menos um destinatário.'; +$PHPMAILER_LANG['recipients_failed'] = 'Erro de SMTP: Os seguintes destinatários falharam: '; +$PHPMAILER_LANG['signing'] = 'Erro de Assinatura: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falhou.'; +$PHPMAILER_LANG['smtp_code'] = 'Código do servidor SMTP: '; +$PHPMAILER_LANG['smtp_error'] = 'Erro de servidor SMTP: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Informações adicionais do servidor SMTP: '; +$PHPMAILER_LANG['smtp_detail'] = 'Detalhes do servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Não foi possível definir ou redefinir a variável: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php new file mode 100644 index 00000000..45bef915 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php @@ -0,0 +1,33 @@ + + * @author Foster Snowhill + */ + +$PHPMAILER_LANG['authenticate'] = 'Ошибка SMTP: ошибка авторизации.'; +$PHPMAILER_LANG['connect_host'] = 'Ошибка SMTP: не удается подключиться к SMTP-серверу.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Ошибка SMTP: данные не приняты.'; +$PHPMAILER_LANG['encoding'] = 'Неизвестная кодировка: '; +$PHPMAILER_LANG['execute'] = 'Невозможно выполнить команду: '; +$PHPMAILER_LANG['file_access'] = 'Нет доступа к файлу: '; +$PHPMAILER_LANG['file_open'] = 'Файловая ошибка: не удаётся открыть файл: '; +$PHPMAILER_LANG['from_failed'] = 'Неверный адрес отправителя: '; +$PHPMAILER_LANG['instantiate'] = 'Невозможно запустить функцию mail().'; +$PHPMAILER_LANG['provide_address'] = 'Пожалуйста, введите хотя бы один email-адрес получателя.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' — почтовый сервер не поддерживается.'; +$PHPMAILER_LANG['recipients_failed'] = 'Ошибка SMTP: не удалась отправка таким адресатам: '; +$PHPMAILER_LANG['empty_message'] = 'Пустое сообщение'; +$PHPMAILER_LANG['invalid_address'] = 'Не отправлено из-за неправильного формата email-адреса: '; +$PHPMAILER_LANG['signing'] = 'Ошибка подписи: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Ошибка соединения с SMTP-сервером'; +$PHPMAILER_LANG['smtp_error'] = 'Ошибка SMTP-сервера: '; +$PHPMAILER_LANG['variable_set'] = 'Невозможно установить или сбросить переменную: '; +$PHPMAILER_LANG['extension_missing'] = 'Расширение отсутствует: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php new file mode 100644 index 00000000..dce502aa --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php @@ -0,0 +1,34 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP දෝෂය: සත්‍යාපනය අසාර්ථක විය.'; +$PHPMAILER_LANG['buggy_php'] = 'ඔබගේ PHP version එකෙහි පවතින දෝෂයක් නිසා email පණිවිඩ දෝෂ සහගත වීමේ හැකියාවක් ඇත. මෙය විසදීම සදහා SMTP භාවිතා කිරීම, mail.add_x_header INI setting එක අක්‍රීය කිරීම, MacOS හෝ Linux වලට මාරු වීම, හෝ ඔබගේ PHP version එක 7.0.17+ හෝ 7.1.3+ වලට අලුත් කිරීම කරගන්න.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP දෝෂය: සම්බන්ධ වීමට නොහැකි විය.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP දෝෂය: දත්ත පිළිගනු නොලැබේ.'; +$PHPMAILER_LANG['empty_message'] = 'පණිවිඩ අන්තර්ගතය හිස්'; +$PHPMAILER_LANG['encoding'] = 'නොදන්නා කේතනය: '; +$PHPMAILER_LANG['execute'] = 'ක්‍රියාත්මක කළ නොහැකි විය: '; +$PHPMAILER_LANG['extension_missing'] = 'Extension එක නොමැත: '; +$PHPMAILER_LANG['file_access'] = 'File එකට ප්‍රවේශ විය නොහැකි විය: '; +$PHPMAILER_LANG['file_open'] = 'File දෝෂය: File එක විවෘත කළ නොහැක: '; +$PHPMAILER_LANG['from_failed'] = 'පහත From ලිපිනයන් අසාර්ථක විය: '; +$PHPMAILER_LANG['instantiate'] = 'mail function එක ක්‍රියාත්මක කළ නොහැක.'; +$PHPMAILER_LANG['invalid_address'] = 'වලංගු නොවන ලිපිනය: '; +$PHPMAILER_LANG['invalid_header'] = 'වලංගු නොවන header නාමයක් හෝ අගයක්'; +$PHPMAILER_LANG['invalid_hostentry'] = 'වලංගු නොවන hostentry එකක්: '; +$PHPMAILER_LANG['invalid_host'] = 'වලංගු නොවන host එකක්: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer සහාය නොදක්වයි.'; +$PHPMAILER_LANG['provide_address'] = 'ඔබ අවම වශයෙන් එක් ලබන්නෙකුගේ ඊමේල් ලිපිනයක් සැපයිය යුතුය.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP දෝෂය: පහත ලබන්නන් අසමත් විය: '; +$PHPMAILER_LANG['signing'] = 'Sign කිරීමේ දෝෂය: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP කේතය: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'අමතර SMTP තොරතුරු: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP සම්බන්ධය අසාර්ථක විය.'; +$PHPMAILER_LANG['smtp_detail'] = 'තොරතුරු: '; +$PHPMAILER_LANG['smtp_error'] = 'SMTP දෝෂය: '; +$PHPMAILER_LANG['variable_set'] = 'Variable එක සැකසීමට හෝ නැවත සැකසීමට නොහැක: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php new file mode 100644 index 00000000..028f5bc4 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php @@ -0,0 +1,30 @@ + + * @author Peter Orlický + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Error: Chyba autentifikácie.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Nebolo možné nadviazať spojenie so SMTP serverom.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Error: Dáta neboli prijaté'; +$PHPMAILER_LANG['empty_message'] = 'Prázdne telo správy.'; +$PHPMAILER_LANG['encoding'] = 'Neznáme kódovanie: '; +$PHPMAILER_LANG['execute'] = 'Nedá sa vykonať: '; +$PHPMAILER_LANG['file_access'] = 'Súbor nebol nájdený: '; +$PHPMAILER_LANG['file_open'] = 'File Error: Súbor sa otvoriť pre čítanie: '; +$PHPMAILER_LANG['from_failed'] = 'Následujúca adresa From je nesprávna: '; +$PHPMAILER_LANG['instantiate'] = 'Nedá sa vytvoriť inštancia emailovej funkcie.'; +$PHPMAILER_LANG['invalid_address'] = 'Neodoslané, emailová adresa je nesprávna: '; +$PHPMAILER_LANG['invalid_hostentry'] = 'Záznam hostiteľa je nesprávny: '; +$PHPMAILER_LANG['invalid_host'] = 'Hostiteľ je nesprávny: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' emailový klient nieje podporovaný.'; +$PHPMAILER_LANG['provide_address'] = 'Musíte zadať aspoň jednu emailovú adresu príjemcu.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: Adresy príjemcov niesu správne '; +$PHPMAILER_LANG['signing'] = 'Chyba prihlasovania: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() zlyhalo.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP chyba serveru: '; +$PHPMAILER_LANG['variable_set'] = 'Nemožno nastaviť alebo resetovať premennú: '; +$PHPMAILER_LANG['extension_missing'] = 'Chýba rozšírenie: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php new file mode 100644 index 00000000..3e00c259 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php @@ -0,0 +1,36 @@ + + * @author Filip Š + * @author Blaž Oražem + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP napaka: Avtentikacija ni uspela.'; +$PHPMAILER_LANG['buggy_php'] = 'Na vašo PHP različico vpliva napaka, ki lahko povzroči poškodovana sporočila. Če želite težavo odpraviti, preklopite na pošiljanje prek SMTP, onemogočite možnost mail.add_x_header v vaši php.ini datoteki, preklopite na MacOS ali Linux, ali nadgradite vašo PHP zaličico na 7.0.17+ ali 7.1.3+.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP napaka: Vzpostavljanje povezave s SMTP gostiteljem ni uspelo.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP napaka: Strežnik zavrača podatke.'; +$PHPMAILER_LANG['empty_message'] = 'E-poštno sporočilo nima vsebine.'; +$PHPMAILER_LANG['encoding'] = 'Nepoznan tip kodiranja: '; +$PHPMAILER_LANG['execute'] = 'Operacija ni uspela: '; +$PHPMAILER_LANG['extension_missing'] = 'Manjkajoča razširitev: '; +$PHPMAILER_LANG['file_access'] = 'Nimam dostopa do datoteke: '; +$PHPMAILER_LANG['file_open'] = 'Ne morem odpreti datoteke: '; +$PHPMAILER_LANG['from_failed'] = 'Neveljaven e-naslov pošiljatelja: '; +$PHPMAILER_LANG['instantiate'] = 'Ne morem inicializirati mail funkcije.'; +$PHPMAILER_LANG['invalid_address'] = 'E-poštno sporočilo ni bilo poslano. E-naslov je neveljaven: '; +$PHPMAILER_LANG['invalid_header'] = 'Neveljavno ime ali vrednost glave'; +$PHPMAILER_LANG['invalid_hostentry'] = 'Neveljaven vnos gostitelja: '; +$PHPMAILER_LANG['invalid_host'] = 'Neveljaven gostitelj: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer ni podprt.'; +$PHPMAILER_LANG['provide_address'] = 'Prosimo, vnesite vsaj enega naslovnika.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP napaka: Sledeči naslovniki so neveljavni: '; +$PHPMAILER_LANG['signing'] = 'Napaka pri podpisovanju: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP koda: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Dodatne informacije o SMTP: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Ne morem vzpostaviti povezave s SMTP strežnikom.'; +$PHPMAILER_LANG['smtp_detail'] = 'Podrobnosti: '; +$PHPMAILER_LANG['smtp_error'] = 'Napaka SMTP strežnika: '; +$PHPMAILER_LANG['variable_set'] = 'Ne morem nastaviti oz. ponastaviti spremenljivke: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php new file mode 100644 index 00000000..0b5280f7 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php @@ -0,0 +1,28 @@ + + * @author Miloš Milanović + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP грешка: аутентификација није успела.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP грешка: повезивање са SMTP сервером није успело.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP грешка: подаци нису прихваћени.'; +$PHPMAILER_LANG['empty_message'] = 'Садржај поруке је празан.'; +$PHPMAILER_LANG['encoding'] = 'Непознато кодирање: '; +$PHPMAILER_LANG['execute'] = 'Није могуће извршити наредбу: '; +$PHPMAILER_LANG['file_access'] = 'Није могуће приступити датотеци: '; +$PHPMAILER_LANG['file_open'] = 'Није могуће отворити датотеку: '; +$PHPMAILER_LANG['from_failed'] = 'SMTP грешка: слање са следећих адреса није успело: '; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP грешка: слање на следеће адресе није успело: '; +$PHPMAILER_LANG['instantiate'] = 'Није могуће покренути mail функцију.'; +$PHPMAILER_LANG['invalid_address'] = 'Порука није послата. Неисправна адреса: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' мејлер није подржан.'; +$PHPMAILER_LANG['provide_address'] = 'Дефинишите бар једну адресу примаоца.'; +$PHPMAILER_LANG['signing'] = 'Грешка приликом пријаве: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Повезивање са SMTP сервером није успело.'; +$PHPMAILER_LANG['smtp_error'] = 'Грешка SMTP сервера: '; +$PHPMAILER_LANG['variable_set'] = 'Није могуће задати нити ресетовати променљиву: '; +$PHPMAILER_LANG['extension_missing'] = 'Недостаје проширење: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php new file mode 100644 index 00000000..62138329 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php @@ -0,0 +1,28 @@ + + * @author Miloš Milanović + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP greška: autentifikacija nije uspela.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP greška: povezivanje sa SMTP serverom nije uspelo.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP greška: podaci nisu prihvaćeni.'; +$PHPMAILER_LANG['empty_message'] = 'Sadržaj poruke je prazan.'; +$PHPMAILER_LANG['encoding'] = 'Nepoznato kodiranje: '; +$PHPMAILER_LANG['execute'] = 'Nije moguće izvršiti naredbu: '; +$PHPMAILER_LANG['file_access'] = 'Nije moguće pristupiti datoteci: '; +$PHPMAILER_LANG['file_open'] = 'Nije moguće otvoriti datoteku: '; +$PHPMAILER_LANG['from_failed'] = 'SMTP greška: slanje sa sledećih adresa nije uspelo: '; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP greška: slanje na sledeće adrese nije uspelo: '; +$PHPMAILER_LANG['instantiate'] = 'Nije moguće pokrenuti mail funkciju.'; +$PHPMAILER_LANG['invalid_address'] = 'Poruka nije poslata. Neispravna adresa: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' majler nije podržan.'; +$PHPMAILER_LANG['provide_address'] = 'Definišite bar jednu adresu primaoca.'; +$PHPMAILER_LANG['signing'] = 'Greška prilikom prijave: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Povezivanje sa SMTP serverom nije uspelo.'; +$PHPMAILER_LANG['smtp_error'] = 'Greška SMTP servera: '; +$PHPMAILER_LANG['variable_set'] = 'Nije moguće zadati niti resetovati promenljivu: '; +$PHPMAILER_LANG['extension_missing'] = 'Nedostaje proširenje: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php new file mode 100644 index 00000000..9872c192 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP fel: Kunde inte autentisera.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP fel: Kunde inte ansluta till SMTP-server.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP fel: Data accepterades inte.'; +//$PHPMAILER_LANG['empty_message'] = 'Message body empty'; +$PHPMAILER_LANG['encoding'] = 'Okänt encode-format: '; +$PHPMAILER_LANG['execute'] = 'Kunde inte köra: '; +$PHPMAILER_LANG['file_access'] = 'Ingen åtkomst till fil: '; +$PHPMAILER_LANG['file_open'] = 'Fil fel: Kunde inte öppna fil: '; +$PHPMAILER_LANG['from_failed'] = 'Följande avsändaradress är felaktig: '; +$PHPMAILER_LANG['instantiate'] = 'Kunde inte initiera e-postfunktion.'; +$PHPMAILER_LANG['invalid_address'] = 'Felaktig adress: '; +$PHPMAILER_LANG['provide_address'] = 'Du måste ange minst en mottagares e-postadress.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer stöds inte.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP fel: Följande mottagare är felaktig: '; +$PHPMAILER_LANG['signing'] = 'Signeringsfel: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() misslyckades.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP serverfel: '; +$PHPMAILER_LANG['variable_set'] = 'Kunde inte definiera eller återställa variabel: '; +$PHPMAILER_LANG['extension_missing'] = 'Tillägg ej tillgängligt: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php new file mode 100644 index 00000000..d15bed1c --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php @@ -0,0 +1,28 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Error: Hindi mapatotohanan.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Hindi makakonekta sa SMTP host.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Error: Ang datos ay hindi naitanggap.'; +$PHPMAILER_LANG['empty_message'] = 'Walang laman ang mensahe'; +$PHPMAILER_LANG['encoding'] = 'Hindi alam ang encoding: '; +$PHPMAILER_LANG['execute'] = 'Hindi maisasagawa: '; +$PHPMAILER_LANG['file_access'] = 'Hindi ma-access ang file: '; +$PHPMAILER_LANG['file_open'] = 'File Error: Hindi mabuksan ang file: '; +$PHPMAILER_LANG['from_failed'] = 'Ang sumusunod na address ay nabigo: '; +$PHPMAILER_LANG['instantiate'] = 'Hindi maisimulan ang instance ng mail function.'; +$PHPMAILER_LANG['invalid_address'] = 'Hindi wasto ang address na naibigay: '; +$PHPMAILER_LANG['mailer_not_supported'] = 'Ang mailer ay hindi suportado.'; +$PHPMAILER_LANG['provide_address'] = 'Kailangan mong magbigay ng kahit isang email address na tatanggap.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: Ang mga sumusunod na tatanggap ay nabigo: '; +$PHPMAILER_LANG['signing'] = 'Hindi ma-sign: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Ang SMTP connect() ay nabigo.'; +$PHPMAILER_LANG['smtp_error'] = 'Ang server ng SMTP ay nabigo: '; +$PHPMAILER_LANG['variable_set'] = 'Hindi matatakda o ma-reset ang mga variables: '; +$PHPMAILER_LANG['extension_missing'] = 'Nawawala ang extension: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php new file mode 100644 index 00000000..f938f802 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php @@ -0,0 +1,31 @@ + + * @fixed by Boris Yurchenko + */ + +$PHPMAILER_LANG['authenticate'] = 'Помилка SMTP: помилка авторизації.'; +$PHPMAILER_LANG['connect_host'] = 'Помилка SMTP: не вдається під\'єднатися до SMTP-серверу.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Помилка SMTP: дані не прийнято.'; +$PHPMAILER_LANG['encoding'] = 'Невідоме кодування: '; +$PHPMAILER_LANG['execute'] = 'Неможливо виконати команду: '; +$PHPMAILER_LANG['file_access'] = 'Немає доступу до файлу: '; +$PHPMAILER_LANG['file_open'] = 'Помилка файлової системи: не вдається відкрити файл: '; +$PHPMAILER_LANG['from_failed'] = 'Невірна адреса відправника: '; +$PHPMAILER_LANG['instantiate'] = 'Неможливо запустити функцію mail().'; +$PHPMAILER_LANG['provide_address'] = 'Будь ласка, введіть хоча б одну email-адресу отримувача.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' - поштовий сервер не підтримується.'; +$PHPMAILER_LANG['recipients_failed'] = 'Помилка SMTP: не вдалося відправлення для таких отримувачів: '; +$PHPMAILER_LANG['empty_message'] = 'Пусте повідомлення'; +$PHPMAILER_LANG['invalid_address'] = 'Не відправлено через неправильний формат email-адреси: '; +$PHPMAILER_LANG['signing'] = 'Помилка підпису: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Помилка з\'єднання з SMTP-сервером'; +$PHPMAILER_LANG['smtp_error'] = 'Помилка SMTP-сервера: '; +$PHPMAILER_LANG['variable_set'] = 'Неможливо встановити або скинути змінну: '; +$PHPMAILER_LANG['extension_missing'] = 'Розширення відсутнє: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php new file mode 100644 index 00000000..d65576e2 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Lỗi SMTP: Không thể xác thực.'; +$PHPMAILER_LANG['connect_host'] = 'Lỗi SMTP: Không thể kết nối máy chủ SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Lỗi SMTP: Dữ liệu không được chấp nhận.'; +$PHPMAILER_LANG['empty_message'] = 'Không có nội dung'; +$PHPMAILER_LANG['encoding'] = 'Mã hóa không xác định: '; +$PHPMAILER_LANG['execute'] = 'Không thực hiện được: '; +$PHPMAILER_LANG['file_access'] = 'Không thể truy cập tệp tin '; +$PHPMAILER_LANG['file_open'] = 'Lỗi Tập tin: Không thể mở tệp tin: '; +$PHPMAILER_LANG['from_failed'] = 'Lỗi địa chỉ gửi đi: '; +$PHPMAILER_LANG['instantiate'] = 'Không dùng được các hàm gửi thư.'; +$PHPMAILER_LANG['invalid_address'] = 'Đại chỉ emai không đúng: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' trình gửi thư không được hỗ trợ.'; +$PHPMAILER_LANG['provide_address'] = 'Bạn phải cung cấp ít nhất một địa chỉ người nhận.'; +$PHPMAILER_LANG['recipients_failed'] = 'Lỗi SMTP: lỗi địa chỉ người nhận: '; +$PHPMAILER_LANG['signing'] = 'Lỗi đăng nhập: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Lỗi kết nối với SMTP'; +$PHPMAILER_LANG['smtp_error'] = 'Lỗi máy chủ smtp '; +$PHPMAILER_LANG['variable_set'] = 'Không thể thiết lập hoặc thiết lập lại biến: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php new file mode 100644 index 00000000..35e4e700 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php @@ -0,0 +1,29 @@ + + * @author Peter Dave Hello <@PeterDaveHello/> + * @author Jason Chiang + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP 錯誤:登入失敗。'; +$PHPMAILER_LANG['connect_host'] = 'SMTP 錯誤:無法連線到 SMTP 主機。'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 錯誤:無法接受的資料。'; +$PHPMAILER_LANG['empty_message'] = '郵件內容為空'; +$PHPMAILER_LANG['encoding'] = '未知編碼: '; +$PHPMAILER_LANG['execute'] = '無法執行:'; +$PHPMAILER_LANG['file_access'] = '無法存取檔案:'; +$PHPMAILER_LANG['file_open'] = '檔案錯誤:無法開啟檔案:'; +$PHPMAILER_LANG['from_failed'] = '發送地址錯誤:'; +$PHPMAILER_LANG['instantiate'] = '未知函數呼叫。'; +$PHPMAILER_LANG['invalid_address'] = '因為電子郵件地址無效,無法傳送: '; +$PHPMAILER_LANG['mailer_not_supported'] = '不支援的發信客戶端。'; +$PHPMAILER_LANG['provide_address'] = '必須提供至少一個收件人地址。'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP 錯誤:以下收件人地址錯誤:'; +$PHPMAILER_LANG['signing'] = '電子簽章錯誤: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP 連線失敗'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP 伺服器錯誤: '; +$PHPMAILER_LANG['variable_set'] = '無法設定或重設變數: '; +$PHPMAILER_LANG['extension_missing'] = '遺失模組 Extension: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php new file mode 100644 index 00000000..03d49116 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php @@ -0,0 +1,36 @@ + + * @author young + * @author Teddysun + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP 错误:登录失败。'; +$PHPMAILER_LANG['buggy_php'] = '您的 PHP 版本存在漏洞,可能会导致消息损坏。为修复此问题,请切换到使用 SMTP 发送,在您的 php.ini 中禁用 mail.add_x_header 选项。切换到 MacOS 或 Linux,或将您的 PHP 升级到 7.0.17+ 或 7.1.3+ 版本。'; +$PHPMAILER_LANG['connect_host'] = 'SMTP 错误:无法连接到 SMTP 主机。'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 错误:数据不被接受。'; +$PHPMAILER_LANG['empty_message'] = '邮件正文为空。'; +$PHPMAILER_LANG['encoding'] = '未知编码:'; +$PHPMAILER_LANG['execute'] = '无法执行:'; +$PHPMAILER_LANG['extension_missing'] = '缺少扩展名:'; +$PHPMAILER_LANG['file_access'] = '无法访问文件:'; +$PHPMAILER_LANG['file_open'] = '文件错误:无法打开文件:'; +$PHPMAILER_LANG['from_failed'] = '发送地址错误:'; +$PHPMAILER_LANG['instantiate'] = '未知函数调用。'; +$PHPMAILER_LANG['invalid_address'] = '发送失败,电子邮箱地址是无效的:'; +$PHPMAILER_LANG['mailer_not_supported'] = '发信客户端不被支持。'; +$PHPMAILER_LANG['provide_address'] = '必须提供至少一个收件人地址。'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP 错误:收件人地址错误:'; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP服务器连接失败。'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP服务器出错:'; +$PHPMAILER_LANG['variable_set'] = '无法设置或重置变量:'; +$PHPMAILER_LANG['invalid_header'] = '无效的标题名称或值'; +$PHPMAILER_LANG['invalid_hostentry'] = '无效的hostentry: '; +$PHPMAILER_LANG['invalid_host'] = '无效的主机:'; +$PHPMAILER_LANG['signing'] = '签名错误:'; +$PHPMAILER_LANG['smtp_code'] = 'SMTP代码: '; +$PHPMAILER_LANG['smtp_code_ex'] = '附加SMTP信息: '; +$PHPMAILER_LANG['smtp_detail'] = '详情:'; diff --git a/src/vendor/phpmailer/phpmailer/src/DSNConfigurator.php b/src/vendor/phpmailer/phpmailer/src/DSNConfigurator.php new file mode 100644 index 00000000..566c9618 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/src/DSNConfigurator.php @@ -0,0 +1,245 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2023 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * Configure PHPMailer with DSN string. + * + * @see https://en.wikipedia.org/wiki/Data_source_name + * + * @author Oleg Voronkovich + */ +class DSNConfigurator +{ + /** + * Create new PHPMailer instance configured by DSN. + * + * @param string $dsn DSN + * @param bool $exceptions Should we throw external exceptions? + * + * @return PHPMailer + */ + public static function mailer($dsn, $exceptions = null) + { + static $configurator = null; + + if (null === $configurator) { + $configurator = new DSNConfigurator(); + } + + return $configurator->configure(new PHPMailer($exceptions), $dsn); + } + + /** + * Configure PHPMailer instance with DSN string. + * + * @param PHPMailer $mailer PHPMailer instance + * @param string $dsn DSN + * + * @return PHPMailer + */ + public function configure(PHPMailer $mailer, $dsn) + { + $config = $this->parseDSN($dsn); + + $this->applyConfig($mailer, $config); + + return $mailer; + } + + /** + * Parse DSN string. + * + * @param string $dsn DSN + * + * @throws Exception If DSN is malformed + * + * @return array Configuration + */ + private function parseDSN($dsn) + { + $config = $this->parseUrl($dsn); + + if (false === $config || !isset($config['scheme']) || !isset($config['host'])) { + throw new Exception('Malformed DSN'); + } + + if (isset($config['query'])) { + parse_str($config['query'], $config['query']); + } + + return $config; + } + + /** + * Apply configuration to mailer. + * + * @param PHPMailer $mailer PHPMailer instance + * @param array $config Configuration + * + * @throws Exception If scheme is invalid + */ + private function applyConfig(PHPMailer $mailer, $config) + { + switch ($config['scheme']) { + case 'mail': + $mailer->isMail(); + break; + case 'sendmail': + $mailer->isSendmail(); + break; + case 'qmail': + $mailer->isQmail(); + break; + case 'smtp': + case 'smtps': + $mailer->isSMTP(); + $this->configureSMTP($mailer, $config); + break; + default: + throw new Exception( + sprintf( + 'Invalid scheme: "%s". Allowed values: "mail", "sendmail", "qmail", "smtp", "smtps".', + $config['scheme'] + ) + ); + } + + if (isset($config['query'])) { + $this->configureOptions($mailer, $config['query']); + } + } + + /** + * Configure SMTP. + * + * @param PHPMailer $mailer PHPMailer instance + * @param array $config Configuration + */ + private function configureSMTP($mailer, $config) + { + $isSMTPS = 'smtps' === $config['scheme']; + + if ($isSMTPS) { + $mailer->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; + } + + $mailer->Host = $config['host']; + + if (isset($config['port'])) { + $mailer->Port = $config['port']; + } elseif ($isSMTPS) { + $mailer->Port = SMTP::DEFAULT_SECURE_PORT; + } + + $mailer->SMTPAuth = isset($config['user']) || isset($config['pass']); + + if (isset($config['user'])) { + $mailer->Username = $config['user']; + } + + if (isset($config['pass'])) { + $mailer->Password = $config['pass']; + } + } + + /** + * Configure options. + * + * @param PHPMailer $mailer PHPMailer instance + * @param array $options Options + * + * @throws Exception If option is unknown + */ + private function configureOptions(PHPMailer $mailer, $options) + { + $allowedOptions = get_object_vars($mailer); + + unset($allowedOptions['Mailer']); + unset($allowedOptions['SMTPAuth']); + unset($allowedOptions['Username']); + unset($allowedOptions['Password']); + unset($allowedOptions['Hostname']); + unset($allowedOptions['Port']); + unset($allowedOptions['ErrorInfo']); + + $allowedOptions = \array_keys($allowedOptions); + + foreach ($options as $key => $value) { + if (!in_array($key, $allowedOptions)) { + throw new Exception( + sprintf( + 'Unknown option: "%s". Allowed values: "%s"', + $key, + implode('", "', $allowedOptions) + ) + ); + } + + switch ($key) { + case 'AllowEmpty': + case 'SMTPAutoTLS': + case 'SMTPKeepAlive': + case 'SingleTo': + case 'UseSendmailOptions': + case 'do_verp': + case 'DKIM_copyHeaderFields': + $mailer->$key = (bool) $value; + break; + case 'Priority': + case 'SMTPDebug': + case 'WordWrap': + $mailer->$key = (int) $value; + break; + default: + $mailer->$key = $value; + break; + } + } + } + + /** + * Parse a URL. + * Wrapper for the built-in parse_url function to work around a bug in PHP 5.5. + * + * @param string $url URL + * + * @return array|false + */ + protected function parseUrl($url) + { + if (\PHP_VERSION_ID >= 50600 || false === strpos($url, '?')) { + return parse_url($url); + } + + $chunks = explode('?', $url); + if (is_array($chunks)) { + $result = parse_url($chunks[0]); + if (is_array($result)) { + $result['query'] = $chunks[1]; + } + return $result; + } + + return false; + } +} diff --git a/src/vendor/phpmailer/phpmailer/src/Exception.php b/src/vendor/phpmailer/phpmailer/src/Exception.php new file mode 100644 index 00000000..52eaf951 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/src/Exception.php @@ -0,0 +1,40 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer exception handler. + * + * @author Marcus Bointon + */ +class Exception extends \Exception +{ + /** + * Prettify error message output. + * + * @return string + */ + public function errorMessage() + { + return '' . htmlspecialchars($this->getMessage(), ENT_COMPAT | ENT_HTML401) . "
\n"; + } +} diff --git a/src/vendor/phpmailer/phpmailer/src/OAuth.php b/src/vendor/phpmailer/phpmailer/src/OAuth.php new file mode 100644 index 00000000..c1d5b776 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/src/OAuth.php @@ -0,0 +1,139 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +use League\OAuth2\Client\Grant\RefreshToken; +use League\OAuth2\Client\Provider\AbstractProvider; +use League\OAuth2\Client\Token\AccessToken; + +/** + * OAuth - OAuth2 authentication wrapper class. + * Uses the oauth2-client package from the League of Extraordinary Packages. + * + * @see http://oauth2-client.thephpleague.com + * + * @author Marcus Bointon (Synchro/coolbru) + */ +class OAuth implements OAuthTokenProvider +{ + /** + * An instance of the League OAuth Client Provider. + * + * @var AbstractProvider + */ + protected $provider; + + /** + * The current OAuth access token. + * + * @var AccessToken + */ + protected $oauthToken; + + /** + * The user's email address, usually used as the login ID + * and also the from address when sending email. + * + * @var string + */ + protected $oauthUserEmail = ''; + + /** + * The client secret, generated in the app definition of the service you're connecting to. + * + * @var string + */ + protected $oauthClientSecret = ''; + + /** + * The client ID, generated in the app definition of the service you're connecting to. + * + * @var string + */ + protected $oauthClientId = ''; + + /** + * The refresh token, used to obtain new AccessTokens. + * + * @var string + */ + protected $oauthRefreshToken = ''; + + /** + * OAuth constructor. + * + * @param array $options Associative array containing + * `provider`, `userName`, `clientSecret`, `clientId` and `refreshToken` elements + */ + public function __construct($options) + { + $this->provider = $options['provider']; + $this->oauthUserEmail = $options['userName']; + $this->oauthClientSecret = $options['clientSecret']; + $this->oauthClientId = $options['clientId']; + $this->oauthRefreshToken = $options['refreshToken']; + } + + /** + * Get a new RefreshToken. + * + * @return RefreshToken + */ + protected function getGrant() + { + return new RefreshToken(); + } + + /** + * Get a new AccessToken. + * + * @return AccessToken + */ + protected function getToken() + { + return $this->provider->getAccessToken( + $this->getGrant(), + ['refresh_token' => $this->oauthRefreshToken] + ); + } + + /** + * Generate a base64-encoded OAuth token. + * + * @return string + */ + public function getOauth64() + { + //Get a new token if it's not available or has expired + if (null === $this->oauthToken || $this->oauthToken->hasExpired()) { + $this->oauthToken = $this->getToken(); + } + + return base64_encode( + 'user=' . + $this->oauthUserEmail . + "\001auth=Bearer " . + $this->oauthToken . + "\001\001" + ); + } +} diff --git a/src/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php b/src/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php new file mode 100644 index 00000000..11555074 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php @@ -0,0 +1,44 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * OAuthTokenProvider - OAuth2 token provider interface. + * Provides base64 encoded OAuth2 auth strings for SMTP authentication. + * + * @see OAuth + * @see SMTP::authenticate() + * + * @author Peter Scopes (pdscopes) + * @author Marcus Bointon (Synchro/coolbru) + */ +interface OAuthTokenProvider +{ + /** + * Generate a base64-encoded OAuth token ensuring that the access token has not expired. + * The string to be base 64 encoded should be in the form: + * "user=\001auth=Bearer \001\001" + * + * @return string + */ + public function getOauth64(); +} diff --git a/src/vendor/phpmailer/phpmailer/src/PHPMailer.php b/src/vendor/phpmailer/phpmailer/src/PHPMailer.php new file mode 100644 index 00000000..ba4bcd47 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/src/PHPMailer.php @@ -0,0 +1,5252 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer - PHP email creation and transport class. + * + * @author Marcus Bointon (Synchro/coolbru) + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + */ +class PHPMailer +{ + const CHARSET_ASCII = 'us-ascii'; + const CHARSET_ISO88591 = 'iso-8859-1'; + const CHARSET_UTF8 = 'utf-8'; + + const CONTENT_TYPE_PLAINTEXT = 'text/plain'; + const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar'; + const CONTENT_TYPE_TEXT_HTML = 'text/html'; + const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative'; + const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed'; + const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related'; + + const ENCODING_7BIT = '7bit'; + const ENCODING_8BIT = '8bit'; + const ENCODING_BASE64 = 'base64'; + const ENCODING_BINARY = 'binary'; + const ENCODING_QUOTED_PRINTABLE = 'quoted-printable'; + + const ENCRYPTION_STARTTLS = 'tls'; + const ENCRYPTION_SMTPS = 'ssl'; + + const ICAL_METHOD_REQUEST = 'REQUEST'; + const ICAL_METHOD_PUBLISH = 'PUBLISH'; + const ICAL_METHOD_REPLY = 'REPLY'; + const ICAL_METHOD_ADD = 'ADD'; + const ICAL_METHOD_CANCEL = 'CANCEL'; + const ICAL_METHOD_REFRESH = 'REFRESH'; + const ICAL_METHOD_COUNTER = 'COUNTER'; + const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER'; + + /** + * Email priority. + * Options: null (default), 1 = High, 3 = Normal, 5 = low. + * When null, the header is not set at all. + * + * @var int|null + */ + public $Priority; + + /** + * The character set of the message. + * + * @var string + */ + public $CharSet = self::CHARSET_ISO88591; + + /** + * The MIME Content-type of the message. + * + * @var string + */ + public $ContentType = self::CONTENT_TYPE_PLAINTEXT; + + /** + * The message encoding. + * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". + * + * @var string + */ + public $Encoding = self::ENCODING_8BIT; + + /** + * Holds the most recent mailer error message. + * + * @var string + */ + public $ErrorInfo = ''; + + /** + * The From email address for the message. + * + * @var string + */ + public $From = ''; + + /** + * The From name of the message. + * + * @var string + */ + public $FromName = ''; + + /** + * The envelope sender of the message. + * This will usually be turned into a Return-Path header by the receiver, + * and is the address that bounces will be sent to. + * If not empty, will be passed via `-f` to sendmail or as the 'MAIL FROM' value over SMTP. + * + * @var string + */ + public $Sender = ''; + + /** + * The Subject of the message. + * + * @var string + */ + public $Subject = ''; + + /** + * An HTML or plain text message body. + * If HTML then call isHTML(true). + * + * @var string + */ + public $Body = ''; + + /** + * The plain-text message body. + * This body can be read by mail clients that do not have HTML email + * capability such as mutt & Eudora. + * Clients that can read HTML will view the normal Body. + * + * @var string + */ + public $AltBody = ''; + + /** + * An iCal message part body. + * Only supported in simple alt or alt_inline message types + * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator. + * + * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ + * @see http://kigkonsult.se/iCalcreator/ + * + * @var string + */ + public $Ical = ''; + + /** + * Value-array of "method" in Contenttype header "text/calendar" + * + * @var string[] + */ + protected static $IcalMethods = [ + self::ICAL_METHOD_REQUEST, + self::ICAL_METHOD_PUBLISH, + self::ICAL_METHOD_REPLY, + self::ICAL_METHOD_ADD, + self::ICAL_METHOD_CANCEL, + self::ICAL_METHOD_REFRESH, + self::ICAL_METHOD_COUNTER, + self::ICAL_METHOD_DECLINECOUNTER, + ]; + + /** + * The complete compiled MIME message body. + * + * @var string + */ + protected $MIMEBody = ''; + + /** + * The complete compiled MIME message headers. + * + * @var string + */ + protected $MIMEHeader = ''; + + /** + * Extra headers that createHeader() doesn't fold in. + * + * @var string + */ + protected $mailHeader = ''; + + /** + * Word-wrap the message body to this number of chars. + * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. + * + * @see static::STD_LINE_LENGTH + * + * @var int + */ + public $WordWrap = 0; + + /** + * Which method to use to send mail. + * Options: "mail", "sendmail", or "smtp". + * + * @var string + */ + public $Mailer = 'mail'; + + /** + * The path to the sendmail program. + * + * @var string + */ + public $Sendmail = '/usr/sbin/sendmail'; + + /** + * Whether mail() uses a fully sendmail-compatible MTA. + * One which supports sendmail's "-oi -f" options. + * + * @var bool + */ + public $UseSendmailOptions = true; + + /** + * The email address that a reading confirmation should be sent to, also known as read receipt. + * + * @var string + */ + public $ConfirmReadingTo = ''; + + /** + * The hostname to use in the Message-ID header and as default HELO string. + * If empty, PHPMailer attempts to find one with, in order, + * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value + * 'localhost.localdomain'. + * + * @see PHPMailer::$Helo + * + * @var string + */ + public $Hostname = ''; + + /** + * An ID to be used in the Message-ID header. + * If empty, a unique id will be generated. + * You can set your own, but it must be in the format "", + * as defined in RFC5322 section 3.6.4 or it will be ignored. + * + * @see https://tools.ietf.org/html/rfc5322#section-3.6.4 + * + * @var string + */ + public $MessageID = ''; + + /** + * The message Date to be used in the Date header. + * If empty, the current date will be added. + * + * @var string + */ + public $MessageDate = ''; + + /** + * SMTP hosts. + * Either a single hostname or multiple semicolon-delimited hostnames. + * You can also specify a different port + * for each host by using this format: [hostname:port] + * (e.g. "smtp1.example.com:25;smtp2.example.com"). + * You can also specify encryption type, for example: + * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). + * Hosts will be tried in order. + * + * @var string + */ + public $Host = 'localhost'; + + /** + * The default SMTP server port. + * + * @var int + */ + public $Port = 25; + + /** + * The SMTP HELO/EHLO name used for the SMTP connection. + * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find + * one with the same method described above for $Hostname. + * + * @see PHPMailer::$Hostname + * + * @var string + */ + public $Helo = ''; + + /** + * What kind of encryption to use on the SMTP connection. + * Options: '', static::ENCRYPTION_STARTTLS, or static::ENCRYPTION_SMTPS. + * + * @var string + */ + public $SMTPSecure = ''; + + /** + * Whether to enable TLS encryption automatically if a server supports it, + * even if `SMTPSecure` is not set to 'tls'. + * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. + * + * @var bool + */ + public $SMTPAutoTLS = true; + + /** + * Whether to use SMTP authentication. + * Uses the Username and Password properties. + * + * @see PHPMailer::$Username + * @see PHPMailer::$Password + * + * @var bool + */ + public $SMTPAuth = false; + + /** + * Options array passed to stream_context_create when connecting via SMTP. + * + * @var array + */ + public $SMTPOptions = []; + + /** + * SMTP username. + * + * @var string + */ + public $Username = ''; + + /** + * SMTP password. + * + * @var string + */ + public $Password = ''; + + /** + * SMTP authentication type. Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2. + * If not specified, the first one from that list that the server supports will be selected. + * + * @var string + */ + public $AuthType = ''; + + /** + * SMTP SMTPXClient command attibutes + * + * @var array + */ + protected $SMTPXClient = []; + + /** + * An implementation of the PHPMailer OAuthTokenProvider interface. + * + * @var OAuthTokenProvider + */ + protected $oauth; + + /** + * The SMTP server timeout in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. + * + * @var int + */ + public $Timeout = 300; + + /** + * Comma separated list of DSN notifications + * 'NEVER' under no circumstances a DSN must be returned to the sender. + * If you use NEVER all other notifications will be ignored. + * 'SUCCESS' will notify you when your mail has arrived at its destination. + * 'FAILURE' will arrive if an error occurred during delivery. + * 'DELAY' will notify you if there is an unusual delay in delivery, but the actual + * delivery's outcome (success or failure) is not yet decided. + * + * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY + */ + public $dsn = ''; + + /** + * SMTP class debug output mode. + * Debug output level. + * Options: + * @see SMTP::DEBUG_OFF: No output + * @see SMTP::DEBUG_CLIENT: Client messages + * @see SMTP::DEBUG_SERVER: Client and server messages + * @see SMTP::DEBUG_CONNECTION: As SERVER plus connection status + * @see SMTP::DEBUG_LOWLEVEL: Noisy, low-level data output, rarely needed + * + * @see SMTP::$do_debug + * + * @var int + */ + public $SMTPDebug = 0; + + /** + * How to handle debug output. + * Options: + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise. + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * ```php + * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * ``` + * + * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug` + * level output is used: + * + * ```php + * $mail->Debugoutput = new myPsr3Logger; + * ``` + * + * @see SMTP::$Debugoutput + * + * @var string|callable|\Psr\Log\LoggerInterface + */ + public $Debugoutput = 'echo'; + + /** + * Whether to keep the SMTP connection open after each message. + * If this is set to true then the connection will remain open after a send, + * and closing the connection will require an explicit call to smtpClose(). + * It's a good idea to use this if you are sending multiple messages as it reduces overhead. + * See the mailing list example for how to use it. + * + * @var bool + */ + public $SMTPKeepAlive = false; + + /** + * Whether to split multiple to addresses into multiple messages + * or send them all in one message. + * Only supported in `mail` and `sendmail` transports, not in SMTP. + * + * @var bool + * + * @deprecated 6.0.0 PHPMailer isn't a mailing list manager! + */ + public $SingleTo = false; + + /** + * Storage for addresses when SingleTo is enabled. + * + * @var array + */ + protected $SingleToArray = []; + + /** + * Whether to generate VERP addresses on send. + * Only applicable when sending via SMTP. + * + * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path + * @see http://www.postfix.org/VERP_README.html Postfix VERP info + * + * @var bool + */ + public $do_verp = false; + + /** + * Whether to allow sending messages with an empty body. + * + * @var bool + */ + public $AllowEmpty = false; + + /** + * DKIM selector. + * + * @var string + */ + public $DKIM_selector = ''; + + /** + * DKIM Identity. + * Usually the email address used as the source of the email. + * + * @var string + */ + public $DKIM_identity = ''; + + /** + * DKIM passphrase. + * Used if your key is encrypted. + * + * @var string + */ + public $DKIM_passphrase = ''; + + /** + * DKIM signing domain name. + * + * @example 'example.com' + * + * @var string + */ + public $DKIM_domain = ''; + + /** + * DKIM Copy header field values for diagnostic use. + * + * @var bool + */ + public $DKIM_copyHeaderFields = true; + + /** + * DKIM Extra signing headers. + * + * @example ['List-Unsubscribe', 'List-Help'] + * + * @var array + */ + public $DKIM_extraHeaders = []; + + /** + * DKIM private key file path. + * + * @var string + */ + public $DKIM_private = ''; + + /** + * DKIM private key string. + * + * If set, takes precedence over `$DKIM_private`. + * + * @var string + */ + public $DKIM_private_string = ''; + + /** + * Callback Action function name. + * + * The function that handles the result of the send email action. + * It is called out by send() for each email sent. + * + * Value can be any php callable: http://www.php.net/is_callable + * + * Parameters: + * bool $result result of the send action + * array $to email addresses of the recipients + * array $cc cc email addresses + * array $bcc bcc email addresses + * string $subject the subject + * string $body the email body + * string $from email address of sender + * string $extra extra information of possible use + * "smtp_transaction_id' => last smtp transaction id + * + * @var string + */ + public $action_function = ''; + + /** + * What to put in the X-Mailer header. + * Options: An empty string for PHPMailer default, whitespace/null for none, or a string to use. + * + * @var string|null + */ + public $XMailer = ''; + + /** + * Which validator to use by default when validating email addresses. + * May be a callable to inject your own validator, but there are several built-in validators. + * The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option. + * + * @see PHPMailer::validateAddress() + * + * @var string|callable + */ + public static $validator = 'php'; + + /** + * An instance of the SMTP sender class. + * + * @var SMTP + */ + protected $smtp; + + /** + * The array of 'to' names and addresses. + * + * @var array + */ + protected $to = []; + + /** + * The array of 'cc' names and addresses. + * + * @var array + */ + protected $cc = []; + + /** + * The array of 'bcc' names and addresses. + * + * @var array + */ + protected $bcc = []; + + /** + * The array of reply-to names and addresses. + * + * @var array + */ + protected $ReplyTo = []; + + /** + * An array of all kinds of addresses. + * Includes all of $to, $cc, $bcc. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * + * @var array + */ + protected $all_recipients = []; + + /** + * An array of names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $all_recipients + * and one of $to, $cc, or $bcc. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * @see PHPMailer::$all_recipients + * + * @var array + */ + protected $RecipientsQueue = []; + + /** + * An array of reply-to names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $ReplyTo. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$ReplyTo + * + * @var array + */ + protected $ReplyToQueue = []; + + /** + * The array of attachments. + * + * @var array + */ + protected $attachment = []; + + /** + * The array of custom headers. + * + * @var array + */ + protected $CustomHeader = []; + + /** + * The most recent Message-ID (including angular brackets). + * + * @var string + */ + protected $lastMessageID = ''; + + /** + * The message's MIME type. + * + * @var string + */ + protected $message_type = ''; + + /** + * The array of MIME boundary strings. + * + * @var array + */ + protected $boundary = []; + + /** + * The array of available text strings for the current language. + * + * @var array + */ + protected $language = []; + + /** + * The number of errors encountered. + * + * @var int + */ + protected $error_count = 0; + + /** + * The S/MIME certificate file path. + * + * @var string + */ + protected $sign_cert_file = ''; + + /** + * The S/MIME key file path. + * + * @var string + */ + protected $sign_key_file = ''; + + /** + * The optional S/MIME extra certificates ("CA Chain") file path. + * + * @var string + */ + protected $sign_extracerts_file = ''; + + /** + * The S/MIME password for the key. + * Used only if the key is encrypted. + * + * @var string + */ + protected $sign_key_pass = ''; + + /** + * Whether to throw exceptions for errors. + * + * @var bool + */ + protected $exceptions = false; + + /** + * Unique ID used for message ID and boundaries. + * + * @var string + */ + protected $uniqueid = ''; + + /** + * The PHPMailer Version number. + * + * @var string + */ + const VERSION = '6.9.1'; + + /** + * Error severity: message only, continue processing. + * + * @var int + */ + const STOP_MESSAGE = 0; + + /** + * Error severity: message, likely ok to continue processing. + * + * @var int + */ + const STOP_CONTINUE = 1; + + /** + * Error severity: message, plus full stop, critical error reached. + * + * @var int + */ + const STOP_CRITICAL = 2; + + /** + * The SMTP standard CRLF line break. + * If you want to change line break format, change static::$LE, not this. + */ + const CRLF = "\r\n"; + + /** + * "Folding White Space" a white space string used for line folding. + */ + const FWS = ' '; + + /** + * SMTP RFC standard line ending; Carriage Return, Line Feed. + * + * @var string + */ + protected static $LE = self::CRLF; + + /** + * The maximum line length supported by mail(). + * + * Background: mail() will sometimes corrupt messages + * with headers longer than 65 chars, see #818. + * + * @var int + */ + const MAIL_MAX_LINE_LENGTH = 63; + + /** + * The maximum line length allowed by RFC 2822 section 2.1.1. + * + * @var int + */ + const MAX_LINE_LENGTH = 998; + + /** + * The lower maximum line length allowed by RFC 2822 section 2.1.1. + * This length does NOT include the line break + * 76 means that lines will be 77 or 78 chars depending on whether + * the line break format is LF or CRLF; both are valid. + * + * @var int + */ + const STD_LINE_LENGTH = 76; + + /** + * Constructor. + * + * @param bool $exceptions Should we throw external exceptions? + */ + public function __construct($exceptions = null) + { + if (null !== $exceptions) { + $this->exceptions = (bool) $exceptions; + } + //Pick an appropriate debug output format automatically + $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html'); + } + + /** + * Destructor. + */ + public function __destruct() + { + //Close any open SMTP connection nicely + $this->smtpClose(); + } + + /** + * Call mail() in a safe_mode-aware fashion. + * Also, unless sendmail_path points to sendmail (or something that + * claims to be sendmail), don't pass params (not a perfect fix, + * but it will do). + * + * @param string $to To + * @param string $subject Subject + * @param string $body Message Body + * @param string $header Additional Header(s) + * @param string|null $params Params + * + * @return bool + */ + private function mailPassthru($to, $subject, $body, $header, $params) + { + //Check overloading of mail function to avoid double-encoding + if ((int)ini_get('mbstring.func_overload') & 1) { + $subject = $this->secureHeader($subject); + } else { + $subject = $this->encodeHeader($this->secureHeader($subject)); + } + //Calling mail() with null params breaks + $this->edebug('Sending with mail()'); + $this->edebug('Sendmail path: ' . ini_get('sendmail_path')); + $this->edebug("Envelope sender: {$this->Sender}"); + $this->edebug("To: {$to}"); + $this->edebug("Subject: {$subject}"); + $this->edebug("Headers: {$header}"); + if (!$this->UseSendmailOptions || null === $params) { + $result = @mail($to, $subject, $body, $header); + } else { + $this->edebug("Additional params: {$params}"); + $result = @mail($to, $subject, $body, $header, $params); + } + $this->edebug('Result: ' . ($result ? 'true' : 'false')); + return $result; + } + + /** + * Output debugging info via a user-defined method. + * Only generates output if debug output is enabled. + * + * @see PHPMailer::$Debugoutput + * @see PHPMailer::$SMTPDebug + * + * @param string $str + */ + protected function edebug($str) + { + if ($this->SMTPDebug <= 0) { + return; + } + //Is this a PSR-3 logger? + if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { + $this->Debugoutput->debug($str); + + return; + } + //Avoid clash with built-in function names + if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) { + call_user_func($this->Debugoutput, $str, $this->SMTPDebug); + + return; + } + switch ($this->Debugoutput) { + case 'error_log': + //Don't output, just log + /** @noinspection ForgottenDebugOutputInspection */ + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking, HTML-safe output + echo htmlentities( + preg_replace('/[\r\n]+/', '', $str), + ENT_QUOTES, + 'UTF-8' + ), "
\n"; + break; + case 'echo': + default: + //Normalize line breaks + $str = preg_replace('/\r\n|\r/m', "\n", $str); + echo gmdate('Y-m-d H:i:s'), + "\t", + //Trim trailing space + trim( + //Indent for readability, except for trailing break + str_replace( + "\n", + "\n \t ", + trim($str) + ) + ), + "\n"; + } + } + + /** + * Sets message type to HTML or plain. + * + * @param bool $isHtml True for HTML mode + */ + public function isHTML($isHtml = true) + { + if ($isHtml) { + $this->ContentType = static::CONTENT_TYPE_TEXT_HTML; + } else { + $this->ContentType = static::CONTENT_TYPE_PLAINTEXT; + } + } + + /** + * Send messages using SMTP. + */ + public function isSMTP() + { + $this->Mailer = 'smtp'; + } + + /** + * Send messages using PHP's mail() function. + */ + public function isMail() + { + $this->Mailer = 'mail'; + } + + /** + * Send messages using $Sendmail. + */ + public function isSendmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'sendmail')) { + $this->Sendmail = '/usr/sbin/sendmail'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'sendmail'; + } + + /** + * Send messages using qmail. + */ + public function isQmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'qmail')) { + $this->Sendmail = '/var/qmail/bin/qmail-inject'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'qmail'; + } + + /** + * Add a "To" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addAddress($address, $name = '') + { + return $this->addOrEnqueueAnAddress('to', $address, $name); + } + + /** + * Add a "CC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('cc', $address, $name); + } + + /** + * Add a "BCC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addBCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('bcc', $address, $name); + } + + /** + * Add a "Reply-To" address. + * + * @param string $address The email address to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addReplyTo($address, $name = '') + { + return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer + * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still + * be modified after calling this function), addition of such addresses is delayed until send(). + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address + * @param string $name An optional username associated with the address + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addOrEnqueueAnAddress($kind, $address, $name) + { + $pos = false; + if ($address !== null) { + $address = trim($address); + $pos = strrpos($address, '@'); + } + if (false === $pos) { + //At-sign is missing. + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if ($name !== null && is_string($name)) { + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + } else { + $name = ''; + } + $params = [$kind, $address, $name]; + //Enqueue addresses with IDN until we know the PHPMailer::$CharSet. + //Domain is assumed to be whatever is after the last @ symbol in the address + if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) { + if ('Reply-To' !== $kind) { + if (!array_key_exists($address, $this->RecipientsQueue)) { + $this->RecipientsQueue[$address] = $params; + + return true; + } + } elseif (!array_key_exists($address, $this->ReplyToQueue)) { + $this->ReplyToQueue[$address] = $params; + + return true; + } + + return false; + } + + //Immediately add standard addresses without IDN. + return call_user_func_array([$this, 'addAnAddress'], $params); + } + + /** + * Set the boundaries to use for delimiting MIME parts. + * If you override this, ensure you set all 3 boundaries to unique values. + * The default boundaries include a "=_" sequence which cannot occur in quoted-printable bodies, + * as suggested by https://www.rfc-editor.org/rfc/rfc2045#section-6.7 + * + * @return void + */ + public function setBoundaries() + { + $this->uniqueid = $this->generateId(); + $this->boundary[1] = 'b1=_' . $this->uniqueid; + $this->boundary[2] = 'b2=_' . $this->uniqueid; + $this->boundary[3] = 'b3=_' . $this->uniqueid; + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addAnAddress($kind, $address, $name = '') + { + if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) { + $error_message = sprintf( + '%s: %s', + $this->lang('Invalid recipient kind'), + $kind + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if (!static::validateAddress($address)) { + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if ('Reply-To' !== $kind) { + if (!array_key_exists(strtolower($address), $this->all_recipients)) { + $this->{$kind}[] = [$address, $name]; + $this->all_recipients[strtolower($address)] = true; + + return true; + } + } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) { + $this->ReplyTo[strtolower($address)] = [$address, $name]; + + return true; + } + + return false; + } + + /** + * Parse and validate a string containing one or more RFC822-style comma-separated email addresses + * of the form "display name
" into an array of name/address pairs. + * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. + * Note that quotes in the name part are removed. + * + * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation + * + * @param string $addrstr The address list string + * @param bool $useimap Whether to use the IMAP extension to parse the list + * @param string $charset The charset to use when decoding the address list string. + * + * @return array + */ + public static function parseAddresses($addrstr, $useimap = true, $charset = self::CHARSET_ISO88591) + { + $addresses = []; + if ($useimap && function_exists('imap_rfc822_parse_adrlist')) { + //Use this built-in parser if it's available + $list = imap_rfc822_parse_adrlist($addrstr, ''); + // Clear any potential IMAP errors to get rid of notices being thrown at end of script. + imap_errors(); + foreach ($list as $address) { + if ( + '.SYNTAX-ERROR.' !== $address->host && + static::validateAddress($address->mailbox . '@' . $address->host) + ) { + //Decode the name part if it's present and encoded + if ( + property_exists($address, 'personal') && + //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled + defined('MB_CASE_UPPER') && + preg_match('/^=\?.*\?=$/s', $address->personal) + ) { + $origCharset = mb_internal_encoding(); + mb_internal_encoding($charset); + //Undo any RFC2047-encoded spaces-as-underscores + $address->personal = str_replace('_', '=20', $address->personal); + //Decode the name + $address->personal = mb_decode_mimeheader($address->personal); + mb_internal_encoding($origCharset); + } + + $addresses[] = [ + 'name' => (property_exists($address, 'personal') ? $address->personal : ''), + 'address' => $address->mailbox . '@' . $address->host, + ]; + } + } + } else { + //Use this simpler parser + $list = explode(',', $addrstr); + foreach ($list as $address) { + $address = trim($address); + //Is there a separate name part? + if (strpos($address, '<') === false) { + //No separate name, just use the whole thing + if (static::validateAddress($address)) { + $addresses[] = [ + 'name' => '', + 'address' => $address, + ]; + } + } else { + list($name, $email) = explode('<', $address); + $email = trim(str_replace('>', '', $email)); + $name = trim($name); + if (static::validateAddress($email)) { + //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled + //If this name is encoded, decode it + if (defined('MB_CASE_UPPER') && preg_match('/^=\?.*\?=$/s', $name)) { + $origCharset = mb_internal_encoding(); + mb_internal_encoding($charset); + //Undo any RFC2047-encoded spaces-as-underscores + $name = str_replace('_', '=20', $name); + //Decode the name + $name = mb_decode_mimeheader($name); + mb_internal_encoding($origCharset); + } + $addresses[] = [ + //Remove any surrounding quotes and spaces from the name + 'name' => trim($name, '\'" '), + 'address' => $email, + ]; + } + } + } + } + + return $addresses; + } + + /** + * Set the From and FromName properties. + * + * @param string $address + * @param string $name + * @param bool $auto Whether to also set the Sender address, defaults to true + * + * @throws Exception + * + * @return bool + */ + public function setFrom($address, $name = '', $auto = true) + { + $address = trim((string)$address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + //Don't validate now addresses with IDN. Will be done in send(). + $pos = strrpos($address, '@'); + if ( + (false === $pos) + || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported()) + && !static::validateAddress($address)) + ) { + $error_message = sprintf( + '%s (From): %s', + $this->lang('invalid_address'), + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + $this->From = $address; + $this->FromName = $name; + if ($auto && empty($this->Sender)) { + $this->Sender = $address; + } + + return true; + } + + /** + * Return the Message-ID header of the last email. + * Technically this is the value from the last time the headers were created, + * but it's also the message ID of the last sent message except in + * pathological cases. + * + * @return string + */ + public function getLastMessageID() + { + return $this->lastMessageID; + } + + /** + * Check that a string looks like an email address. + * Validation patterns supported: + * * `auto` Pick best pattern automatically; + * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0; + * * `pcre` Use old PCRE implementation; + * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; + * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. + * * `noregex` Don't use a regex: super fast, really dumb. + * Alternatively you may pass in a callable to inject your own validator, for example: + * + * ```php + * PHPMailer::validateAddress('user@example.com', function($address) { + * return (strpos($address, '@') !== false); + * }); + * ``` + * + * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator. + * + * @param string $address The email address to check + * @param string|callable $patternselect Which pattern to use + * + * @return bool + */ + public static function validateAddress($address, $patternselect = null) + { + if (null === $patternselect) { + $patternselect = static::$validator; + } + //Don't allow strings as callables, see SECURITY.md and CVE-2021-3603 + if (is_callable($patternselect) && !is_string($patternselect)) { + return call_user_func($patternselect, $address); + } + //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 + if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) { + return false; + } + switch ($patternselect) { + case 'pcre': //Kept for BC + case 'pcre8': + /* + * A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL + * is based. + * In addition to the addresses allowed by filter_var, also permits: + * * dotless domains: `a@b` + * * comments: `1234 @ local(blah) .machine .example` + * * quoted elements: `'"test blah"@example.org'` + * * numeric TLDs: `a@b.123` + * * unbracketed IPv4 literals: `a@192.168.0.1` + * * IPv6 literals: 'first.last@[IPv6:a1::]' + * Not all of these will necessarily work for sending! + * + * @see http://squiloople.com/2009/12/20/email-address-validation/ + * @copyright 2009-2010 Michael Rushton + * Feel free to use and redistribute this code. But please keep this copyright notice. + */ + return (bool) preg_match( + '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . + '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . + '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . + '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . + '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . + '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . + '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . + '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . + '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', + $address + ); + case 'html5': + /* + * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. + * + * @see https://html.spec.whatwg.org/#e-mail-state-(type=email) + */ + return (bool) preg_match( + '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . + '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', + $address + ); + case 'php': + default: + return filter_var($address, FILTER_VALIDATE_EMAIL) !== false; + } + } + + /** + * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the + * `intl` and `mbstring` PHP extensions. + * + * @return bool `true` if required functions for IDN support are present + */ + public static function idnSupported() + { + return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding'); + } + + /** + * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. + * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. + * This function silently returns unmodified address if: + * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) + * - Conversion to punycode is impossible (e.g. required PHP functions are not available) + * or fails for any reason (e.g. domain contains characters not allowed in an IDN). + * + * @see PHPMailer::$CharSet + * + * @param string $address The email address to convert + * + * @return string The encoded address in ASCII form + */ + public function punyencodeAddress($address) + { + //Verify we have required functions, CharSet, and at-sign. + $pos = strrpos($address, '@'); + if ( + !empty($this->CharSet) && + false !== $pos && + static::idnSupported() + ) { + $domain = substr($address, ++$pos); + //Verify CharSet string is a valid one, and domain properly encoded in this CharSet. + if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) { + //Convert the domain from whatever charset it's in to UTF-8 + $domain = mb_convert_encoding($domain, self::CHARSET_UTF8, $this->CharSet); + //Ignore IDE complaints about this line - method signature changed in PHP 5.4 + $errorcode = 0; + if (defined('INTL_IDNA_VARIANT_UTS46')) { + //Use the current punycode standard (appeared in PHP 7.2) + $punycode = idn_to_ascii( + $domain, + \IDNA_DEFAULT | \IDNA_USE_STD3_RULES | \IDNA_CHECK_BIDI | + \IDNA_CHECK_CONTEXTJ | \IDNA_NONTRANSITIONAL_TO_ASCII, + \INTL_IDNA_VARIANT_UTS46 + ); + } elseif (defined('INTL_IDNA_VARIANT_2003')) { + //Fall back to this old, deprecated/removed encoding + $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_2003); + } else { + //Fall back to a default we don't know about + $punycode = idn_to_ascii($domain, $errorcode); + } + if (false !== $punycode) { + return substr($address, 0, $pos) . $punycode; + } + } + } + + return $address; + } + + /** + * Create a message and send it. + * Uses the sending method specified by $Mailer. + * + * @throws Exception + * + * @return bool false on error - See the ErrorInfo property for details of the error + */ + public function send() + { + try { + if (!$this->preSend()) { + return false; + } + + return $this->postSend(); + } catch (Exception $exc) { + $this->mailHeader = ''; + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Prepare a message for sending. + * + * @throws Exception + * + * @return bool + */ + public function preSend() + { + if ( + 'smtp' === $this->Mailer + || ('mail' === $this->Mailer && (\PHP_VERSION_ID >= 80000 || stripos(PHP_OS, 'WIN') === 0)) + ) { + //SMTP mandates RFC-compliant line endings + //and it's also used with mail() on Windows + static::setLE(self::CRLF); + } else { + //Maintain backward compatibility with legacy Linux command line mailers + static::setLE(PHP_EOL); + } + //Check for buggy PHP versions that add a header with an incorrect line break + if ( + 'mail' === $this->Mailer + && ((\PHP_VERSION_ID >= 70000 && \PHP_VERSION_ID < 70017) + || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70103)) + && ini_get('mail.add_x_header') === '1' + && stripos(PHP_OS, 'WIN') === 0 + ) { + trigger_error($this->lang('buggy_php'), E_USER_WARNING); + } + + try { + $this->error_count = 0; //Reset errors + $this->mailHeader = ''; + + //Dequeue recipient and Reply-To addresses with IDN + foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { + $params[1] = $this->punyencodeAddress($params[1]); + call_user_func_array([$this, 'addAnAddress'], $params); + } + if (count($this->to) + count($this->cc) + count($this->bcc) < 1) { + throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL); + } + + //Validate From, Sender, and ConfirmReadingTo addresses + foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) { + if ($this->{$address_kind} === null) { + $this->{$address_kind} = ''; + continue; + } + $this->{$address_kind} = trim($this->{$address_kind}); + if (empty($this->{$address_kind})) { + continue; + } + $this->{$address_kind} = $this->punyencodeAddress($this->{$address_kind}); + if (!static::validateAddress($this->{$address_kind})) { + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $address_kind, + $this->{$address_kind} + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + } + + //Set whether the message is multipart/alternative + if ($this->alternativeExists()) { + $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE; + } + + $this->setMessageType(); + //Refuse to send an empty message unless we are specifically allowing it + if (!$this->AllowEmpty && empty($this->Body)) { + throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL); + } + + //Trim subject consistently + $this->Subject = trim($this->Subject); + //Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) + $this->MIMEHeader = ''; + $this->MIMEBody = $this->createBody(); + //createBody may have added some headers, so retain them + $tempheaders = $this->MIMEHeader; + $this->MIMEHeader = $this->createHeader(); + $this->MIMEHeader .= $tempheaders; + + //To capture the complete message when using mail(), create + //an extra header list which createHeader() doesn't fold in + if ('mail' === $this->Mailer) { + if (count($this->to) > 0) { + $this->mailHeader .= $this->addrAppend('To', $this->to); + } else { + $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); + } + $this->mailHeader .= $this->headerLine( + 'Subject', + $this->encodeHeader($this->secureHeader($this->Subject)) + ); + } + + //Sign with DKIM if enabled + if ( + !empty($this->DKIM_domain) + && !empty($this->DKIM_selector) + && (!empty($this->DKIM_private_string) + || (!empty($this->DKIM_private) + && static::isPermittedPath($this->DKIM_private) + && file_exists($this->DKIM_private) + ) + ) + ) { + $header_dkim = $this->DKIM_Add( + $this->MIMEHeader . $this->mailHeader, + $this->encodeHeader($this->secureHeader($this->Subject)), + $this->MIMEBody + ); + $this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader) . static::$LE . + static::normalizeBreaks($header_dkim) . static::$LE; + } + + return true; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Actually send a message via the selected mechanism. + * + * @throws Exception + * + * @return bool + */ + public function postSend() + { + try { + //Choose the mailer and send through it + switch ($this->Mailer) { + case 'sendmail': + case 'qmail': + return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); + case 'smtp': + return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); + case 'mail': + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + default: + $sendMethod = $this->Mailer . 'Send'; + if (method_exists($this, $sendMethod)) { + return $this->{$sendMethod}($this->MIMEHeader, $this->MIMEBody); + } + + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + } + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true && $this->smtp->connected()) { + $this->smtp->reset(); + } + if ($this->exceptions) { + throw $exc; + } + } + + return false; + } + + /** + * Send mail using the $Sendmail program. + * + * @see PHPMailer::$Sendmail + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function sendmailSend($header, $body) + { + if ($this->Mailer === 'qmail') { + $this->edebug('Sending with qmail'); + } else { + $this->edebug('Sending with sendmail'); + } + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver + //A space after `-f` is optional, but there is a long history of its presence + //causing problems, so we don't use one + //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html + //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Example problem: https://www.drupal.org/node/1057954 + + //PHP 5.6 workaround + $sendmail_from_value = ini_get('sendmail_from'); + if (empty($this->Sender) && !empty($sendmail_from_value)) { + //PHP config has a sender address we can use + $this->Sender = ini_get('sendmail_from'); + } + //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) { + if ($this->Mailer === 'qmail') { + $sendmailFmt = '%s -f%s'; + } else { + $sendmailFmt = '%s -oi -f%s -t'; + } + } else { + //allow sendmail to choose a default envelope sender. It may + //seem preferable to force it to use the From header as with + //SMTP, but that introduces new problems (see + //), and + //it has historically worked this way. + $sendmailFmt = '%s -oi -t'; + } + + $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender); + $this->edebug('Sendmail path: ' . $this->Sendmail); + $this->edebug('Sendmail command: ' . $sendmail); + $this->edebug('Envelope sender: ' . $this->Sender); + $this->edebug("Headers: {$header}"); + + if ($this->SingleTo) { + foreach ($this->SingleToArray as $toAddr) { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + $this->edebug("To: {$toAddr}"); + fwrite($mail, 'To: ' . $toAddr . "\n"); + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); + $this->doCallback( + ($result === 0), + [[$addrinfo['address'], $addrinfo['name']]], + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + } else { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $this->doCallback( + ($result === 0), + $this->to, + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + + return true; + } + + /** + * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters. + * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows. + * + * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report + * + * @param string $string The string to be validated + * + * @return bool + */ + protected static function isShellSafe($string) + { + //It's not possible to use shell commands safely (which includes the mail() function) without escapeshellarg, + //but some hosting providers disable it, creating a security problem that we don't want to have to deal with, + //so we don't. + if (!function_exists('escapeshellarg') || !function_exists('escapeshellcmd')) { + return false; + } + + if ( + escapeshellcmd($string) !== $string + || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""]) + ) { + return false; + } + + $length = strlen($string); + + for ($i = 0; $i < $length; ++$i) { + $c = $string[$i]; + + //All other characters have a special meaning in at least one common shell, including = and +. + //Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. + //Note that this does permit non-Latin alphanumeric characters based on the current locale. + if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { + return false; + } + } + + return true; + } + + /** + * Check whether a file path is of a permitted type. + * Used to reject URLs and phar files from functions that access local file paths, + * such as addAttachment. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function isPermittedPath($path) + { + //Matches scheme definition from https://tools.ietf.org/html/rfc3986#section-3.1 + return !preg_match('#^[a-z][a-z\d+.-]*://#i', $path); + } + + /** + * Check whether a file path is safe, accessible, and readable. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function fileIsAccessible($path) + { + if (!static::isPermittedPath($path)) { + return false; + } + $readable = is_file($path); + //If not a UNC path (expected to start with \\), check read permission, see #2069 + if (strpos($path, '\\\\') !== 0) { + $readable = $readable && is_readable($path); + } + return $readable; + } + + /** + * Send mail using the PHP mail() function. + * + * @see http://www.php.net/manual/en/book.mail.php + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function mailSend($header, $body) + { + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + + $toArr = []; + foreach ($this->to as $toaddr) { + $toArr[] = $this->addrFormat($toaddr); + } + $to = trim(implode(', ', $toArr)); + + //If there are no To-addresses (e.g. when sending only to BCC-addresses) + //the following should be added to get a correct DKIM-signature. + //Compare with $this->preSend() + if ($to === '') { + $to = 'undisclosed-recipients:;'; + } + + $params = null; + //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver + //A space after `-f` is optional, but there is a long history of its presence + //causing problems, so we don't use one + //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html + //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Example problem: https://www.drupal.org/node/1057954 + //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + + //PHP 5.6 workaround + $sendmail_from_value = ini_get('sendmail_from'); + if (empty($this->Sender) && !empty($sendmail_from_value)) { + //PHP config has a sender address we can use + $this->Sender = ini_get('sendmail_from'); + } + if (!empty($this->Sender) && static::validateAddress($this->Sender)) { + if (self::isShellSafe($this->Sender)) { + $params = sprintf('-f%s', $this->Sender); + } + $old_from = ini_get('sendmail_from'); + ini_set('sendmail_from', $this->Sender); + } + $result = false; + if ($this->SingleTo && count($toArr) > 1) { + foreach ($toArr as $toAddr) { + $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); + $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); + $this->doCallback( + $result, + [[$addrinfo['address'], $addrinfo['name']]], + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + } + } else { + $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); + $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []); + } + if (isset($old_from)) { + ini_set('sendmail_from', $old_from); + } + if (!$result) { + throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL); + } + + return true; + } + + /** + * Get an instance to use for SMTP operations. + * Override this function to load your own SMTP implementation, + * or set one with setSMTPInstance. + * + * @return SMTP + */ + public function getSMTPInstance() + { + if (!is_object($this->smtp)) { + $this->smtp = new SMTP(); + } + + return $this->smtp; + } + + /** + * Provide an instance to use for SMTP operations. + * + * @return SMTP + */ + public function setSMTPInstance(SMTP $smtp) + { + $this->smtp = $smtp; + + return $this->smtp; + } + + /** + * Provide SMTP XCLIENT attributes + * + * @param string $name Attribute name + * @param ?string $value Attribute value + * + * @return bool + */ + public function setSMTPXclientAttribute($name, $value) + { + if (!in_array($name, SMTP::$xclient_allowed_attributes)) { + return false; + } + if (isset($this->SMTPXClient[$name]) && $value === null) { + unset($this->SMTPXClient[$name]); + } elseif ($value !== null) { + $this->SMTPXClient[$name] = $value; + } + + return true; + } + + /** + * Get SMTP XCLIENT attributes + * + * @return array + */ + public function getSMTPXclientAttributes() + { + return $this->SMTPXClient; + } + + /** + * Send mail via SMTP. + * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. + * + * @see PHPMailer::setSMTPInstance() to use a different class. + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function smtpSend($header, $body) + { + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + $bad_rcpt = []; + if (!$this->smtpConnect($this->SMTPOptions)) { + throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); + } + //Sender already validated in preSend() + if ('' === $this->Sender) { + $smtp_from = $this->From; + } else { + $smtp_from = $this->Sender; + } + if (count($this->SMTPXClient)) { + $this->smtp->xclient($this->SMTPXClient); + } + if (!$this->smtp->mail($smtp_from)) { + $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); + throw new Exception($this->ErrorInfo, self::STOP_CRITICAL); + } + + $callbacks = []; + //Attempt to send to all recipients + foreach ([$this->to, $this->cc, $this->bcc] as $togroup) { + foreach ($togroup as $to) { + if (!$this->smtp->recipient($to[0], $this->dsn)) { + $error = $this->smtp->getError(); + $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']]; + $isSent = false; + } else { + $isSent = true; + } + + $callbacks[] = ['issent' => $isSent, 'to' => $to[0], 'name' => $to[1]]; + } + } + + //Only send the DATA command if we have viable recipients + if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) { + throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL); + } + + $smtp_transaction_id = $this->smtp->getLastTransactionID(); + + if ($this->SMTPKeepAlive) { + $this->smtp->reset(); + } else { + $this->smtp->quit(); + $this->smtp->close(); + } + + foreach ($callbacks as $cb) { + $this->doCallback( + $cb['issent'], + [[$cb['to'], $cb['name']]], + [], + [], + $this->Subject, + $body, + $this->From, + ['smtp_transaction_id' => $smtp_transaction_id] + ); + } + + //Create error message for any bad addresses + if (count($bad_rcpt) > 0) { + $errstr = ''; + foreach ($bad_rcpt as $bad) { + $errstr .= $bad['to'] . ': ' . $bad['error']; + } + throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE); + } + + return true; + } + + /** + * Initiate a connection to an SMTP server. + * Returns false if the operation failed. + * + * @param array $options An array of options compatible with stream_context_create() + * + * @throws Exception + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @return bool + */ + public function smtpConnect($options = null) + { + if (null === $this->smtp) { + $this->smtp = $this->getSMTPInstance(); + } + + //If no options are provided, use whatever is set in the instance + if (null === $options) { + $options = $this->SMTPOptions; + } + + //Already connected? + if ($this->smtp->connected()) { + return true; + } + + $this->smtp->setTimeout($this->Timeout); + $this->smtp->setDebugLevel($this->SMTPDebug); + $this->smtp->setDebugOutput($this->Debugoutput); + $this->smtp->setVerp($this->do_verp); + if ($this->Host === null) { + $this->Host = 'localhost'; + } + $hosts = explode(';', $this->Host); + $lastexception = null; + + foreach ($hosts as $hostentry) { + $hostinfo = []; + if ( + !preg_match( + '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/', + trim($hostentry), + $hostinfo + ) + ) { + $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry)); + //Not a valid host entry + continue; + } + //$hostinfo[1]: optional ssl or tls prefix + //$hostinfo[2]: the hostname + //$hostinfo[3]: optional port number + //The host string prefix can temporarily override the current setting for SMTPSecure + //If it's not specified, the default value is used + + //Check the host name is a valid name or IP address before trying to use it + if (!static::isValidHost($hostinfo[2])) { + $this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]); + continue; + } + $prefix = ''; + $secure = $this->SMTPSecure; + $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure); + if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) { + $prefix = 'ssl://'; + $tls = false; //Can't have SSL and TLS at the same time + $secure = static::ENCRYPTION_SMTPS; + } elseif ('tls' === $hostinfo[1]) { + $tls = true; + //TLS doesn't use a prefix + $secure = static::ENCRYPTION_STARTTLS; + } + //Do we need the OpenSSL extension? + $sslext = defined('OPENSSL_ALGO_SHA256'); + if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) { + //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled + if (!$sslext) { + throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL); + } + } + $host = $hostinfo[2]; + $port = $this->Port; + if ( + array_key_exists(3, $hostinfo) && + is_numeric($hostinfo[3]) && + $hostinfo[3] > 0 && + $hostinfo[3] < 65536 + ) { + $port = (int) $hostinfo[3]; + } + if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { + try { + if ($this->Helo) { + $hello = $this->Helo; + } else { + $hello = $this->serverHostname(); + } + $this->smtp->hello($hello); + //Automatically enable TLS encryption if: + //* it's not disabled + //* we are not connecting to localhost + //* we have openssl extension + //* we are not already using SSL + //* the server offers STARTTLS + if ( + $this->SMTPAutoTLS && + $this->Host !== 'localhost' && + $sslext && + $secure !== 'ssl' && + $this->smtp->getServerExt('STARTTLS') + ) { + $tls = true; + } + if ($tls) { + if (!$this->smtp->startTLS()) { + $message = $this->getSmtpErrorMessage('connect_host'); + throw new Exception($message); + } + //We must resend EHLO after TLS negotiation + $this->smtp->hello($hello); + } + if ( + $this->SMTPAuth && !$this->smtp->authenticate( + $this->Username, + $this->Password, + $this->AuthType, + $this->oauth + ) + ) { + throw new Exception($this->lang('authenticate')); + } + + return true; + } catch (Exception $exc) { + $lastexception = $exc; + $this->edebug($exc->getMessage()); + //We must have connected, but then failed TLS or Auth, so close connection nicely + $this->smtp->quit(); + } + } + } + //If we get here, all connection attempts have failed, so close connection hard + $this->smtp->close(); + //As we've caught all exceptions, just report whatever the last one was + if ($this->exceptions && null !== $lastexception) { + throw $lastexception; + } + if ($this->exceptions) { + // no exception was thrown, likely $this->smtp->connect() failed + $message = $this->getSmtpErrorMessage('connect_host'); + throw new Exception($message); + } + + return false; + } + + /** + * Close the active SMTP session if one exists. + */ + public function smtpClose() + { + if ((null !== $this->smtp) && $this->smtp->connected()) { + $this->smtp->quit(); + $this->smtp->close(); + } + } + + /** + * Set the language for error messages. + * The default language is English. + * + * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") + * Optionally, the language code can be enhanced with a 4-character + * script annotation and/or a 2-character country annotation. + * @param string $lang_path Path to the language file directory, with trailing separator (slash) + * Do not set this from user input! + * + * @return bool Returns true if the requested language was loaded, false otherwise. + */ + public function setLanguage($langcode = 'en', $lang_path = '') + { + //Backwards compatibility for renamed language codes + $renamed_langcodes = [ + 'br' => 'pt_br', + 'cz' => 'cs', + 'dk' => 'da', + 'no' => 'nb', + 'se' => 'sv', + 'rs' => 'sr', + 'tg' => 'tl', + 'am' => 'hy', + ]; + + if (array_key_exists($langcode, $renamed_langcodes)) { + $langcode = $renamed_langcodes[$langcode]; + } + + //Define full set of translatable strings in English + $PHPMAILER_LANG = [ + 'authenticate' => 'SMTP Error: Could not authenticate.', + 'buggy_php' => 'Your version of PHP is affected by a bug that may result in corrupted messages.' . + ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' . + ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.', + 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', + 'data_not_accepted' => 'SMTP Error: data not accepted.', + 'empty_message' => 'Message body empty', + 'encoding' => 'Unknown encoding: ', + 'execute' => 'Could not execute: ', + 'extension_missing' => 'Extension missing: ', + 'file_access' => 'Could not access file: ', + 'file_open' => 'File Error: Could not open file: ', + 'from_failed' => 'The following From address failed: ', + 'instantiate' => 'Could not instantiate mail function.', + 'invalid_address' => 'Invalid address: ', + 'invalid_header' => 'Invalid header name or value', + 'invalid_hostentry' => 'Invalid hostentry: ', + 'invalid_host' => 'Invalid host: ', + 'mailer_not_supported' => ' mailer is not supported.', + 'provide_address' => 'You must provide at least one recipient email address.', + 'recipients_failed' => 'SMTP Error: The following recipients failed: ', + 'signing' => 'Signing Error: ', + 'smtp_code' => 'SMTP code: ', + 'smtp_code_ex' => 'Additional SMTP info: ', + 'smtp_connect_failed' => 'SMTP connect() failed.', + 'smtp_detail' => 'Detail: ', + 'smtp_error' => 'SMTP server error: ', + 'variable_set' => 'Cannot set or reset variable: ', + ]; + if (empty($lang_path)) { + //Calculate an absolute path so it can work if CWD is not here + $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR; + } + + //Validate $langcode + $foundlang = true; + $langcode = strtolower($langcode); + if ( + !preg_match('/^(?P[a-z]{2})(?P