From 7f67f244f4403a9d59b00d396e3004e3eee0e17c Mon Sep 17 00:00:00 2001 From: Vilyaem Date: Thu, 6 Mar 2025 14:50:41 -0500 Subject: [PATCH] Last minute changes. --- LICENSE | 121 + README.TXT | 179 ++ composer.json | 7 + data/Doxyfile | 92 + data/items.txt | 2 + data/payments.db | 0 files/IndustrialSocietyItsFuture.pdf | Bin 0 -> 398126 bytes index.php | 560 +++++ physprep.php | 51 + vendor/autoload.php | 25 + vendor/chillerlan/php-qrcode/LICENSE-ASL-2.0 | 202 ++ vendor/chillerlan/php-qrcode/LICENSE-MIT | 21 + vendor/chillerlan/php-qrcode/NOTICE | 40 + vendor/chillerlan/php-qrcode/README.md | 168 ++ vendor/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 + vendor/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 + vendor/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 | 167 ++ .../php-settings-container/composer.json | 52 + .../rules-magic-access.neon | 4 + .../src/SettingsContainerAbstract.php | 252 +++ .../src/SettingsContainerInterface.php | 86 + vendor/composer/ClassLoader.php | 579 +++++ vendor/composer/InstalledVersions.php | 359 +++ vendor/composer/LICENSE | 21 + vendor/composer/autoload_classmap.php | 10 + vendor/composer/autoload_namespaces.php | 9 + vendor/composer/autoload_psr4.php | 12 + vendor/composer/autoload_real.php | 38 + vendor/composer/autoload_static.php | 49 + vendor/composer/installed.json | 272 +++ vendor/composer/installed.php | 59 + vendor/composer/platform_check.php | 26 + vendor/monero-integrations/monerophp/LICENSE | 21 + .../monero-integrations/monerophp/README.md | 45 + .../monerophp/composer.json | 66 + .../monerophp/composer.lock | 1286 +++++++++++ .../monerophp/docs/README.md | 166 ++ .../monerophp/docs/base58.md | 42 + .../monerophp/docs/cryptonote.md | 246 ++ .../monerophp/docs/daemonRPC.md | 392 ++++ .../monerophp/docs/ed25519.md | 29 + .../monerophp/docs/walletRPC.md | 1142 ++++++++++ .../monero-integrations/monerophp/example.php | 166 ++ .../monero-integrations/monerophp/phpcs.xml | 14 + .../monerophp/phpstan.neon | 5 + .../monerophp/src/Cryptonote.php | 384 ++++ .../monerophp/src/Varint.php | 100 + .../monerophp/src/base58.php | 395 ++++ .../monerophp/src/daemonRPC.php | 678 ++++++ .../monerophp/src/ed25519.php | 502 +++++ .../monerophp/src/jsonRPCClient.php | 217 ++ .../monerophp/src/mnemonic.php | 297 +++ .../monerophp/src/subaddress.php | 127 ++ .../monerophp/src/walletRPC.php | 1987 +++++++++++++++++ .../src/wordsets/chinese_simplified.ws.php | 1665 ++++++++++++++ .../monerophp/src/wordsets/dutch.ws.php | 1665 ++++++++++++++ .../monerophp/src/wordsets/english.ws.php | 1665 ++++++++++++++ .../monerophp/src/wordsets/english_old.ws.php | 1670 ++++++++++++++ .../monerophp/src/wordsets/esperanto.ws.php | 1665 ++++++++++++++ .../monerophp/src/wordsets/french.ws.php | 1665 ++++++++++++++ .../monerophp/src/wordsets/german.ws.php | 1666 ++++++++++++++ .../monerophp/src/wordsets/italian.ws.php | 1665 ++++++++++++++ .../monerophp/src/wordsets/japanese.ws.php | 1665 ++++++++++++++ .../monerophp/src/wordsets/lojban.ws.php | 1665 ++++++++++++++ .../monerophp/src/wordsets/portuguese.ws.php | 1666 ++++++++++++++ .../monerophp/src/wordsets/russian.ws.php | 1666 ++++++++++++++ .../monerophp/src/wordsets/spanish.ws.php | 1667 ++++++++++++++ vendor/norgul/xmpp-php/Example.php | 80 + vendor/norgul/xmpp-php/LICENSE | 21 + vendor/norgul/xmpp-php/README.md | 280 +++ vendor/norgul/xmpp-php/composer.json | 32 + vendor/norgul/xmpp-php/phpcs.xml | 23 + vendor/norgul/xmpp-php/phpmd.xml | 31 + vendor/norgul/xmpp-php/phpunit.xml | 12 + .../xmpp-php/src/AuthTypes/Authenticable.php | 13 + .../xmpp-php/src/AuthTypes/Authentication.php | 24 + .../xmpp-php/src/AuthTypes/DigestMD5.php | 14 + .../norgul/xmpp-php/src/AuthTypes/Plain.php | 14 + vendor/norgul/xmpp-php/src/Buffers/Buffer.php | 17 + .../norgul/xmpp-php/src/Buffers/Response.php | 27 + .../xmpp-php/src/Exceptions/DeadSocket.php | 17 + .../xmpp-php/src/Exceptions/StreamError.php | 14 + .../norgul/xmpp-php/src/Loggers/Loggable.php | 37 + vendor/norgul/xmpp-php/src/Loggers/Logger.php | 67 + vendor/norgul/xmpp-php/src/Options.php | 218 ++ vendor/norgul/xmpp-php/src/Socket.php | 111 + .../norgul/xmpp-php/src/Xml/Stanzas/Auth.php | 49 + vendor/norgul/xmpp-php/src/Xml/Stanzas/Iq.php | 97 + .../xmpp-php/src/Xml/Stanzas/Message.php | 29 + .../xmpp-php/src/Xml/Stanzas/Presence.php | 66 + .../xmpp-php/src/Xml/Stanzas/Stanza.php | 32 + vendor/norgul/xmpp-php/src/Xml/Xml.php | 117 + vendor/norgul/xmpp-php/src/XmppClient.php | 145 ++ vendor/norgul/xmpp-php/tests/OptionsTest.php | 121 + vendor/norgul/xmpp-php/tests/XmlTest.php | 16 + 157 files changed, 46467 insertions(+) create mode 100644 LICENSE create mode 100644 README.TXT create mode 100644 composer.json create mode 100755 data/Doxyfile create mode 100644 data/items.txt create mode 100644 data/payments.db create mode 100644 files/IndustrialSocietyItsFuture.pdf create mode 100644 index.php create mode 100644 physprep.php create mode 100644 vendor/autoload.php create mode 100644 vendor/chillerlan/php-qrcode/LICENSE-ASL-2.0 create mode 100644 vendor/chillerlan/php-qrcode/LICENSE-MIT create mode 100644 vendor/chillerlan/php-qrcode/NOTICE create mode 100644 vendor/chillerlan/php-qrcode/README.md create mode 100644 vendor/chillerlan/php-qrcode/composer.json create mode 100644 vendor/chillerlan/php-qrcode/src/Common/BitBuffer.php create mode 100644 vendor/chillerlan/php-qrcode/src/Common/ECICharset.php create mode 100644 vendor/chillerlan/php-qrcode/src/Common/EccLevel.php create mode 100644 vendor/chillerlan/php-qrcode/src/Common/GDLuminanceSource.php create mode 100644 vendor/chillerlan/php-qrcode/src/Common/GF256.php create mode 100644 vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php create mode 100644 vendor/chillerlan/php-qrcode/src/Common/IMagickLuminanceSource.php create mode 100644 vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceAbstract.php create mode 100644 vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceInterface.php create mode 100644 vendor/chillerlan/php-qrcode/src/Common/MaskPattern.php create mode 100644 vendor/chillerlan/php-qrcode/src/Common/Mode.php create mode 100644 vendor/chillerlan/php-qrcode/src/Common/Version.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/Byte.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/ECI.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/Hanzi.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/Kanji.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/Number.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/QRData.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/QRDataModeAbstract.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/QRDataModeInterface.php create mode 100755 vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/ReedSolomonEncoder.php create mode 100644 vendor/chillerlan/php-qrcode/src/Decoder/Binarizer.php create mode 100644 vendor/chillerlan/php-qrcode/src/Decoder/BitMatrix.php create mode 100644 vendor/chillerlan/php-qrcode/src/Decoder/Decoder.php create mode 100644 vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php create mode 100644 vendor/chillerlan/php-qrcode/src/Decoder/QRCodeDecoderException.php create mode 100644 vendor/chillerlan/php-qrcode/src/Decoder/ReedSolomonDecoder.php create mode 100644 vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php create mode 100644 vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php create mode 100644 vendor/chillerlan/php-qrcode/src/Detector/Detector.php create mode 100644 vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php create mode 100644 vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php create mode 100644 vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php create mode 100644 vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php create mode 100644 vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php create mode 100644 vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QREps.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QRImage.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QRImagick.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QRString.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php create mode 100644 vendor/chillerlan/php-qrcode/src/Output/QRStringText.php create mode 100755 vendor/chillerlan/php-qrcode/src/QRCode.php create mode 100644 vendor/chillerlan/php-qrcode/src/QRCodeException.php create mode 100644 vendor/chillerlan/php-qrcode/src/QROptions.php create mode 100644 vendor/chillerlan/php-qrcode/src/QROptionsTrait.php create mode 100644 vendor/chillerlan/php-settings-container/LICENSE create mode 100644 vendor/chillerlan/php-settings-container/README.md create mode 100644 vendor/chillerlan/php-settings-container/composer.json create mode 100644 vendor/chillerlan/php-settings-container/rules-magic-access.neon create mode 100644 vendor/chillerlan/php-settings-container/src/SettingsContainerAbstract.php create mode 100644 vendor/chillerlan/php-settings-container/src/SettingsContainerInterface.php create mode 100644 vendor/composer/ClassLoader.php create mode 100644 vendor/composer/InstalledVersions.php create mode 100644 vendor/composer/LICENSE create mode 100644 vendor/composer/autoload_classmap.php create mode 100644 vendor/composer/autoload_namespaces.php create mode 100644 vendor/composer/autoload_psr4.php create mode 100644 vendor/composer/autoload_real.php create mode 100644 vendor/composer/autoload_static.php create mode 100644 vendor/composer/installed.json create mode 100644 vendor/composer/installed.php create mode 100644 vendor/composer/platform_check.php create mode 100644 vendor/monero-integrations/monerophp/LICENSE create mode 100644 vendor/monero-integrations/monerophp/README.md create mode 100644 vendor/monero-integrations/monerophp/composer.json create mode 100644 vendor/monero-integrations/monerophp/composer.lock create mode 100644 vendor/monero-integrations/monerophp/docs/README.md create mode 100644 vendor/monero-integrations/monerophp/docs/base58.md create mode 100644 vendor/monero-integrations/monerophp/docs/cryptonote.md create mode 100644 vendor/monero-integrations/monerophp/docs/daemonRPC.md create mode 100644 vendor/monero-integrations/monerophp/docs/ed25519.md create mode 100644 vendor/monero-integrations/monerophp/docs/walletRPC.md create mode 100644 vendor/monero-integrations/monerophp/example.php create mode 100644 vendor/monero-integrations/monerophp/phpcs.xml create mode 100644 vendor/monero-integrations/monerophp/phpstan.neon create mode 100644 vendor/monero-integrations/monerophp/src/Cryptonote.php create mode 100644 vendor/monero-integrations/monerophp/src/Varint.php create mode 100644 vendor/monero-integrations/monerophp/src/base58.php create mode 100644 vendor/monero-integrations/monerophp/src/daemonRPC.php create mode 100644 vendor/monero-integrations/monerophp/src/ed25519.php create mode 100644 vendor/monero-integrations/monerophp/src/jsonRPCClient.php create mode 100644 vendor/monero-integrations/monerophp/src/mnemonic.php create mode 100644 vendor/monero-integrations/monerophp/src/subaddress.php create mode 100644 vendor/monero-integrations/monerophp/src/walletRPC.php create mode 100644 vendor/monero-integrations/monerophp/src/wordsets/chinese_simplified.ws.php create mode 100644 vendor/monero-integrations/monerophp/src/wordsets/dutch.ws.php create mode 100644 vendor/monero-integrations/monerophp/src/wordsets/english.ws.php create mode 100644 vendor/monero-integrations/monerophp/src/wordsets/english_old.ws.php create mode 100644 vendor/monero-integrations/monerophp/src/wordsets/esperanto.ws.php create mode 100644 vendor/monero-integrations/monerophp/src/wordsets/french.ws.php create mode 100644 vendor/monero-integrations/monerophp/src/wordsets/german.ws.php create mode 100644 vendor/monero-integrations/monerophp/src/wordsets/italian.ws.php create mode 100644 vendor/monero-integrations/monerophp/src/wordsets/japanese.ws.php create mode 100644 vendor/monero-integrations/monerophp/src/wordsets/lojban.ws.php create mode 100644 vendor/monero-integrations/monerophp/src/wordsets/portuguese.ws.php create mode 100644 vendor/monero-integrations/monerophp/src/wordsets/russian.ws.php create mode 100644 vendor/monero-integrations/monerophp/src/wordsets/spanish.ws.php create mode 100644 vendor/norgul/xmpp-php/Example.php create mode 100644 vendor/norgul/xmpp-php/LICENSE create mode 100644 vendor/norgul/xmpp-php/README.md create mode 100644 vendor/norgul/xmpp-php/composer.json create mode 100644 vendor/norgul/xmpp-php/phpcs.xml create mode 100644 vendor/norgul/xmpp-php/phpmd.xml create mode 100644 vendor/norgul/xmpp-php/phpunit.xml create mode 100644 vendor/norgul/xmpp-php/src/AuthTypes/Authenticable.php create mode 100644 vendor/norgul/xmpp-php/src/AuthTypes/Authentication.php create mode 100644 vendor/norgul/xmpp-php/src/AuthTypes/DigestMD5.php create mode 100644 vendor/norgul/xmpp-php/src/AuthTypes/Plain.php create mode 100644 vendor/norgul/xmpp-php/src/Buffers/Buffer.php create mode 100644 vendor/norgul/xmpp-php/src/Buffers/Response.php create mode 100644 vendor/norgul/xmpp-php/src/Exceptions/DeadSocket.php create mode 100644 vendor/norgul/xmpp-php/src/Exceptions/StreamError.php create mode 100644 vendor/norgul/xmpp-php/src/Loggers/Loggable.php create mode 100644 vendor/norgul/xmpp-php/src/Loggers/Logger.php create mode 100644 vendor/norgul/xmpp-php/src/Options.php create mode 100644 vendor/norgul/xmpp-php/src/Socket.php create mode 100644 vendor/norgul/xmpp-php/src/Xml/Stanzas/Auth.php create mode 100644 vendor/norgul/xmpp-php/src/Xml/Stanzas/Iq.php create mode 100644 vendor/norgul/xmpp-php/src/Xml/Stanzas/Message.php create mode 100644 vendor/norgul/xmpp-php/src/Xml/Stanzas/Presence.php create mode 100644 vendor/norgul/xmpp-php/src/Xml/Stanzas/Stanza.php create mode 100644 vendor/norgul/xmpp-php/src/Xml/Xml.php create mode 100644 vendor/norgul/xmpp-php/src/XmppClient.php create mode 100644 vendor/norgul/xmpp-php/tests/OptionsTest.php create mode 100644 vendor/norgul/xmpp-php/tests/XmlTest.php diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/README.TXT b/README.TXT new file mode 100644 index 0000000..ce5a258 --- /dev/null +++ b/README.TXT @@ -0,0 +1,179 @@ +------------------------------------------------------------------------------ + + + /| //| | + //| // | | ___ __ ___ ___ + // | // | | // ) ) // ) ) // ) ) // ) ) // / / + // | // | | // / / // / / //___/ / // / / ((___/ / +// |// | | ((___/ / // / / // ((___( ( / / + + + + +Monpay (now renamed to Neropay) is a simple GET-only PHP Monero & Wownero +cryptocurrency payment system. it uses the XMPP chat protocol for +notifications, receipts, and support. + + + Manifest + +It is unnecessarily difficult to integrate a cryptocurrency payment system into +one or more online shops and businesses, one is pressured and tempted into +using foreign and bloated APIs or plugins into preexisting (and terrible) +Content Management Systems (CMS) such as 'Wordpress', and yet still not make +100% of the profit, as well as dealing with restrictions, such as a cap on +number of transactions per month. There is some libre-licensed projects that +allow individuals to host crypto payments, but these usually come with caveats +of their own, such as making Monero a second priority, using Javascript, having +a difficult setup, large number of lines, not handling multiple shops or +services, and not allowing for user instantiated services and businesses. + + + Features + + +Pros: + + * Sale of digital items + * Sale of physical items + * XMPP notifications + * under 1K LOC + * No javascript + * Easy integration with websites + * Optionally redirect users after purchase + * Generates QR codes during payment + * No middle man (other than you) + * No fees + * Self hosted + * No docker + * Support for Wownero WOW + * Automatic documentation via doxygen + +Cons: + * PHP sucks + * so does the 'composer' package manager + + + Setup + +1. Install in a web directory +2. Install php-fpm, and php-curl if you havn't, check the output of phpinfo(); +3. SQLite for PHP is usually default, check if so +4. Install the monero package, remote node RPC setup is below + +monero-wallet-rpc --rpc-bind-port 18083 --disable-rpc-login --wallet-dir wallets --daemon-address soynero.net.org.com.edu.gov:18081 + + +Consider making a service, the following is an example SysVInit service +script: + +------------------------------- +#!/bin/sh +### BEGIN INIT INFO +# Provides: monero +# Required-Start: $local_fs $remote_fs $network $syslog $named +# Required-Stop: $local_fs $remote_fs $network $syslog $named +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Start Monero Wallet RPC +# Description: Start Monero Wallet RPC +### END INIT INFO + +NAME=monero +DESC=monero + +. /lib/init/vars.sh +. /lib/lsb/init-functions + + +start_monero() { + # Start the daemon/service + #Change daemon address if necessary! + monero-wallet-rpc --rpc-bind-port 18083 --disable-rpc-login --wallet-dir /var/www/neropay/wallets --daemon-address 127.0.0.1:18081 --log-file /var/log/monero.log --detach +} + + +stop_monero() { + pkill monero-wallet +} + +case "$1" in + start) + start_monero + ;; + stop) + stop_monero + case "$?" in + 0|1) log_end_msg 0 ;; + 2) log_end_msg 1 ;; + esac + ;; + *) + echo "Usage: $NAME {start|stop|restart}" >&2 + exit 3 + ;; +esac + +------------------------------- + +5. Ensure www-data ownership/permissions, unix nonsense +6. Put items in data/items.txt according to the format +7. NGINX/httpd web server configuration +8. Test to see if transactions work +9. Turn off error reporting and benchmarking for live usage +10. Make 'data' directory inaccessible or out of web root +11. Enjoy + + Specification + +Monpay uses an SQLite database to store concurrent and completed payments, +this was done because Vilyaem wanted to experiment with SQLite, and to prevent +race-condition problems that result in the loss of funds for both parties. + +Monpay uses a simple CSV formatted plain-text file as a 'database' for items, +each entry defining the name (or filename) of the item, the type of the item +(physical or digital), the price in XMR of the item, the seller's XMPP address +(for notifications), the Monero address, an optional entry for a 'ledger path' +which would be used to communicate to a locally hosted site what transactions +have occured, and finally, an optional 'redirect' entry, when the transaction +is complete, the user will be redirected to the page specified in the +'redirect' field. + + +Payments: + +In the 'payments' table in 'payments.db' + +IP Address (ip) +Temporary monero wallet name (walletname) +Item Name (item) +Amount due (dues) +Status (status) + +Items: + + name,type,price,seller xmpp,seller payment address,ledgerdir,redirect + + Dependencies + +* xmpp-php by norgul +* php-qrcode by chillerlan +* monerophp by Monero Integrations + + + Roadmap + +1. Successful digital transactions X +2. Successful physical transactions X +3. Successful merchant support +4. Support Wownero? X +5. Support I2P/Tor? +6. Complete rewrite in C CGI? + + + License + +Public Domain CC0 + + +----------------------------------------------------------------------------- diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..573c065 --- /dev/null +++ b/composer.json @@ -0,0 +1,7 @@ +{ + "require": { + "norgul/xmpp-php": "^2.2", + "chillerlan/php-qrcode": "^5.0", + "monero-integrations/monerophp": "@dev" + } +} diff --git a/data/Doxyfile b/data/Doxyfile new file mode 100755 index 0000000..4a1bdb3 --- /dev/null +++ b/data/Doxyfile @@ -0,0 +1,92 @@ +# Neropay Doxyfile (a configuration for automatic docs generator Doxygen) + +PROJECT_NAME = "Neropay" +DOXYFILE_ENCODING = UTF-8 +INPUT_ENCODING = UTF-8 +PROJECT_BRIEF = "'Nero payment system" +OUTPUT_DIRECTORY = ./doc +CREATE_SUBDIRS = NO +ALLOW_UNICODE_NAMES = NO +OUTPUT_LANGUAGE = English +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +OPTIMIZE_OUTPUT_FOR_C = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_TIMESTAMP = YES +HTML_DYNAMIC_SECTIONS = NO +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_HTML = YES +GENERATE_DOCSET = NO +GENERATE_LATEX = NO +GENERATE_AUTOGEN_DEF = NO +GENERATE_HTMLHELP = NO +GENERATE_CHI = NO +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +FULL_PATH_NAMES = YES +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 4 +MARKDOWN_SUPPORT = YES +SUBGROUPING = YES +TYPEDEF_HIDES_STRUCT = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +HIDE_COMPOUND_REFERENCE = NO +SHOW_INCLUDE_FILES = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SHOW_USED_FILES = YES +SHOW_FILES = YES +SHOW_NAMESPACES = YES +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +EXTRACT_ALL = YES +EXTRACT_PACKAGE = NO +EXTRACT_PRIVATE = YES +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +EXTRACT_STATIC = YES +EXTRACT_ANON_NSPACES = NO +RECURSIVE = NO +EXCLUDE_SYMLINKS = NO +EXAMPLE_RECURSIVE = NO +FILTER_SOURCE_FILES = NO +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +SOURCE_TOOLTIPS = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +CLANG_ASSISTED_PARSING = NO +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 5 +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +EXT_LINKS_IN_WINDOW = NO +SEARCHENGINE = YES +SEARCHDATA_FILE = searchdata.xml +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +SKIP_FUNCTION_MACROS = NO +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +EXTERNAL_PAGES = YES +HAVE_DOT = NO diff --git a/data/items.txt b/data/items.txt new file mode 100644 index 0000000..5594a79 --- /dev/null +++ b/data/items.txt @@ -0,0 +1,2 @@ +IndustrialSocietyItsFuture.pdf,digital,0.000005,kenyaz@vilyaem.xyz,48Sxa8J6518gqp4WeGtQ4rLe6SctPrEnnCqm6v6ydjLwRPi9Uh9gvVuUsU2AEDw75meTHCNY8KfU6Txysom4Bn5qPKMJ75w,NULL,NULL +Example,physical,0.000005,kenyaz@vilyaem.xyz,48Sxa8J6518gqp4WeGtQ4rLe6SctPrEnnCqm6v6ydjLwRPi9Uh9gvVuUsU2AEDw75meTHCNY8KfU6Txysom4Bn5qPKMJ75w,NULL,NULL diff --git a/data/payments.db b/data/payments.db new file mode 100644 index 0000000..e69de29 diff --git a/files/IndustrialSocietyItsFuture.pdf b/files/IndustrialSocietyItsFuture.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6a21f42e07f01965c57814d461215f7ba9b17942 GIT binary patch literal 398126 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 Ig&lmxY*1N>Jz9@eZ(L8>5 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 + + +$db->exec("PRAGMA busy_timeout = 60000;"); /*Hold your horses*/ +$db->exec("PRAGMA journal_mode=WAL;"); + +$open = $db->query('CREATE TABLE IF NOT EXISTS "payments" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + "ip" TEXT, + "walletname" TEXT, + "item" TEXT, + "dues" TEXT, + "status" TEXT, + "address" TEXT, + "date" TEXT + )'); + +if($open === false){ + echo 'Failed to create database!'; + exit; +} + + + +/*wallet address string we use to avoid + redundant open_wallet calls*/ +$newwaladdr = NULL; + + +/*Benchmarking*/ +$time = microtime(); +$time = explode(' ', $time); +$time = $time[1] + $time[0]; +$start = $time; + + +?> + + + +Neropay + + + +

+

Neropay

+A tiny Monero payment system
+Welcome to Neropay, a tiny payment processsing system for Monero

'; + exit; + } + + if(isset($_GET['wow'])){ + $wow = true; + echo '

You are making a Wownero transaction!

'; + } + else{ + $wow = false; + } + + /* Globals */ + $ip = $_SERVER['REMOTE_ADDR']; + + /*Nasty sanitization*/ + $item = substr(str_replace([",", "#", "$", "%", "*", "~", "'", "=", "{", "[", "|", "`", "^", "]", "}", ":", ";", "<", ">", "/", "?", "&"], "",htmlspecialchars($_GET['item'])),0,256); + + if(isset($_GET['addr'])){ + $buyinfo = substr(str_replace([",", "#", "$", "%", "*", "~", "'", "=", "{", "[", "|", "`", "^", "]", "}", ":", ";", "<", ">", "/", "?", "&"], "",htmlspecialchars($_GET['addr'])),0,512); + } + + /*Setup XMPP*/ + /* + $xmppsettings = new Norgul\Xmpp\Options(); + $xmppsettings + ->setHost(XMPPHOST) + ->setUsername(XMPPUSER) + ->setPassword(XMPPPASS); + $xmppclient = new Norgul\Xmpp\XmppClient($xmppsettings); + $xmppclient->connect(); + * / + + + /*Setup Monero or Wownero*/ + if(!$wow){ + $walletRPC = new walletRPC('127.0.0.1', 18083, false); + } + else{ + $walletRPC = new walletRPC('127.0.0.1', 18084, false); + } + + /********************************************* + * Description - Error messages to give to consoomers + * Author - Vilyaem + * *******************************************/ + function NeropayError($msg){ + echo '

Error, please report the following to kenyaz [at] vilyaem.xyz via Email or XMPP: ' . $msg . '

'; + SendXMPP(VILXMPPADDR,$msg); + /*$xmppclient->disconnect();*/ + exit; + } + + /********************************************* + * Description - Retarded, PHP does scary stuff + * when you least expect it + * Author - Vilyaem + * *******************************************/ + function IsBad($val){ + if(!isset($val) || $val === false || $val === NULL){ + return true; + } + else{ + return false; + } + } + + /********************************************* + * Description - Send an XMPP message + * Author - Vilyaem + * Date - Jul 21 2024 + * *******************************************/ + function SendXMPP($user,$msg){ + /* + global $xmppsettings,$xmppclient; + $xmppclient->message->send($msg, $user); + */ + + /*Just write to a file for the moment, + is XMPP abhorrently slow?*/ + $date = date('Y-m-d h:i:s A'); + $file = fopen("data/notes.txt","a") or NeropayError("10013"); + fwrite($file,"$date: $msg\n"); + fclose($file); + } + + /********************************************* + * Description - Get property of a list. + * Author - Vilyaem + * *******************************************/ + function GetListProp($object,$prop,$file){ + $scanitems = file($file,FILE_IGNORE_NEW_LINES); + /*var_dump($object);*/ + /*var_dump($file);*/ + /*var_dump($prop);*/ + foreach($scanitems as $scanitem){ + $itemdata = explode(",",$scanitem); + /*var_dump($itemdata);*/ + /*var_dump($itemdata[0]);*/ + if($itemdata[0] === $object){ + return $itemdata[$prop];/*return type*/ + } + } + return NULL; /*failed*/ + } + + /********************************************* + * Description - Find payment + * Used when checking if a payment is already ongoing + * Author - Vilyaem + * *******************************************/ + function FindPayment(){ + global $db,$item,$ip; + + + $stm = $db->prepare('SELECT * FROM payments WHERE ip = :ip AND item = :item AND date = :date AND status = :status'); + $stm->bindValue(':ip',$ip); + $stm->bindValue(':item',$item); + $stm->bindValue(':date',date('Y-m-d')); + $stm->bindValue(':status',"unpaid"); + $res = $stm->execute(); + + $row = $res->fetchArray(SQLITE3_NUM); + + + /*Can't find it*/ + if(IsBad($row)){ + return false; + } + + if(IsBad($res)){ + return false; /*failed*/ + } + else{ + return true; /*There is one*/ + } + } + + /********************************************* + * Description - Ask for payment + * Author - Vilyaem + * *******************************************/ + function AskPayment(){ + global $db,$item,$ip,$walletRPC,$newwaladdr,$wow; + + /*Access the database and and get the dues and wallet name*/ + $stm = $db->prepare('SELECT * FROM payments WHERE ip = :ip AND item = :item AND date = :date AND status = :status'); + + if($stm === false){ + NeropayError("Error code 1002"); + } + + $stm->bindValue(":ip",$ip); + $stm->bindValue(":item",$item); + $stm->bindValue(":date",date('Y-m-d')); + $stm->bindValue(":status","unpaid"); + $res = $stm->execute(); + + if($res === false){ + NeropayError("Error code 1003"); + } + + /*echo var_dump($stm) . "
";*/ + /*echo var_dump($res) . "
";*/ + + /*echo var_dump($res->numColumns()) . "
";*/ + /*echo var_dump($res->columnType(0)) . "
";*/ + + + if ($res->numColumns() && $res->columnType(0) === SQLITE3_NULL) { + NeropayError("Error code 1004"); + } + + if($res->reset() === false){ + NeropayError("Error code 1005"); + } + + + $row = $res->fetchArray(SQLITE3_NUM); + + /*echo var_dump($stm) . "
";*/ + /*echo var_dump($res) . "
";*/ + /*echo var_dump($row) . "
";*/ + + if($row === false){ + NeropayError("Error code 1006"); + } + /* + else{ + echo "

AskPayment got a valid row!

"; + } + */ + + /*$db = null;*/ + + if($newwaladdr === NULL){ + $wallet = $walletRPC->open_wallet($row[2],''); + $addr = $walletRPC->get_address(); + } + else{ + $addr = $newwaladdr; + } + + $amnt = $row[4]; + + /*echo "

" . var_dump($addr) . "
";*/ + + echo 'QR Code'; + + /*var_dump($wow);*/ + + if(!$wow){ + echo '

Please pay ' . $amnt . ' XMR to ' . $addr['address'] . '

'; + } + else{ + echo '

Please pay ' . $amnt * 4005 . ' WOW to ' . $addr['address'] . '

'; + } + } + + /********************************************* + * Description - Get an Item given a payment ID + * Author - Vilyaem + * *******************************************/ + function GetItemByPayment(){ + global $db,$ip,$item; + $stm = $db->prepare('SELECT * FROM payments WHERE ip = ? AND item = ? AND date = ? AND status = ?'); + $stm->bindValue(":ip",$ip); + $stm->bindValue(":item",$item); + $stm->bindValue(":date",date('Y-m-d')); + $stm->bindValue(":status","unpaid"); + $res = $stm->execute(); + + if($res === false){ + NeropayError("Error code 1007"); + } + + $row = $res->fetchArray(SQLITE3_NUM); + + + return $row[2]; /*This should be the item*/ + } + + /********************************************* + * Description - Create a new concurrent payment + * in the database. This also creates a new wallet + * Author - Vilyaem + * *******************************************/ + function NewPayment(){ + global $db,$item,$ip,$walletRPC,$newwaladdr; + + /*$db = null;*/ + + /*Create the wallet*/ + + $namenum=rand(); + $name=md5($namenum); + $makewallet = $walletRPC->create_wallet($name, ''); + $newwallet = $walletRPC->open_wallet($name,''); + $newaddress = $walletRPC->get_address(); + + + $newwaladdr = $newaddress; + + $newprice = GetListProp($item,2,"data/items.txt"); + + /*If item does not exist, stop!*/ + if($newprice === NULL){ + NeropayError("Error code 1008"); + } + + /*$db = new SQLite3(SQLDBPATH);*/ + + /*Add the payment to the database*/ + $stm = $db->prepare('INSERT INTO "payments" ("ip", "walletname", + "item", "dues", "status", "address", "date") + VALUES (:ip, :walletname, :item, :dues, :status, :address, :date)'); + + if($stm === false){ + NeropayError("Error code 1009"); + } + + $stm->bindValue(':ip', $ip); + $stm->bindValue(':walletname', $name); + $stm->bindValue(':item', $item); + $stm->bindValue(':dues', $newprice); + $stm->bindValue(':status', 'unpaid'); + $stm->bindValue(':address', 'NULL'); + $stm->bindValue(':date', date('Y-m-d')); + $res = $stm->execute(); + + if($stm === false || $res === false){ + NeropayError("Error code 1010"); + } + + /*echo "newpayment: $ip,$name,$item";*/ + + + /*Notify Vilyaem that a new payment process has begun*/ + SendXMPP(VILXMPPADDR,'!!! A new payment has begun: ' . $ip . ',' . $item . ',' . $name); + + /*Return wallet's address*/ + return $newaddress; + } + + /********************************************* + * Description - Success + * Author - Vilyaem + * *******************************************/ + function Success(){ + global $ip,$item,$buyinfo; + $date = date("Y-m-d"); + $itemtype = GetListProp($item,1,"data/items.txt"); + $profit = GetListProp($item,2,"data/items.txt"); + + + /*Notify via XMPP of the success*/ + SendXMPP(VILXMPPADDR,'Congratulations! CLIENT: ' . $ip . ' ITEM: ' . $item . ' DATE: ' . $date . ' PROFIT: ' . $profit); + + /*If the item is digital, give the user an immediate download*/ + if($itemtype === "digital"){ /*digital*/ + ob_clean(); + flush(); + $filename = "files/$item"; + $mimetype = mime_content_type($filename); + header("Content-Type: ".$mimetype ); + header("Content-Disposition: attachment; filename=\"$item\""); + /*echo readfile($filename); */ + readfile($filename); + + + } + else{ /*physical*/ + /*NeropayError("Error code 1009");*/ + if(isset($buyinfo)){ + SendXMPP(VILXMMRADDR,$buyinfo); + echo "

Your shipping/contact information has been sent.

"; + } + } + + /*Congratulate the user*/ + echo "

Congratulations on your purchase of $item!

"; + } + + /********************************************* + * Description - Check if payment is complete + * Author - Vilyaem + * *******************************************/ + function CheckPayment(){ + global $db,$item,$ip,$walletRPC,$wow; + + /*Get payment from database*/ + $stm = $db->prepare('SELECT * FROM payments WHERE ip = :ip AND item = :item AND date = :date'); + $stm->bindValue(':ip',$ip); + $stm->bindValue(':item',$item); + $stm->bindValue(':date',date('Y-m-d')); + $res = $stm->execute(); + + if(IsBad($res)){ + NeropayError("Error code 1011"); + } + + $row = $res->fetchArray(SQLITE3_NUM); + + if(IsBad($row)){ + NeropayError("Error code 1012"); + } + + /*echo var_dump($row) . "
";*/ + + + /*If payment is already done succeed*/ + if($row[4] === "finished"){ + return true; + } + + /*If not, check it*/ + + + /*Open the wallet and see if the payment has been met*/ + $mywallet = $walletRPC->open_wallet($row[2]); + $mybalance = $walletRPC->get_balance()['balance']; + + /*echo var_dump($mywallet) . "
";*/ + /*echo var_dump($mybalance) . "
";*/ + + if(($mybalance >= $row[4] && $wow === false) || ($mybalance >= $row[4] * 4005 && $wow === true)){ + /*if(true){*/ + + /*transfer the money TODO this API call's args are confusing*/ + /*echo '

' . $walletRPC->transfer() . '

'*/ + + /*Mark as finished*/ + $stm = $db->prepare("UPDATE payments SET status='finished' WHERE ip = :ip AND item = :item AND date = :date"); + $stm->bindValue(':ip',$ip); + $stm->bindValue(':item',$item); + $stm->bindValue(':date',date('Y-m-d')); + $res = $stm->execute(); + + if($res === false){ + NeropayError("Error code 1013"); + } + + $row = $res->fetchArray(SQLITE3_NUM); + + + return true; + } + else{ /*Payment has not succeeded, and the balance has not changed.*/ + return false; + } + } + + /********************************************* + * Description - Main + * Author - Vilyaem + * *******************************************/ + function Main(){ + /*Check if this payment is already ongoing*/ + if(FindPayment() === true){ + /*echo "

Found the payment!

";*/ + /*If payment is already ongoing, check if it has been paid, + if so, show success, otherwise ask for payment*/ + if(CheckPayment() === true){ + /*echo "

Check payment succeeded

";*/ + Success(); + } + else{ + /*echo "

Check payment falied, ask for payment

";*/ + AskPayment(); + } + } + /*If it is not, create it and ask for payment*/ + else{ + /*echo "

Failed to find the payment!

";*/ + NewPayment(); + AskPayment(); + } + } + + Main(); + /*$xmppclient->disconnect();*/ + + } + + ?> + +

+ Note: Payments may take a couple minutes to go through. +
Report irregularities or problems to kenyaz [at] vilyaem.xyz +
The page should refresh automatically, if not, refresh manually
+
WARNING: Monero might liberate you. +

+ + Page generated in '.$total_time.' seconds.'; + ?> + +
+ + + diff --git a/physprep.php b/physprep.php new file mode 100644 index 0000000..5499bfe --- /dev/null +++ b/physprep.php @@ -0,0 +1,51 @@ + + + + +Neropay + + + +
+

Neropay

+A tiny Monero payment system
+
+ + + +
+ +
+ +

+Note: Payments may take a couple minutes to go through. +
Report irregularities or problems to kenyaz [at] vilyaem.xyz
+
WARNING: Monero might liberate you. +

+ +
+ + + + diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..8a768df --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,25 @@ + + +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/vendor/chillerlan/php-qrcode/NOTICE b/vendor/chillerlan/php-qrcode/NOTICE new file mode 100644 index 0000000..596130b --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/README.md b/vendor/chillerlan/php-qrcode/README.md new file mode 100644 index 0000000..9aaaaea --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/composer.json b/vendor/chillerlan/php-qrcode/composer.json new file mode 100644 index 0000000..7e74be5 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Common/BitBuffer.php b/vendor/chillerlan/php-qrcode/src/Common/BitBuffer.php new file mode 100644 index 0000000..4a59f2b --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Common/ECICharset.php b/vendor/chillerlan/php-qrcode/src/Common/ECICharset.php new file mode 100644 index 0000000..0c98e36 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Common/EccLevel.php b/vendor/chillerlan/php-qrcode/src/Common/EccLevel.php new file mode 100644 index 0000000..789d7f7 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Common/GDLuminanceSource.php b/vendor/chillerlan/php-qrcode/src/Common/GDLuminanceSource.php new file mode 100644 index 0000000..027466f --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Common/GF256.php b/vendor/chillerlan/php-qrcode/src/Common/GF256.php new file mode 100644 index 0000000..d8ba095 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php b/vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php new file mode 100644 index 0000000..ae361b9 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Common/IMagickLuminanceSource.php b/vendor/chillerlan/php-qrcode/src/Common/IMagickLuminanceSource.php new file mode 100644 index 0000000..d4f66c5 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceAbstract.php b/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceAbstract.php new file mode 100644 index 0000000..432a6e0 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceInterface.php b/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceInterface.php new file mode 100644 index 0000000..64409e3 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Common/MaskPattern.php b/vendor/chillerlan/php-qrcode/src/Common/MaskPattern.php new file mode 100644 index 0000000..8441a7f --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Common/Mode.php b/vendor/chillerlan/php-qrcode/src/Common/Mode.php new file mode 100644 index 0000000..523d379 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Common/Version.php b/vendor/chillerlan/php-qrcode/src/Common/Version.php new file mode 100644 index 0000000..fe7240f --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php b/vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php new file mode 100644 index 0000000..77242d7 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Data/Byte.php b/vendor/chillerlan/php-qrcode/src/Data/Byte.php new file mode 100644 index 0000000..10ab852 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Data/ECI.php b/vendor/chillerlan/php-qrcode/src/Data/ECI.php new file mode 100644 index 0000000..3029e83 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Data/Hanzi.php b/vendor/chillerlan/php-qrcode/src/Data/Hanzi.php new file mode 100644 index 0000000..4476ad9 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Data/Kanji.php b/vendor/chillerlan/php-qrcode/src/Data/Kanji.php new file mode 100644 index 0000000..e42f96d --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Data/Number.php b/vendor/chillerlan/php-qrcode/src/Data/Number.php new file mode 100644 index 0000000..285be37 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php b/vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php new file mode 100644 index 0000000..04ffbd7 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Data/QRData.php b/vendor/chillerlan/php-qrcode/src/Data/QRData.php new file mode 100644 index 0000000..4b2dcf7 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Data/QRDataModeAbstract.php b/vendor/chillerlan/php-qrcode/src/Data/QRDataModeAbstract.php new file mode 100644 index 0000000..94b93ac --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Data/QRDataModeInterface.php b/vendor/chillerlan/php-qrcode/src/Data/QRDataModeInterface.php new file mode 100644 index 0000000..321cf60 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php b/vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php new file mode 100755 index 0000000..d92c1a7 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Data/ReedSolomonEncoder.php b/vendor/chillerlan/php-qrcode/src/Data/ReedSolomonEncoder.php new file mode 100644 index 0000000..6044437 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Decoder/Binarizer.php b/vendor/chillerlan/php-qrcode/src/Decoder/Binarizer.php new file mode 100644 index 0000000..7b7b49f --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Decoder/BitMatrix.php b/vendor/chillerlan/php-qrcode/src/Decoder/BitMatrix.php new file mode 100644 index 0000000..ea2e321 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Decoder/Decoder.php b/vendor/chillerlan/php-qrcode/src/Decoder/Decoder.php new file mode 100644 index 0000000..6f369a6 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php b/vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php new file mode 100644 index 0000000..02b4d79 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Decoder/QRCodeDecoderException.php b/vendor/chillerlan/php-qrcode/src/Decoder/QRCodeDecoderException.php new file mode 100644 index 0000000..11157af --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Decoder/ReedSolomonDecoder.php b/vendor/chillerlan/php-qrcode/src/Decoder/ReedSolomonDecoder.php new file mode 100644 index 0000000..2bd539a --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php b/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php new file mode 100644 index 0000000..72feafd --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php b/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php new file mode 100644 index 0000000..ca62c6f --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Detector/Detector.php b/vendor/chillerlan/php-qrcode/src/Detector/Detector.php new file mode 100644 index 0000000..e43798b --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php b/vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php new file mode 100644 index 0000000..1f799e8 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php b/vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php new file mode 100644 index 0000000..755d08c --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php b/vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php new file mode 100644 index 0000000..0d915e3 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php b/vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php new file mode 100644 index 0000000..4f1f654 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php b/vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php new file mode 100644 index 0000000..2444e19 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php b/vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php new file mode 100644 index 0000000..92997a7 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php b/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php new file mode 100644 index 0000000..bf30f1b --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QREps.php b/vendor/chillerlan/php-qrcode/src/Output/QREps.php new file mode 100644 index 0000000..a04f20d --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php b/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php new file mode 100644 index 0000000..a802954 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php new file mode 100644 index 0000000..f3ed6ea --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php new file mode 100644 index 0000000..268ebe7 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php new file mode 100644 index 0000000..a021309 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php new file mode 100644 index 0000000..6be36e2 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php new file mode 100644 index 0000000..2db3fd5 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php new file mode 100644 index 0000000..cf8dfa9 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QRImage.php b/vendor/chillerlan/php-qrcode/src/Output/QRImage.php new file mode 100644 index 0000000..cda496d --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php b/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php new file mode 100644 index 0000000..cfd3a58 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php b/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php new file mode 100644 index 0000000..22551e3 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php b/vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php new file mode 100644 index 0000000..65dc49a --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php b/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php new file mode 100644 index 0000000..735c418 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php b/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php new file mode 100644 index 0000000..7ac64ff --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php b/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php new file mode 100644 index 0000000..919f1ba --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QRString.php b/vendor/chillerlan/php-qrcode/src/Output/QRString.php new file mode 100644 index 0000000..cb70a6c --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php b/vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php new file mode 100644 index 0000000..6f2e7d5 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/Output/QRStringText.php b/vendor/chillerlan/php-qrcode/src/Output/QRStringText.php new file mode 100644 index 0000000..f475a2a --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/QRCode.php b/vendor/chillerlan/php-qrcode/src/QRCode.php new file mode 100755 index 0000000..bca6ac5 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/QRCodeException.php b/vendor/chillerlan/php-qrcode/src/QRCodeException.php new file mode 100644 index 0000000..600ce50 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/QROptions.php b/vendor/chillerlan/php-qrcode/src/QROptions.php new file mode 100644 index 0000000..91b5b45 --- /dev/null +++ b/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/vendor/chillerlan/php-qrcode/src/QROptionsTrait.php b/vendor/chillerlan/php-qrcode/src/QROptionsTrait.php new file mode 100644 index 0000000..d2bc8c2 --- /dev/null +++ b/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/vendor/chillerlan/php-settings-container/LICENSE b/vendor/chillerlan/php-settings-container/LICENSE new file mode 100644 index 0000000..25d371f --- /dev/null +++ b/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/vendor/chillerlan/php-settings-container/README.md b/vendor/chillerlan/php-settings-container/README.md new file mode 100644 index 0000000..3fb122d --- /dev/null +++ b/vendor/chillerlan/php-settings-container/README.md @@ -0,0 +1,167 @@ +# 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. + +A PHPStan ruleset to exclude errors generated by accessing magic properties on `SettingsContainerInterface` can be found in `rules-magic-access.neon`. + + +### 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/vendor/chillerlan/php-settings-container/composer.json b/vendor/chillerlan/php-settings-container/composer.json new file mode 100644 index 0000000..16b8e5e --- /dev/null +++ b/vendor/chillerlan/php-settings-container/composer.json @@ -0,0 +1,52 @@ +{ + "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": { + "phpmd/phpmd": "^2.15", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-deprecation-rules": "^1.2", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^3.10" + }, + "autoload": { + "psr-4": { + "chillerlan\\Settings\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "chillerlan\\SettingsTest\\": "tests" + } + }, + "scripts": { + "phpunit": "@php vendor/bin/phpunit", + "phpstan": "@php vendor/bin/phpstan" + }, + "config": { + "lock": false, + "sort-packages": true, + "platform-check": true + } +} diff --git a/vendor/chillerlan/php-settings-container/rules-magic-access.neon b/vendor/chillerlan/php-settings-container/rules-magic-access.neon new file mode 100644 index 0000000..5a98d3a --- /dev/null +++ b/vendor/chillerlan/php-settings-container/rules-magic-access.neon @@ -0,0 +1,4 @@ +parameters: + ignoreErrors: + # yes, these are magic + - message: "#^Access to an undefined property chillerlan\\\\Settings\\\\SettingsContainerInterface\\:\\:\\$[\\w]+\\.$#" diff --git a/vendor/chillerlan/php-settings-container/src/SettingsContainerAbstract.php b/vendor/chillerlan/php-settings-container/src/SettingsContainerAbstract.php new file mode 100644 index 0000000..826faaf --- /dev/null +++ b/vendor/chillerlan/php-settings-container/src/SettingsContainerAbstract.php @@ -0,0 +1,252 @@ + + * @copyright 2018 Smiley + * @license MIT + */ +declare(strict_types=1); + +namespace chillerlan\Settings; + +use InvalidArgumentException, JsonException, ReflectionClass, ReflectionProperty; +use function array_keys, get_object_vars, is_object, json_decode, json_encode, + json_last_error_msg, method_exists, property_exists, serialize, unserialize; +use const JSON_THROW_ON_ERROR; + +abstract class SettingsContainerAbstract implements SettingsContainerInterface{ + + /** + * SettingsContainerAbstract constructor. + * + * @phpstan-param array $properties + */ + 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{ + $json = json_encode($this, ($jsonOptions ?? 0)); + + if($json === false){ + throw new JsonException(json_last_error_msg()); + } + + return $json; + } + + /** + * @inheritdoc + */ + public function fromJSON(string $json):static{ + /** @phpstan-var array $data */ + $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR); + + return $this->fromIterable($data); + } + + /** + * @inheritdoc + * @return array + */ + 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() + * + * @param array $data + */ + public function __unserialize(array $data):void{ + + foreach($data as $key => $value){ + $this->{$key} = $value; + } + + } + +} diff --git a/vendor/chillerlan/php-settings-container/src/SettingsContainerInterface.php b/vendor/chillerlan/php-settings-container/src/SettingsContainerInterface.php new file mode 100644 index 0000000..6f3cc1a --- /dev/null +++ b/vendor/chillerlan/php-settings-container/src/SettingsContainerInterface.php @@ -0,0 +1,86 @@ + + * @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. + * + * @return array + */ + 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. + * + * @phpstan-param array $properties + */ + public function fromIterable(iterable $properties):static; + + /** + * Returns a JSON representation of the settings object + * + * @see \json_encode() + * @see \chillerlan\Settings\SettingsContainerInterface::toArray() + * + * @throws \JsonException + */ + public function toJSON(int|null $jsonOptions = null):string; + + /** + * Sets properties from a given JSON string + * + * @see \chillerlan\Settings\SettingsContainerInterface::fromIterable() + * + * @throws \Exception + * @throws \JsonException + */ + public function fromJSON(string $json):static; + +} diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php new file mode 100644 index 0000000..7824d8f --- /dev/null +++ b/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/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..51e734a --- /dev/null +++ b/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/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +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: + +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/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..0fb0a2c --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,10 @@ + $vendorDir . '/composer/InstalledVersions.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..15a2ff3 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/chillerlan/php-settings-container/src'), + 'chillerlan\\QRCode\\' => array($vendorDir . '/chillerlan/php-qrcode/src'), + 'Norgul\\Xmpp\\' => array($vendorDir . '/norgul/xmpp-php/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..182b624 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,38 @@ +register(true); + + return $loader; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..7291ec3 --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,49 @@ + + array ( + 'chillerlan\\Settings\\' => 20, + 'chillerlan\\QRCode\\' => 18, + ), + 'N' => + array ( + 'Norgul\\Xmpp\\' => 12, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'chillerlan\\Settings\\' => + array ( + 0 => __DIR__ . '/..' . '/chillerlan/php-settings-container/src', + ), + 'chillerlan\\QRCode\\' => + array ( + 0 => __DIR__ . '/..' . '/chillerlan/php-qrcode/src', + ), + 'Norgul\\Xmpp\\' => + array ( + 0 => __DIR__ . '/..' . '/norgul/xmpp-php/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 = ComposerStaticInit039ef714d06adc1d2f797455add3d2e5::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit039ef714d06adc1d2f797455add3d2e5::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit039ef714d06adc1d2f797455add3d2e5::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..9a464a1 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,272 @@ +{ + "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.1", + "version_normalized": "3.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/chillerlan/php-settings-container.git", + "reference": "95ed3e9676a1d47cab2e3174d19b43f5dbf52681" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/95ed3e9676a1d47cab2e3174d19b43f5dbf52681", + "reference": "95ed3e9676a1d47cab2e3174d19b43f5dbf52681", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^8.1" + }, + "require-dev": { + "phpmd/phpmd": "^2.15", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-deprecation-rules": "^1.2", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^3.10" + }, + "time": "2024-07-16T11:13:48+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": "monero-integrations/monerophp", + "version": "dev-Dev", + "version_normalized": "dev-Dev", + "source": { + "type": "git", + "url": "https://github.com/monero-integrations/monerophp.git", + "reference": "c45af18802312f6ea251c9adff642baa7a6ce965" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/monero-integrations/monerophp/zipball/c45af18802312f6ea251c9adff642baa7a6ce965", + "reference": "c45af18802312f6ea251c9adff642baa7a6ce965", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "time": "2018-02-14T02:49:13+00:00", + "type": "library", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "SerHack", + "email": "support@monerointegrations.com" + } + ], + "description": "A PHP library for the Monero Wallet RPC JSON-RPC interface.", + "homepage": "https://github.com/monero-integrations/monerophp", + "keywords": [ + "Monero", + "XMR", + "cryptocurrency", + "json-rpc" + ], + "support": { + "issues": "https://github.com/monero-integrations/monerophp/issues", + "source": "https://github.com/monero-integrations/monerophp/tree/Dev" + }, + "install-path": "../monero-integrations/monerophp" + }, + { + "name": "norgul/xmpp-php", + "version": "v2.2.3", + "version_normalized": "2.2.3.0", + "source": { + "type": "git", + "url": "https://github.com/Norgul/xmpp-php.git", + "reference": "f9d24ca167599e9040f5cc3f500f5ec90d28faf3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Norgul/xmpp-php/zipball/f9d24ca167599e9040f5cc3f500f5ec90d28faf3", + "reference": "f9d24ca167599e9040f5cc3f500f5ec90d28faf3", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.0.x-dev", + "phpmd/phpmd": "2.6.0", + "phpunit/phpunit": "6.*", + "squizlabs/php_codesniffer": "3.4.2" + }, + "time": "2019-10-14T18:36:17+00:00", + "type": "project", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Norgul\\Xmpp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marko Dupor", + "email": "marko.dupor@gmail.com" + } + ], + "description": "PHP library for XMPP.", + "keywords": [ + "jabber", + "library", + "php", + "xmpp" + ], + "support": { + "issues": "https://github.com/Norgul/xmpp-php/issues", + "source": "https://github.com/Norgul/xmpp-php/tree/v2.2.3" + }, + "install-path": "../norgul/xmpp-php" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php new file mode 100644 index 0000000..d7d058e --- /dev/null +++ b/vendor/composer/installed.php @@ -0,0 +1,59 @@ + array( + 'name' => '__root__', + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => null, + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => null, + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + '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.1', + 'version' => '3.2.1.0', + 'reference' => '95ed3e9676a1d47cab2e3174d19b43f5dbf52681', + 'type' => 'library', + 'install_path' => __DIR__ . '/../chillerlan/php-settings-container', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'monero-integrations/monerophp' => array( + 'pretty_version' => 'dev-Dev', + 'version' => 'dev-Dev', + 'reference' => 'c45af18802312f6ea251c9adff642baa7a6ce965', + 'type' => 'library', + 'install_path' => __DIR__ . '/../monero-integrations/monerophp', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'norgul/xmpp-php' => array( + 'pretty_version' => 'v2.2.3', + 'version' => '2.2.3.0', + 'reference' => 'f9d24ca167599e9040f5cc3f500f5ec90d28faf3', + 'type' => 'project', + 'install_path' => __DIR__ . '/../norgul/xmpp-php', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php new file mode 100644 index 0000000..4c3a5d6 --- /dev/null +++ b/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/vendor/monero-integrations/monerophp/LICENSE b/vendor/monero-integrations/monerophp/LICENSE new file mode 100644 index 0000000..73894ea --- /dev/null +++ b/vendor/monero-integrations/monerophp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 SerHack and 2018 Monero Integrations team + +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/vendor/monero-integrations/monerophp/README.md b/vendor/monero-integrations/monerophp/README.md new file mode 100644 index 0000000..5762cb3 --- /dev/null +++ b/vendor/monero-integrations/monerophp/README.md @@ -0,0 +1,45 @@ +[![PHPCS PSR-12](https://img.shields.io/badge/PHPCS-PSR–12-226146.svg)](https://www.php-fig.org/psr/psr-12/) [![PHPStan ](.github/phpstan.svg)](https://phpstan.org/) + +# Monero Library +A Monero library written in PHP by the [Monero Integrations](https://monerointegrations.com) [team](https://github.com/monero-integrations/monerophp/graphs/contributors). + +## How It Works +This library has 3 main parts: + +1. A Monero daemon JSON RPC API wrapper, `daemonRPC.php` +2. A Monero wallet (`monero-wallet-rpc`) JSON RPC API wrapper, `walletRPC.php` +3. A Monero/Cryptonote toolbox, `cryptonote.php`, with both lower level functions used in Monero related cryptography and higher level methods for things like generating Monero private/public keys. + +In addition to these features, there are other lower-level libraries included for portability, *eg.* an ed25519 library, a SHA3 library, *etc.* + +## Preview +![Preview](https://user-images.githubusercontent.com/4107993/38056594-b6cd6e14-3291-11e8-96e2-a771b0e9cee3.png) + +## Documentation + +Documentation can be found in the [`/docs`](https://github.com/sneurlax/monerophp/tree/master/docs) folder. + +## Configuration +### Requirements + - Monero daemon (`monerod`) + - Webserver with PHP, for example XMPP, Apache, or NGINX + - cURL PHP extension for JSON RPC API(s) + - GMP PHP extension for about 100x faster calculations (as opposed to BCMath) + +Debian (or Ubuntu) are recommended. + +### Getting Started + +1. Start the Monero daemon (`monerod`) on testnet. +```bash +monerod --testnet --detach +``` + +2. Start the Monero wallet RPC interface (`monero-wallet-rpc`) on testnet. +```bash +monero-wallet-rpc --testnet --rpc-bind-port 28083 --disable-rpc-login --wallet-dir /path/to/wallet/directory +``` + +3. Edit `example.php` with your the IP address of `monerod` and `monero-wallet-rpc` (use `127.0.0.1:28081` and `127.0.0.1:28083`, respectively, for testnet.) + +4. Serve `example.php` with your webserver (*eg.* XMPP, Apache/Apache2, NGINX, *etc.*) and navigate to it. If everything has been set up correctly, information from your Monero daemon and wallet will be displayed. diff --git a/vendor/monero-integrations/monerophp/composer.json b/vendor/monero-integrations/monerophp/composer.json new file mode 100644 index 0000000..4110a7f --- /dev/null +++ b/vendor/monero-integrations/monerophp/composer.json @@ -0,0 +1,66 @@ +{ + "name": "monero-integrations/monerophp", + "description": "A Monero library written in PHP by the Monero-Integrations team.", + "keywords": ["Monero", "XMR", "monerod", "monero-wallet-rpc", "cryptonote", "JSONRPC", "JSON-RPC", "cryptocurrency"], + "homepage": "https://github.com/monero-integrations/monerophp", + "type": "library", + "version" : "1.0.1", + "license": "MIT", + "authors": [ + { + "name": "SerHack", + "email": "support@monerointegrations.com" + }, + { + "name": "cryptochangements34", + "email": "bW9uZXJv@gmail.com" + }, + { + "name": "sneurlax", + "email": "sneurlax@gmail.com" + } + ], + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "require": { + "php": ">=7.3", + "ext-bcmath": "*", + "ext-curl": "*", + "ext-json": "*", + "kornrunner/keccak": "^1.1" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "*", + "phpstan/extension-installer": "*", + "brainmaestro/composer-git-hooks": "^2.8", + "squizlabs/php_codesniffer": "*" + }, + "suggest": { + "ext-gmp": "Used to have a multiple math precision for generating address" + }, + "autoload": { + "psr-4": { + "MoneroIntegrations\\MoneroPhp\\": "src/" + } + }, + "extra": { + "hooks": { + "pre-commit": [ + "vendor/bin/phpcbf" + ] + } + }, + "scripts": { + "post-install-cmd": "cghooks add --ignore-lock", + "post-update-cmd": "cghooks update", + "lint": [ + "phpcbf || true", + "phpcs || true", + "phpstan analyse --memory-limit 1G" + ] + } +} diff --git a/vendor/monero-integrations/monerophp/composer.lock b/vendor/monero-integrations/monerophp/composer.lock new file mode 100644 index 0000000..379a184 --- /dev/null +++ b/vendor/monero-integrations/monerophp/composer.lock @@ -0,0 +1,1286 @@ +{ + "_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": "eaabe8b13af6e0555ab3b8be36c45778", + "packages": [ + { + "name": "kornrunner/keccak", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/kornrunner/php-keccak.git", + "reference": "433749d28e117fb97baf9f2631b92b5d9ab3c890" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kornrunner/php-keccak/zipball/433749d28e117fb97baf9f2631b92b5d9ab3c890", + "reference": "433749d28e117fb97baf9f2631b92b5d9ab3c890", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "symfony/polyfill-mbstring": "^1.8" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "kornrunner\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Boris Momcilovic", + "homepage": "https://github.com/kornrunner/php-keccak" + } + ], + "description": "Pure PHP implementation of Keccak", + "keywords": [ + "keccak", + "sha-3", + "sha3-256" + ], + "support": { + "issues": "https://github.com/kornrunner/php-keccak/issues", + "source": "https://github.com/kornrunner/php-keccak/tree/1.1.0" + }, + "time": "2020-12-07T15:40:44+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "42292d99c55abe617799667f454222c54c60e229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-28T09:04:16+00:00" + } + ], + "packages-dev": [ + { + "name": "brainmaestro/composer-git-hooks", + "version": "v2.8.5", + "source": { + "type": "git", + "url": "https://github.com/BrainMaestro/composer-git-hooks.git", + "reference": "ffed8803690ac12214082120eee3441b00aa390e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/BrainMaestro/composer-git-hooks/zipball/ffed8803690ac12214082120eee3441b00aa390e", + "reference": "ffed8803690ac12214082120eee3441b00aa390e", + "shasum": "" + }, + "require": { + "php": "^5.6 || >=7.0", + "symfony/console": "^3.2 || ^4.0 || ^5.0" + }, + "require-dev": { + "ext-json": "*", + "friendsofphp/php-cs-fixer": "^2.9", + "phpunit/phpunit": "^5.7 || ^7.0" + }, + "bin": [ + "cghooks" + ], + "type": "library", + "extra": { + "hooks": { + "pre-commit": "composer check-style", + "pre-push": [ + "composer test", + "appver=$(grep -o -E '\\d.\\d.\\d' cghooks)", + "tag=$(git describe --tags --abbrev=0)", + "if [ \"$tag\" != \"v$appver\" ]; then", + "echo \"The most recent tag $tag does not match the application version $appver\\n\"", + "tag=${tag#v}", + "sed -i -E \"s/$appver/$tag/\" cghooks", + "exit 1", + "fi" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "BrainMaestro\\GitHooks\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ezinwa Okpoechi", + "email": "brainmaestro@outlook.com" + } + ], + "description": "Easily manage git hooks in your composer config", + "keywords": [ + "HOOK", + "composer", + "git" + ], + "support": { + "issues": "https://github.com/BrainMaestro/composer-git-hooks/issues", + "source": "https://github.com/BrainMaestro/composer-git-hooks/tree/v2.8.5" + }, + "time": "2021-02-08T15:59:11+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "4be43904336affa5c2f70744a348312336afd0da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", + "reference": "4be43904336affa5c2f70744a348312336afd0da", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "time": "2023-01-05T11:28:13+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "f45734bfb9984c6c56c4486b71230355f066a58a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/f45734bfb9984c6c56c4486b71230355f066a58a", + "reference": "f45734bfb9984c6c56c4486b71230355f066a58a", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.3.1" + }, + "time": "2023-05-24T08:59:17+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.10.53", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "0c48595cce15f67d6a7faf30e9d13c775e6e9875" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0c48595cce15f67d6a7faf30e9d13c775e6e9875", + "reference": "0c48595cce15f67d6a7faf30e9d13c775e6e9875", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2024-01-05T13:55:38+00:00" + }, + { + "name": "psr/container", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.1" + }, + "time": "2021-03-05T17:36:06+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "5805f7a4e4958dbb5e944ef1e6edae0a303765e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5805f7a4e4958dbb5e944ef1e6edae0a303765e7", + "reference": "5805f7a4e4958dbb5e944ef1e6edae0a303765e7", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2023-12-08T12:32:31+00:00" + }, + { + "name": "symfony/console", + "version": "v5.4.34", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "4b4d8cd118484aa604ec519062113dd87abde18c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/4b4d8cd118484aa604ec519062113dd87abde18c", + "reference": "4b4d8cd118484aa604ec519062113dd87abde18c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.4.34" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-12-08T13:33:03+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "875e90aeea2777b6f135677f618529449334a612" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", + "reference": "875e90aeea2777b6f135677f618529449334a612", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fe2f306d1d9d346a7fee353d0d5012e401e984b5", + "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-30T19:17:29+00:00" + }, + { + "name": "symfony/string", + "version": "v5.4.34", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "e3f98bfc7885c957488f443df82d97814a3ce061" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/e3f98bfc7885c957488f443df82d97814a3ce061", + "reference": "e3f98bfc7885c957488f443df82d97814a3ce061", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.34" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-12-09T13:20:28+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.3", + "ext-bcmath": "*", + "ext-curl": "*", + "ext-json": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/vendor/monero-integrations/monerophp/docs/README.md b/vendor/monero-integrations/monerophp/docs/README.md new file mode 100644 index 0000000..a5558d3 --- /dev/null +++ b/vendor/monero-integrations/monerophp/docs/README.md @@ -0,0 +1,166 @@ +# MoneroPHP documentation + +This document lists the MoneroPHP classes and their methods. More detailed documentation (including parameters and their types and return formats and examples) can be found on each class' page. + +### Classes + + - [`cryptonote`](#cryptonote-class) ([`src/cryptonote.php`](https://github.com/monero-integrations/monerophp/tree/master/src/cryptonote.php)) + - [`ed25519`](#ed25519-class) ([`src/ed25519.php`](https://github.com/monero-integrations/monerophp/tree/master/src/ed25519.php)) + - [`SHA3`](#SHA3-class) ([`src/SHA3.php`](https://github.com/monero-integrations/monerophp/tree/master/src/SHA3.php)) + - [`base58`](#base58-class) ([`src/base58.php`](https://github.com/monero-integrations/monerophp/tree/master/src/base58.php)) + +JSON RPC wrappers: + + - [`daemonRPC`](#daemonRPC-class) ([`src/daemonRPC.php`](https://github.com/monero-integrations/monerophp/tree/master/src/daemonRPC.php)) + - [`walletRPC`](#walletRPC-class) ([`src/walletRPC.php`](https://github.com/monero-integrations/monerophp/tree/master/src/walletRPC.php)) + +## [`cryptonote` class](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md) + +*Documentation under development* + + - [`keccak_256`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#keccak_256) + - [`gen_new_hex_seed`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#gen_new_hex_seed) + - [`sc_reduce`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#sc_reduce) + - [`hash_to_scalar`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#hash_to_scalar) + - [`derive_viewKey`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#derive_viewKey) + - [`gen_private_keys`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#gen_private_keys) + - [`pk_from_sk`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#pk_from_sk) + - [`gen_key_derivation`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#gen_key_derivation) + - [`encode_varint`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#encode_varint) + - [`derivation_to_scalar`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#derivation_to_scalar) + - [`stealth_payment_id`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#stealth_payment_id) + - [`txpub_from_extra`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#txpub_from_extra) + - [`derive_public_key`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#derive_public_key) + - [`is_output_mine`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#is_output_mine) + - [`encode_address`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#encode_address) + - [`verify_checksum`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#verify_checksum) + - [`decode_address`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#decode_address) + - [`integrated_addr_from_keys`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#integrated_addr_from_keys) + - [`address_from_seed`](https://github.com/monero-integrations/monerophp/tree/master/docs/cryptonote.md#address_from_seed) + +## [`ed25519` class](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md) + +*Documentation under development* + + - [`H`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#H) + - [`pymod`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#pymod) + - [`expmod`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#expmod) + - [`inv`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#inv) + - [`xrecover`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#xrecover) + - [`edwards`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#edwards) + - [`scalarmult`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#scalarmult) + - [`scalarloop`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#scalarloop) + - [`bitsToString`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#bitsToString) + - [`dec2bin_i`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#dec2bin_i) + - [`encodeint`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#encodeint) + - [`encodepoint`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#encodepoint) + - [`bit`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#bit) + - [`publickey`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#publickey) + - [`Hint`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#Hint) + - [`signature`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#signature) + - [`isoncurve`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#isoncurve) + - [`decodeint`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#decodeint) + - [`decodepoint`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#decodepoint) + - [`checkvalid`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#checkvalid) + - [`scalarmult_base`](https://github.com/monero-integrations/monerophp/tree/master/docs/ed25519.md#scalarmult_base) + +## [`SHA3` class](https://github.com/monero-integrations/monerophp/tree/master/docs/SHA3.md) + + - [`init`](https://github.com/monero-integrations/monerophp/tree/master/docs/SHA3.md#init) + - [`absorb`](https://github.com/monero-integrations/monerophp/tree/master/docs/SHA3.md#absorb) + - [`absorb`](https://github.com/monero-integrations/monerophp/tree/master/docs/SHA3.md#absorb) + - [`squeeze`](https://github.com/monero-integrations/monerophp/tree/master/docs/SHA3.md#squeeze) + +## [`base58` class](https://github.com/monero-integrations/monerophp/tree/master/docs/base58.md) + + - [`encode`](https://github.com/monero-integrations/monerophp/tree/master/docs/base58.md##encode) + - [`decode`](https://github.com/monero-integrations/monerophp/tree/master/docs/base58.md##decode) + +## [`daemonRPC` class](https://github.com/monero-integrations/monerophp/tree/master/docs/daemonRPC.md) + + - [`getblockcount`](https://github.com/monero-integrations/monerophp/tree/master/docs/daemonRPC.md#getblockcount) + - [`on_getblockhash`](https://github.com/monero-integrations/monerophp/tree/master/docs/daemonRPC.md#on_getblockhash) + - [`getblocktemplate`](https://github.com/monero-integrations/monerophp/tree/master/docs/daemonRPC.md#getblocktemplate) + - [`submitblock`](https://github.com/monero-integrations/monerophp/tree/master/docs/daemonRPC.md#submitblock) + - [`getlastblockheader`](https://github.com/monero-integrations/monerophp/tree/master/docs/daemonRPC.md#getlastblockheader) + - [`getblockheaderbyhash`](https://github.com/monero-integrations/monerophp/tree/master/docs/daemonRPC.md#getblockheaderbyhash) + - [`getblockheaderbyheight`](https://github.com/monero-integrations/monerophp/tree/master/docs/daemonRPC.md#getblockheaderbyheight) + - [`getblock_by_hash`](https://github.com/monero-integrations/monerophp/tree/master/docs/daemonRPC.md#getblock_by_hash) + - [`getblock_by_height`](https://github.com/monero-integrations/monerophp/tree/master/docs/daemonRPC.md#getblock_by_height) + - [`get_connections`](https://github.com/monero-integrations/monerophp/tree/master/docs/daemonRPC.md#get_connections) + - [`get_info`](https://github.com/monero-integrations/monerophp/tree/master/docs/daemonRPC.md#get_info) + - [`hardfork_info`](https://github.com/monero-integrations/monerophp/tree/master/docs/daemonRPC.md#hardfork_info) + - [`setbans`](https://github.com/monero-integrations/monerophp/tree/master/docs/daemonRPC.md#setbans) + - [`getbans`](https://github.com/monero-integrations/monerophp/tree/master/docs/daemonRPC.md#getbans) + +## [`walletRPC` class](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md) + + - [`_transform`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#_transform) + - [`get_balance`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#get_balance) + - [`get_address`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#get_address) + - [`create_address`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#create_address) + - [`label_address`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#label_address) + - [`get_accounts`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#get_accounts) + - [`create_account`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#create_account) + - [`label_account`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#label_account) + - [`get_account_tags`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#get_account_tags) + - [`tag_accounts`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#tag_accounts) + - [`untag_accounts`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#untag_accounts) + - [`set_account_tag_description`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#set_account_tag_description) + - [`get_height`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#get_height) + - [`transfer`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#transfer) + - [`transfer_split`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#transfer_split) + - [`sweep_dust`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#sweep_dust) + - [`sweep_unmixable`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#sweep_unmixable) + - [`sweep_all`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#sweep_all) + - [`sweep_single`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#sweep_single) + - [`relay_tx`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#relay_tx) + - [`store`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#store) + - [`get_payments`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#get_payments) + - [`get_bulk_payments`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#get_bulk_payments) + - [`incoming_transfers`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#incoming_transfers) + - [`query_key`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#query_key) + - [`view_key`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#view_key) + - [`spend_key`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#spend_key) + - [`mnemonic`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#mnemonic) + - [`make_integrated_address`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#make_integrated_address) + - [`split_integrated_address`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#split_integrated_address) + - [`stop_wallet`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#stop_wallet) + - [`rescan_blockchain`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#rescan_blockchain) + - [`set_tx_notes`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#set_tx_notes) + - [`get_tx_notes`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#get_tx_notes) + - [`set_attribute`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#set_attribute) + - [`get_attribute`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#get_attribute) + - [`get_tx_key`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#get_tx_key) + - [`check_tx_key`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#check_tx_key) + - [`get_tx_proof`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#get_tx_proof) + - [`check_tx_proof`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#check_tx_proof) + - [`get_spend_proof`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#get_spend_proof) + - [`check_spend_proof`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#check_spend_proof) + - [`get_reserve_proof`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#get_reserve_proof) + - [`check_reserve_proof`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#check_reserve_proof) + - [`get_transfers`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#get_transfers) + - [`get_transfer_by_txid`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#get_transfer_by_txid) + - [`sign`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#sign) + - [`verify`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#verify) + - [`export_key_images`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#export_key_images) + - [`import_key_images`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#import_key_images) + - [`make_uri`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#make_uri) + - [`parse_uri`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#parse_uri) + - [`get_address_book`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#get_address_book) + - [`add_address_book`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#add_address_book) + - [`delete_address_book`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#delete_address_book) + - [`rescan_spent`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#rescan_spent) + - [`start_mining`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#start_mining) + - [`stop_mining`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#stop_mining) + - [`get_languages`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#get_languages) + - [`create_wallet`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#create_wallet) + - [`open_wallet`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#open_wallet) + - [`is_multisig`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#is_multisig) + - [`prepare_multisig`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#prepare_multisig) + - [`make_multisig`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#make_multisig) + - [`export_multisig_info`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#export_multisig_info) + - [`import_multisig_info`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#import_multisig_info) + - [`finalize_multisig`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#finalize_multisig) + - [`sign_multisig`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#sign_multisig) + - [`submit_multisig`](https://github.com/monero-integrations/monerophp/tree/master/docs/walletRPC.md#submit_multisig) diff --git a/vendor/monero-integrations/monerophp/docs/base58.md b/vendor/monero-integrations/monerophp/docs/base58.md new file mode 100644 index 0000000..a462f44 --- /dev/null +++ b/vendor/monero-integrations/monerophp/docs/base58.md @@ -0,0 +1,42 @@ +# `base58` class + +[`src/base58.php`](https://github.com/monero-integrations/monerophp/tree/master/src/base58.php) + +A PHP Base58 codec + +### Methods + + - [`encode`](#encode) + - [`decode`](#decode) + +#### `encode` + +Encode a hexadecimal (Base16) string to Base58 + +Parameters: + + - `$hex ` A hexadecimal (Base16) string to convert to Base58 + +Return: `` + +`"479cG5opa54beQWSyqNoWw5tna9sHUNmMTtiFqLPaUhDevpJ2YLwXAggSx5ePdeFrYF8cdbmVRSmp1Kn3t4Y9kFu7rZ7pFw"` + +#### `decode` + +Decode a Base58 string to hexadecimal (Base16) + +Parameters: + + - `$hex ` A Base58 string to convert to hexadecimal (Base16) + +Return: `` + +`"0137F8F06C971B168745F562AA107B4D172F336271BC0F9D3B510C14D3460DFB27D8CEBE561E73AC1E11833D5EA40200EB3C82E9C66ACAF1AB1A6BB53C40537C0B7A22160B0E"` + +### Credits + +Written by the [Monero Integrations team](https://github.com/monero-integrations/monerophp/graphs/contributors) () + +Using work from: + - bigreddmachine [MoneroPy] (https://github.com/bigreddmachine) + - Paul Shapiro [mymonero-core-js] (https://github.com/paulshapiro) diff --git a/vendor/monero-integrations/monerophp/docs/cryptonote.md b/vendor/monero-integrations/monerophp/docs/cryptonote.md new file mode 100644 index 0000000..59f0b6d --- /dev/null +++ b/vendor/monero-integrations/monerophp/docs/cryptonote.md @@ -0,0 +1,246 @@ +# `cryptonote` class + +[`src/cryptonote.php`](https://github.com/monero-integrations/monerophp/tree/master/src/cryptonote.php) + +### Methods + + - [`keccak_256`](#keccak_256) + - [`gen_new_hex_seed`](#gen_new_hex_seed) + - [`sc_reduce`](#sc_reduce) + - [`hash_to_scalar`](#hash_to_scalar) + - [`derive_viewKey`](#derive_viewKey) + - [`gen_private_keys`](#gen_private_keys) + - [`pk_from_sk`](#pk_from_sk) + - [`gen_key_derivation`](#gen_key_derivation) + - [`encode_varint`](#encode_varint) + - [`derivation_to_scalar`](#derivation_to_scalar) + - [`stealth_payment_id`](#stealth_payment_id) + - [`txpub_from_extra`](#txpub_from_extra) + - [`derive_public_key`](#derive_public_key) + - [`is_output_mine`](#is_output_mine) + - [`encode_address`](#encode_address) + - [`verify_checksum`](#verify_checksum) + - [`decode_address`](#decode_address) + - [`integrated_addr_from_keys`](#integrated_addr_from_keys) + - [`address_from_seed`](#address_from_seed) + +#### `keccak_256` + +Derive a Keccak256 hash from a string + +Parameters: + + - `$message ` Hex encoded string of the data to hash + +Return: `` Hex encoded string of the hashed data + +[//]: # (TODO example) + +#### `gen_new_hex_seed` + +Generate a hexadecimal seed + +Return: `` A hex encoded string of 32 random bytes + +[//]: # (TODO example) + +#### `sc_reduce` + +Parameters: + + - `$input ` + +[//]: # (TODO return type and example) + +#### `hash_to_scalar` + +`Hs` in the cryptonote white paper + +Parameters: + + - `$data ` Hex encoded data to hash + +Return: `` A 32 byte encoded integer + +[//]: # (TODO example) + +#### `derive_viewKey` + +Derive a deterministic private view key from a private spend key + +Parameters: + + - `$spendKey ` A deterministic private view key represented as a 32 byte hex string + +Return: `` + +[//]: # (TODO example) + +#### `gen_private_keys` + +Generate a pair of random private keys + +Parameters: + + - `$seed ` A hex string to be used as a seed (this should be random) + +Return: `` An array containing a private spend key and a deterministic view key + +[//]: # (TODO example) + +#### `pk_from_sk` + +Get a public key from a private key on the ed25519 curve + +Parameters: + + - `$privKey ` A 32 byte hex encoded private key + +Return: `` + +[//]: # (TODO example) + +#### `gen_key_derivation` + +Generate key derivation + +Parameters: + + - `$public ` a 32 byte hex encoding of a point on the ed25519 curve used as a public key + - `$private ` a 32 byte hex encoded private key + +Return: `` The hex encoded key derivation + +[//]: # (TODO example) + +#### `encode_varint` + +Parameters: + + - `$der <>` + - `$index <>` + +[//]: # (TODO return type and example) + +#### `derivation_to_scalar` + +Parameters: + + - `$ <>` + +[//]: # (TODO return type and example) + +#### `stealth_payment_id` + +A one way function used for both encrypting and decrypting 8 byte payment IDs + +Parameters: + + - `$payment_id ` + - `$tx_pub_key ` + - `$viewkey ` + +Return: `` + +[//]: # (TODO example) + +#### `txpub_from_extra` + +Takes transaction extra field as hex string and returns transaction public key 'R' as hex string + +Parameters: + + - `$extra ` + +Return: `` + +[//]: # (TODO example) + +#### `derive_public_key` + +Parameters: + + - `$der <>` + - `$index <>` + - `$pub <>` + +Return: `` + +[//]: # (TODO example) + +#### `is_output_mine` + +Perform the calculation P = P' as described in the cryptonote whitepaper + +Parameters: + + - `$txPublic ` 32 byte transaction public key R + - `$privViewkey ` 32 byte receiver private view key a + - `$publicSpendkey ` 32 byte receiver public spend key B + - `$index ` Otput index + - `$P ` Output you want to check against P + +Return: `` + +[//]: # (TODO example) + +#### `encode_address` + +Create a valid base58 encoded Monero address from public keys + +Parameters: + + - `$pSpendKey ` Public spend key + - `$pViewKey ` Public view key + +Return: `` Base58 encoded Monero address + +[//]: # (TODO example) + +#### `verify_checksum` + +Parameters: + + - `$address <>` + +Return: `` + +[//]: # (TODO example) + +#### `decode_address` + +Decode a base58 encoded Monero address + +Parameters: + + - `$address ` A base58 encoded Monero address + +Return: `` An array containing the Address network byte, public spend key, and public view key +[//]: # (TODO example) + + +#### `integrated_addr_from_keys` + +Create an integrated address from public keys and a payment ID + +Parameters: + + - `$public_spendkey ` A 32 byte hex encoded public spend key + - `$public_viewkey ` A 32 byte hex encoded public view key + - `$payment_id ` An 8 byte hex string to use as a payment id + +Return: `` Integrated address + +[//]: # (TODO example) + +#### `address_from_seed` + +Derive Monero address from seed + +Parameters: + + - `$hex_seed ` Hex string to use as seed + +Return: `` A base58 encoded Monero address + +[//]: # (TODO example) diff --git a/vendor/monero-integrations/monerophp/docs/daemonRPC.md b/vendor/monero-integrations/monerophp/docs/daemonRPC.md new file mode 100644 index 0000000..789bc2c --- /dev/null +++ b/vendor/monero-integrations/monerophp/docs/daemonRPC.md @@ -0,0 +1,392 @@ +# `daemonRPC` class + +[`src/daemonRPC.php`](https://github.com/monero-integrations/monerophp/tree/master/src/daemonRPC.php) + +A class for making calls to a Monero daemon's RPC API using PHP + +Parameters: + + - `$host ` Monero daemon port *(optional)* + - `$port ` Monero daemon protocol (*eg.* 'http') *(optional)* + - `$protocol ` Monero daemon IP hostname *(optional)* + - `$user ` Monero daemon RPC username *(optional)* + - `$password ` Monero daemon RPC passphrase *(optional)* + +Parameters can also be passed in as an associative array (object/dictionary,) as in: + +```php +$daemonRPC = new daemonRPC(['host' => '127.0.0.1', 'port' => 28081]) +``` + +If an object is used to provide parameters (as above,) parameters can be declared in any order. + +### Methods + + - [`getblockcount`](#getblockcount) + - [`on_getblockhash`](#on_getblockhash) + - [`getblocktemplate`](#getblocktemplate) + - [`submitblock`](#submitblock) + - [`getlastblockheader`](#getlastblockheader) + - [`getblockheaderbyhash`](#getblockheaderbyhash) + - [`getblockheaderbyheight`](#getblockheaderbyheight) + - [`getblock_by_hash`](#getblock_by_hash) + - [`getblock_by_height`](#getblock_by_height) + - [`get_connections`](#get_connections) + - [`get_info`](#get_info) + - [`hardfork_info`](#hardfork_info) + - [`setbans`](#setbans) + - [`getbans`](#getbans) + +#### `getblockcount` + +Look up how many blocks are in the longest chain known to the node + +*No parameters* + +Return: `` + +```json +{ + "count": 993163, + "status": "OK" +} +``` + +#### `on_getblockhash` + +Look up a block's hash by its height + +Parameters: + + - `$height ` Height of block to look up + +Return: `` + +```json +"e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6" +``` + +#### `getblocktemplate` + +Construct a block template that can be mined upon + +Parameters: + + - `$wallet_address ` Address of wallet to receive coinbase transactions if block is successfully mined + - `$reserve_size ` Reserve size + +Return: `` + +```json +{ + "blocktemplate_blob": "01029af88cb70568b84a11dc9406ace9e635918ca03b008f7728b9726b327c1b482a98d81ed83000000000018bd03c01ffcfcf3c0493d7cec7020278dfc296544f139394e5e045fcda1ba2cca5b69b39c9ddc90b7e0de859fdebdc80e8eda1ba01029c5d518ce3cc4de26364059eadc8220a3f52edabdaf025a9bff4eec8b6b50e3d8080dd9da417021e642d07a8c33fbe497054cfea9c760ab4068d31532ff0fbb543a7856a9b78ee80c0f9decfae01023ef3a7182cb0c260732e7828606052a0645d3686d7a03ce3da091dbb2b75e5955f01ad2af83bce0d823bf3dbbed01ab219250eb36098c62cbb6aa2976936848bae53023c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f12d7c87346d6b84e17680082d9b4a1d84e36dd01bd2c7f3b3893478a8d88fb3", + "difficulty": 982540729, + "height": 993231, + "prev_hash": "68b84a11dc9406ace9e635918ca03b008f7728b9726b327c1b482a98d81ed830", + "reserved_offset": 246, + "status": "OK" +} +``` + +#### `submitblock` + +Submit a mined block to the network + +Parameters: + + - `$block ` Block blob + +Return: `` + +[//]: # (TODO example) + +#### `getlastblockheader` + +Look up the block header of the latest block in the longest chain known to the node + +*No parameters* + +Return: `` + +```json +{ + "block_header": { + "depth": 0, + "difficulty": 746963928, + "hash": "ac0f1e226268d45c99a16202fdcb730d8f7b36ea5e5b4a565b1ba1a8fc252eb0", + "height": 990793, + "major_version": 1, + "minor_version": 1, + "nonce": 1550, + "orphan_status": false, + "prev_hash": "386575e3b0b004ed8d458dbd31bff0fe37b280339937f971e06df33f8589b75c", + "reward": 6856609225169, + "timestamp": 1457589942 + }, + "status": "OK" +} +``` + +#### `getblockheaderbyhash` + +Look up a block header by height + +Parameters: + + - `$height ` Height of block + +Return: `` + +```json +{ + "block_header": { + "depth": 78376, + "difficulty": 815625611, + "hash": "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6", + "height": 912345, + "major_version": 1, + "minor_version": 2, + "nonce": 1646, + "orphan_status": false, + "prev_hash": "b61c58b2e0be53fad5ef9d9731a55e8a81d972b8d90ed07c04fd37ca6403ff78", + "reward": 7388968946286, + "timestamp": 1452793716 + }, + "status": "OK" +} +``` + +#### `getblockheaderbyheight` + +Look up block information by height + +Parameters: + + - `$height ` Height of block + +Return: `` + +```json +{ + "block_header": { + "depth": 78376, + "difficulty": 815625611, + "hash": "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6", + "height": 912345, + "major_version": 1, + "minor_version": 2, + "nonce": 1646, + "orphan_status": false, + "prev_hash": "b61c58b2e0be53fad5ef9d9731a55e8a81d972b8d90ed07c04fd37ca6403ff78", + "reward": 7388968946286, + "timestamp": 1452793716 + }, + "status": "OK" +} +``` + +#### `getblock_by_hash` + +Look up block information by SHA256 hash + +Parameters: + + - `$hash ` SHA256 hash of block + +Return: `` + +```json +{ + "block_header": { + "depth": 78376, + "difficulty": 815625611, + "hash": "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6", + "height": 912345, + "major_version": 1, + "minor_version": 2, + "nonce": 1646, + "orphan_status": false, + "prev_hash": "b61c58b2e0be53fad5ef9d9731a55e8a81d972b8d90ed07c04fd37ca6403ff78", + "reward": 7388968946286, + "timestamp": 1452793716 + }, + "status": "OK" +} +``` + +#### `getblock_by_height` + +Look up block information by height + +Parameters: + + - `$height ` Height of block + +Return: `` + +```json +{ + "blob": "...", + "block_header": { + "depth": 80694, + "difficulty": 815625611, + "hash": "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6", + "height": 912345, + "major_version": 1, + "minor_version": 2, + "nonce": 1646, + "orphan_status": false, + "prev_hash": "b61c58b2e0be53fad5ef9d9731a55e8a81d972b8d90ed07c04fd37ca6403ff78", + "reward": 7388968946286, + "timestamp": 1452793716 + }, + "json": "...", + "status": "OK" +} +``` + +#### `get_connections` + +Look up incoming and outgoing connections to your node + +*No parameters* + +Return: `` + +```json +{ + "connections": [{ + "avg_download": 0, + "avg_upload": 0, + "current_download": 0, + "current_upload": 0, + "incoming": false, + "ip": "76.173.170.133", + "live_time": 1865, + "local_ip": false, + "localhost": false, + "peer_id": "3bfe29d6b1aa7c4c", + "port": "18080", + "recv_count": 116396, + "recv_idle_time": 23, + "send_count": 176893, + "send_idle_time": 1457726610, + "state": "state_normal" + },{ + .. + }], + "status": "OK" +} +``` + +#### `get_info` + +Look up general information about the state of your node and the network + +*No parameters* + +Return: `` + +```json +{ + "alt_blocks_count": 5, + "difficulty": 972165250, + "grey_peerlist_size": 2280, + "height": 993145, + "incoming_connections_count": 0, + "outgoing_connections_count": 8, + "status": "OK", + "target": 60, + "target_height": 993137, + "testnet": false, + "top_block_hash": "", + "tx_count": 564287, + "tx_pool_size": 45, + "white_peerlist_size": 529 +} +``` + +#### `hardfork_info` + +Look up information regarding hard fork voting and readiness + +*No parameters* + +Return: `` + +```json +{ + "alt_blocks_count": 0, + "block_size_limit": 600000, + "block_size_median": 85, + "bootstrap_daemon_address": ?, + "cumulative_difficulty": 40859323048, + "difficulty": 57406, + "free_space": 888592449536, + "grey_peerlist_size": 526, + "height": 1066107, + "height_without_bootstrap": 1066107, + "incoming_connections_count": 1, + "offline": ?, + "outgoing_connections_count": 1, + "rpc_connections_count": 1, + "start_time": 1519963719, + "status": OK, + "target": 120, + "target_height": 1066073, + "testnet": 1, + "top_block_hash": e438aae56de8e5e5c8e0d230167fcb58bc8dde09e369ff7689a4af146040a20e, + "tx_count": 52632, + "tx_pool_size": 0, + "untrusted": ?, + "was_bootstrap_ever_used: ?, + "white_peerlist_size": 5 +} +``` + +#### `setbans` + +Ban another node by IP + +Parameters: + + - `$ip ` IP address of node to ban + +Return: `` + +```json +{ + "status": "OK" +} +``` + +#### `getbans` + +Get list of banned IPs + +*No parameters* + + - `$ <>` + +Return: `` + +```json +{ + "bans": [{ + "ip": 838969536, + "seconds": 1457748792 + }], + "status": "OK" +} +``` + +### Credits + +Written by the [Monero Integrations team](https://github.com/monero-integrations/monerophp/graphs/contributors) () + +Using work from: + - CryptoChangements [Monero_RPC] () (https://github.com/cryptochangements34) + - Serhack [Monero Integrations] () (https://serhack.me) + - TheKoziTwo [xmr-integration] () + - Andrew LeCody [EasyBitcoin-PHP] + - Kacper Rowinski [jsonRPCClient] () diff --git a/vendor/monero-integrations/monerophp/docs/ed25519.md b/vendor/monero-integrations/monerophp/docs/ed25519.md new file mode 100644 index 0000000..3e23212 --- /dev/null +++ b/vendor/monero-integrations/monerophp/docs/ed25519.md @@ -0,0 +1,29 @@ +# `` class + +[`src/.php`](https://github.com/monero-integrations/monerophp/tree/master/src/.php) + +*Documentation under development* + +### Methods + + - `H` + - `pymod` + - `expmod` + - `inv` + - `xrecover` + - `edwards` + - `scalarmult` + - `scalarloop` + - `bitsToString` + - `dec2bin_i` + - `encodeint` + - `encodepoint` + - `bit` + - `publickey` + - `Hint` + - `signature` + - `isoncurve` + - `decodeint` + - `decodepoint` + - `checkvalid` + - `scalarmult_base` \ No newline at end of file diff --git a/vendor/monero-integrations/monerophp/docs/walletRPC.md b/vendor/monero-integrations/monerophp/docs/walletRPC.md new file mode 100644 index 0000000..3c45c63 --- /dev/null +++ b/vendor/monero-integrations/monerophp/docs/walletRPC.md @@ -0,0 +1,1142 @@ +# `walletRPC` class + +[`src/walletRPC.php`](https://github.com/monero-integrations/monerophp/tree/master/src/walletRPC.php) + +A class for making calls to monero-wallet-rpc using PHP + +Parameters: + + - `$host ` monero-wallet-rpc hostname *(optional)* + - `$port ` monero-wallet-rpc port *(optional)* + - `$protocol ` monero-wallet-rpc protocol (eg. 'http') *(optional)* + - `$user ` monero-wallet-rpc RPC username *(optional)* + - `$password ` monero-wallet-rpc RPC passphrase *(optional)* + +Parameters can also be passed in as an associative array (object/dictionary,) as in: + +```php +$walletRPC = new walletRPC(['host' => '127.0.0.1', 'port' => 28083]) +``` + +If an object is used to provide parameters (as above,) parameters can be declared in any order. + +### Methods + + - [`_transform`](#_transform) + - [`get_balance` (alias: `getbalance`)](#get_balance) + - [`get_address` (alias: `getaddress`)](#get_address) + - [`create_address`](#create_address) + - [`label_address`](#label_address) + - [`get_accounts`](#get_accounts) + - [`create_account`](#create_account) + - [`label_account`](#label_account) + - [`get_account_tags`](#get_account_tags) + - [`tag_accounts`](#tag_accounts) + - [`untag_accounts`](#untag_accounts) + - [`set_account_tag_description`](#set_account_tag_description) + - [`get_height` (alias: `getheight`)](#get_height) + - [`transfer`](#transfer) + - [`transfer_split`](#transfer_split) + - [`sweep_dust`](#sweep_dust) + - [`sweep_unmixable`](#sweep_unmixable) + - [`sweep_all`](#sweep_all) + - [`sweep_single`](#sweep_single) + - [`relay_tx`](#relay_tx) + - [`store`](#store) + - [`get_payments`](#get_payments) + - [`get_bulk_payments`](#get_bulk_payments) + - [`incoming_transfers`](#incoming_transfers) + - [`query_key`](#query_key) + - [`view_key`](#view_key) + - [`spend_key`](#spend_key) + - [`mnemonic`](#mnemonic) + - [`make_integrated_address`](#make_integrated_address) + - [`split_integrated_address`](#split_integrated_address) + - [`stop_wallet`](#stop_wallet) + - [`rescan_blockchain`](#rescan_blockchain) + - [`set_tx_notes`](#set_tx_notes) + - [`get_tx_notes`](#get_tx_notes) + - [`set_attribute`](#set_attribute) + - [`get_attribute`](#get_attribute) + - [`get_tx_key`](#get_tx_key) + - [`check_tx_key`](#check_tx_key) + - [`get_tx_proof`](#get_tx_proof) + - [`check_tx_proof`](#check_tx_proof) + - [`get_spend_proof`](#get_spend_proof) + - [`check_spend_proof`](#check_spend_proof) + - [`get_reserve_proof`](#get_reserve_proof) + - [`check_reserve_proof`](#check_reserve_proof) + - [`get_transfers`](#get_transfers) + - [`get_transfer_by_txid`](#get_transfer_by_txid) + - [`sign`](#sign) + - [`verify`](#verify) + - [`export_key_images`](#export_key_images) + - [`import_key_images`](#import_key_images) + - [`make_uri`](#make_uri) + - [`parse_uri`](#parse_uri) + - [`get_address_book`](#get_address_book) + - [`add_address_book`](#add_address_book) + - [`delete_address_book`](#delete_address_book) + - [`rescan_spent`](#rescan_spent) + - [`start_mining`](#start_mining) + - [`stop_mining`](#stop_mining) + - [`get_languages`](#get_languages) + - [`create_wallet`](#create_wallet) + - [`open_wallet`](#open_wallet) + - [`is_multisig`](#is_multisig) + - [`prepare_multisig`](#prepare_multisig) + - [`make_multisig`](#make_multisig) + - [`export_multisig_info`](#export_multisig_info) + - [`import_multisig_info`](#import_multisig_info) + - [`finalize_multisig`](#finalize_multisig) + - [`sign_multisig`](#sign_multisig) + - [`submit_multisig`](#submit_multisig) + - [`get_client` (alias: `getClient`)](#get_client) + +#### `_transform` + +Convert from moneroj to tacoshi (piconero) + +Parameters: + + - `$method ` Amount (in monero) to transform to tacoshi (piconero) *(optional)* + +Return: `` + +#### `get_balance` + +Look up an account's balance + +Parameters: + + - `$account_index ` Index of account to look up *( + optional)* + +Return: `` + +```json +{ + "balance": 140000000000, + "unlocked_balance": 50000000000 +} +``` + +Alias: `getbalance` + +#### `get_address` + +Look up wallet address(es) + +Parameters: + + - `$account_index ` Index of account to look up *(optional)* + - `$address_index ` Index of subaddress to look up *(optional)* + +Return: `` + +```json +{ + "address": "A2XE6ArhRkVZqepY2DQ5QpW8p8P2dhDQLhPJ9scSkW6q9aYUHhrhXVvE8sjg7vHRx2HnRv53zLQH4ATS", + "addresses": [ + { + "address": "A2XE6ArhRkVZqepY2DQ5QpW8p8P2dhDQLhPJ9scSkW6q9aYUHhrhXVvE8sjg7vHRx2HnRv53zLQH", + "address_index": 0, + "label": "Primary account", + "used": true + }, { + "address": "Bh3ttLbjGFnVGCeGJF1HgVh4DfCaBNpDt7PQAgsC2GFug7WKskgfbTmB6e7UupyiijiHDQPmDC7w", + "address_index": 1, + "label": "", + "used": true + } + ] +} +``` + +Alias: `getaddress` + +#### `create_address` + +Create a new subaddress + +Parameters: + + - `$account_index ` The subaddress account index + - `$label ` A label to the new subaddress + +Return: `` + +```json +{ + "address": "Bh3ttLbjGFnVGCeGJF1HgVh4DfCaBNpDt7PQAgsC2GFug7WKskgfbTmB6e7UupyiijiHDQPmDC7wSC", + "address_index": 1 +} +``` + +#### `label_address` + +Label a subaddress + +Parameters: + + - `$index ` The index of the subaddress to label + - `$label ` The label to apply + +#### `get_accounts` + +Look up wallet accounts + +Return: `` + +```json +{ + "subaddress_accounts": { + "0": { + "account_index": 0, + "balance": 2808597352948771, + "base_address": "A2XE6ArhRkVZqepY2DQ5QpW8p8P2dhDQLhPJ9scSkW6q9aYUHhrhXVvE8sjg7vHRx2HnR", + "label": "Primary account", + "tag": "", + "unlocked_balance": 2717153096298162 + }, + "1": { + "account_index": 1, + "balance": 0, + "base_address": "BcXKsfrvffKYVoNGN4HUFfaruAMRdk5DrLZDmJBnYgXrTFrXyudn81xMj7rsmU5P9dX56", + "label": "Secondary account", + "tag": "", + "unlocked_balance": 0 + }, + "total_balance": 2808597352948771, + "total_unlocked_balance": 2717153096298162 +} +``` + +#### `create_account` + +Create a new account + +Parameters: + + - `$label ` Label to apply to new account + +#### `label_account` + +Label an account + +Parameters: + + - `$account_index ` Index of account to label + - `$label ` Label to apply + +#### `get_account_tags` + +Look up account tags + +Return: `` + +```json +{ + "account_tags": { + "0": { + "accounts": { + "0": 0, + "1": 1 + }, + "label": "", + "tag": "Example tag" + } + } +} +``` + +#### `tag_accounts` + +Tag accounts + +Parameters: + + - `$accounts ` The indices of the accounts to tag + - `$tag ` Tag to apply + +#### `untag_accounts` + +Untag accounts + +Parameters: + + - `$accounts ` The indices of the accounts to untag + +#### `set_account_tag_description` + +Describe a tag + +Parameters: + + - `$tag ` Tag to describe + - `$description ` Description to apply to tag + +Return: `` + +[//]: # (TODO example) + +#### `get_height` + +Look up how many blocks are in the longest chain known to the wallet + +Return: `` + +```json +{ + "height": 994310 +} +``` + +Alias: `getheight` + +#### `transfer` + +Send monero + +Parameters can be passed in individually (as listed below) or as an object/dictionary (as listed below) or as an object/dictionary (as listed at bottom) + +To send to multiple recipients, use the object/dictionary (bottom) format and pass an array of recipient addresses and amount arrays in the destinations field (as in "destinations = [['amount' => 1, 'address' => ...], ['amount' => 2, 'address' => ...]]") + +Parameters: + + - `$amount ` Amount of monero to send + - `$address ` Address to receive funds + - `$payment_id ` Payment + - `$mixin ` Mixin number (ringsize - 1) + - `$account_index ` Account to send + - `$subaddr_indices ` Comma-separated list of subaddress indices to spend + - `$priority ` Transaction + - `$unlock_time ` UNIX time or block height to unlock + - `$do_not_relay ` Do not relay + + *or* + + - `$params ` Array containing any of the options listed above, where only amount and address or a destionation's array are required + +Return: `` + +```json +{ + "amount": "1000000000000", + "fee": "1000020000", + "tx_hash": "c60a64ddae46154a75af65544f73a7064911289a7760be8fb5390cb57c06f2db", + "tx_key": "805abdb3882d9440b6c80490c2d6b95a79dbc6d1b05e514131a91768e8040b04" +} +``` + +#### `transfer_split` + +Same as transfer, but splits transfer into more than one transaction if necessary + +Return: `` + +[//]: # (TODO example) + +#### `sweep_dust` + +Send all dust outputs back to the wallet + +Return: `` + +[//]: # (TODO example) + +#### `sweep_unmixable` + +Send all unmixable outputs back to the wallet + +Return: `` + +[//]: # (TODO example) + +#### `sweep_all` + +Send all unlocked outputs from an account to an address + +Parameters: + + - `$address ` Address to receive funds + - `$subaddr_indices ` Comma-separated list of subaddress indices to sweep *(optional)* + - `$account_index ` Index of the account to sweep *(optional)* + - `$payment_id ` Payment ID *(optional)* + - `$mixin ` Mixin number (ringsize - 1) *(optional)* + - `$priority ` Payment ID *(optional)* + - `$below_amount ` Only send outputs below this amount *(optional)* + - `$unlock_time ` UNIX time or block height to unlock output *(optional)* + - `$do_not_relay ` Do not relay transaction *(optional)* + + *or* + + - `$params ` Array containing any of the options listed above, where only address is required + +Return: `` + +```json +{ + "amount": "1000000000000", + "fee": "1000020000", + "tx_hash": "c60a64ddae46154a75af65544f73a7064911289a7760be8fb5390cb57c06f2db", + "tx_key": "805abdb3882d9440b6c80490c2d6b95a79dbc6d1b05e514131a91768e8040b04" +} +``` + +#### `sweep_single` + +Sweep a single key image to an address + +Parameters: + + - `$key_image ` Key image to sweep + - `$address ` Address to receive funds + - `$payment_id ` Payment ID *(optional)* + - `$below_amount ` Only send outputs below this amount *(optional)* + - `$mixin ` Mixin number (ringsize - 1) *(optional)* + - `$priority ` Payment ID *(optional)* + - `$unlock_time ` UNIX time or block height to unlock output *(optional)* + - `$do_not_relay ` Do not relay transaction *(optional)* + + *or* + + - `$params ` Array containing any of the options listed above, where +Return: `` + +```json +{ + "amount": "1000000000000", + "fee": "1000020000", + "tx_hash": "c60a64ddae46154a75af65544f73a7064911289a7760be8fb5390cb57c06f2db", + "tx_key": "805abdb3882d9440b6c80490c2d6b95a79dbc6d1b05e514131a91768e8040b04" +} +``` + +#### `relay_tx` + +Relay a transaction + +Parameters: + + - `$hex ` Blob of transaction to relay + +Return: `` + +[//]: # (TODO example) + +#### `store` + +Save wallet + +[//]: # (TODO example) + +#### `get_payments` + +Look up incoming payments by payment ID + +Parameters: + + - `$payment_id ` Payment ID to look up + +Return: `` + +```json +{ + "payments": [{ + "amount": 10350000000000, + "block_height": 994327, + "payment_id": "4279257e0a20608e25dba8744949c9e1caff4fcdafc7d5362ecf14225f3d9030", + "tx_hash": "c391089f5b1b02067acc15294e3629a463412af1f1ed0f354113dd4467e4f6c1", + "unlock_time": 0 + }] +} +``` + +#### `get_bulk_payments` + +Look up incoming payments by payment ID (or a list of payments IDs) from a given height + +Parameters: + + - `$payment_ids ` Array of payment IDs to look up + - `$min_block_height ` Height to begin search + +Return: `` + +```json +{ + "payments": [{ + "amount": 10350000000000, + "block_height": 994327, + "payment_id": "4279257e0a20608e25dba8744949c9e1caff4fcdafc7d5362ecf14225f3d9030", + "tx_hash": "c391089f5b1b02067acc15294e3629a463412af1f1ed0f354113dd4467e4f6c1", + "unlock_time": 0 + }] +} +``` + +#### `incoming_transfers` + +Look up incoming transfers + +Parameters: + + - `$type ` Type of transfer to look up; must be 'all', 'available', or 'unavailable' (incoming transfers which have already been spent) + - `$account_index ` Index of account to look up *(optional)* + - `$subaddr_indices ` Comma-separated list of subaddress indices to look up *(optional)* + +Return: `` + +```json +{ + "transfers": [{ + "amount": 10000000000000, + "global_index": 711506, + "spent": false, + "tx_hash": "c391089f5b1b02067acc15294e3629a463412af1f1ed0f354113dd4467e4f6c1", + "tx_size": 5870 + },{ + "amount": 300000000000, + "global_index": 794232, + "spent": false, + "tx_hash": "c391089f5b1b02067acc15294e3629a463412af1f1ed0f354113dd4467e4f6c1", + "tx_size": 5870 + },{ + "amount": 50000000000, + "global_index": 213659, + "spent": false, + "tx_hash": "c391089f5b1b02067acc15294e3629a463412af1f1ed0f354113dd4467e4f6c1", + "tx_size": 5870 + }] +} +``` + +#### `query_key` + +Look up a wallet key + +Parameters: + + - `$key_type ` Type of key to look up; must be 'view_key', 'spend_key', or 'mnemonic' + +Return: `` + +```json +{ + "key": "7e341d..." +} +``` + +#### `view_key` + +Look up wallet view key + +Return: `` + +```json +{ + "key": "7e341d..." +} +``` + +#### `spend_key` + +Look up wallet spend key + +Return: `` + +```json +{ + "key": "2ab810..." +} +``` + +#### `mnemonic` + +Look up wallet mnemonic seed + +Return: `` + +```json +{ + "key": "2ab810..." +} +``` + +#### `make_integrated_address` + +Create an integrated address from a given payment ID + +Parameters: + + - `$payment_id ` Payment ID *(optional)* + +Return: `` + +```json +{ + "integrated_address": "4BpEv3WrufwXoyJAeEoBaNW56ScQaLXyyQWgxeRL9KgAUhVzkvfiELZV7fCPBuuB2CG +} +``` + +#### `split_integrated_address` + +Look up the wallet address and payment ID corresponding to an integrated address + +Parameters: + + - `$integrated_address ` Integrated address to split + +Return: `` + +```json +{ + "payment_id": "420fa29b2d9a49f5", + "standard_address": "427ZuEhNJQRXoyJAeEoBaNW56ScQaLXyyQWgxeRL9KgAUhVzkvfiELZV7fCPBuuB2CGuJ +} +``` + +#### `stop_wallet` + +Stop the wallet, saving the state + +#### `rescan_blockchain` + +Rescan the blockchain from scratch + +#### `set_tx_notes` + +Add notes to transactions + + - `$txids ` Array of transaction IDs to note + +Parameters: + + - `$notes ` Array of notes (strings) to add + +#### `get_tx_notes` + +Look up transaction note + +Parameters: + + - `$txids ` Array of transaction IDs (strings) to look up + +Return: `` + +[//]: # (TODO example) + +#### `set_attribute` + +Set a wallet option + +Parameters: + + - `$key ` Option to set + - `$value ` Value to set + +#### `get_attribute` + +Look up a wallet option + +Parameters: + + - `$key ` Wallet option to query +Return: `` + +[//]: # (TODO example) + +#### `get_tx_key` + +Look up a transaction key + +Parameters: + + - `$txid ` Transaction ID to look up + +Return: + +```json +Example: { + "tx_key": "e8e97866b1606bd87178eada8f995bf96d2af3fec5db0bc570a451ab1d589b0f" +} +``` + +#### `check_tx_key` + +Check a transaction key + +Parameters: + + - `$address ` Address that sent transaction + - `$txid ` Transaction ID + - `$tx_key ` Transaction key + +Return: + +```json +Example: { + "confirmations": 1, + "in_pool": , + "received": 0 +} +``` + +#### `get_tx_proof` + +Create proof (signature) of transaction + +Parameters: + + - `$address ` Address that spent funds + - `$txid ` Transaction ID +Return: `` + +```json +{ + "signature": "InProofV1Lq4nejMXxMnAdnLeZhHe3FGCmFdnSvzVM1AiGcXjngTRi4hfHPcDL9D4th7KUuvF9ZH +} +``` + +#### `check_tx_proof` + +Verify transaction proof + +Parameters: + + - `$address ` Address that spent funds + - `$txid ` Transaction ID + - `$signature ` Signature (tx_proof) + +Return: + +```json +{ + "confirmations": 2, + "good": 1, + "in_pool": , + "received": 15752471409492, +} +``` + +#### `get_spend_proof` + +Create proof of a spend + +Parameters: + + - `$txid ` Transaction ID + +Return: `` + +```json +{ + "signature": "SpendProofV1RnP6ywcDQHuQTBzXEMiHKbe5ErzRAjpUB1h4RUMfGPNv4bbR6V7EFyiYkCrURwbb +} +``` + +#### `check_spend_proof` + +Verify spend proof + +Parameters: + + - `$txid ` Transaction ID + - `$signature ` Spend proof to verify + +Return: `` + +```json +{ + "good": 1 +} +``` + +#### `get_reserve_proof` + +Create proof of reserves + +Parameters: + + - `$account_index ` Comma-separated list of account indices of which to prove + +Return: + +```json +{ + "signature": "ReserveProofV11BZ23sBt9sZJeGccf84mzyAmNCP3KzYbE111111111111AjsVgKzau88VxXVGA +} +``` + +#### `check_reserve_proof` + +Verify a reserve proof + +Parameters: + + - `$address ` Wallet address + - `$signature ` Reserve proof + +Return: `` + +```json +{ + "good": 1, + "spent": 0, + "total": 0 +} +``` + +#### `get_transfers` + +Look up transfers + +Parameters: + + - `$input_types ` Array of transfer type strings; possible values include 'all', 'in', 'out', 'pending', 'failed', and 'pool' *(optional)* + - `$account_index ` Index of account to look *(optional)* + - `$subaddr_indices ` Comma-separated list of subaddress indices to look up *(optional)* + - `$min_height ` Minimum block height to use when looking up *(optional)* + - `$max_height ` Maximum block height to use when looking up *(optional)* + + *or* + + - `$inputs_types ` Array containing any of the options listed above, where only an input types array is required + +Return: `` + +```json +{ + "pool": [{ + "amount": 500000000000, + "fee": 0, + "height": 0, + "note": "", + "payment_id": "758d9b225fda7b7f", + "timestamp": 1488312467, + "txid": "da7301d5423efa09fabacb720002e978d114ff2db6a1546f8b820644a1b96208", + "type": "pool" + }] +} +``` + +#### `get_transfer_by_txid` + +Look up transaction by transaction ID + +Parameters: + + - `$txid ` Transaction ID to look up + - `$account_index ` Index of account to query *(optional)* + +Return: `` + +```json +{ + "transfer": { + "amount": 10000000000000, + "fee": 0, + "height": 1316388, + "note": "", + "payment_id": "0000000000000000", + "timestamp": 1495539310, + "txid": "f2d33ba969a09941c6671e6dfe7e9456e5f686eca72c1a94a3e63ac6d7f27baf", + "type": "in" + } +} +``` + +#### `sign` + +Sign a string + +Parameters: + + - `$data ` Data to sign + +Return: `` + +```json +{ + "signature": "SigV1Xp61ZkGguxSCHpkYEVw9eaWfRfSoAf36PCsSCApx4DUrKWHEqM9CdNwjeuhJii6LHDVDFxv +} +``` + +#### `verify` + +Verify a signature + +Parameters: + + - `$data ` Signed data + - `$address ` Address that signed data + - `$signature ` Signature to verify + +Return: + +```json +{ + "good": true +} +``` + +#### `export_key_images` + +Export an array of signed key images + +Return: `` + +[//]: # (TODO example) + +#### `import_key_images` + +Import a signed set of key images + +Parameters: + + - `$signed_key_images ` Array of signed key images + +Return: + +```json +{ + // TODO example + height: , + spent: , + unspent: +} +``` + +#### `make_uri` + +Create a payment URI using the official URI specification + +Parameters: + + - `$address ` Address to receive funds + - `$amount ` Amount of monero to request + - `$payment_id ` Payment ID *(optional)* + - `$recipient_name ` Name of recipient *(optional)* + - `$tx_description ` Payment description *(optional)* + +Return: `` + +[//]: # (TODO example) + +#### `parse_uri` + +Parse a payment URI + +Parameters: + + - `$uri ` Payment URI + +Return: `` + +```json +{ + "uri": { + "address": "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VB + "amount": 10, + "payment_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + "recipient_name": "Monero Project donation address", + "tx_description": "Testing out the make_uri function" + } +} +``` + +#### `get_address_book` + +Look up address book entries + +Parameters: + + - `$entries ` Array of address book entry indices to look up + +Return: `` + +[//]: # (TODO example) + +#### `add_address_book` + +Add entry to the address book + +Parameters: + + - `$address ` Address to add to address book + - `$payment_id ` Payment ID to use with address in address book *(optional)* + - `$description ` Description of address *(optional)* + +Return: `` + +[//]: # (TODO example) + +#### `delete_address_book` + +Delete an entry from the address book + +Parameters: + + - `$index ` Index of the address book entry to remove + +#### `rescan_spent` + +Rescan the blockchain for spent outputs + +#### `start_mining` + +Start mining + + - `$threads_count ` Number of threads with which to mine + - `$do_background_mining ` Mine in background? + - `$ignore_battery ` Ignore battery? + +#### `stop_mining` + +Stop mining + +#### `get_languages` + +Look up a list of available languages for your wallet's seed + +Return: `` + +[//]: # (TODO example) + +#### `create_wallet` + +Create a new wallet + + - `$filename ` Filename of new wallet to create + - `$password ` Password of new wallet to create + - `$language ` Language of new wallet to create + +#### `open_wallet` + +Open a wallet + +Parameters: + + - `$filename ` Filename of wallet to open + - `$password ` Password of wallet to open + +#### `is_multisig` + +Check if wallet is multisig + +Return: `` + +[//]: # (TODO multisig wallet example) + +Non-multisignature wallet return: + +```json +{ + "multisig": , + "ready": , + "threshold": 0, + "total": 0 +} +``` + +#### `prepare_multisig` + +Create information needed to create a multisignature wallet + +Return: `` + +```json +{ + "multisig_info": "MultisigV1WBnkPKszceUBriuPZ6zoDsU6RYJuzQTiwUqE5gYSAD1yGTz85vqZGetawVvioa +} +``` + +#### `make_multisig` + +Create a multisignature wallet + +Parameters: + + - `$multisig_info ` Multisignature information (from eg. prepare_multisig) + - `$threshold ` Threshold required to spend from multisignature wallet + - `$password ` Passphrase to apply to multisignature wallet + +Return: `` + +[//]: # (TODO example) + +#### `export_multisig_info` + +Export multisignature information + +Return: `` + +[//]: # (TODO example) + +#### `import_multisig_info` + +Import multisignature information + +Parameters: + + - `$info ` Multisignature info (from eg. prepare_multisig) + +Return: `` + +[//]: # (TODO example) + +#### `finalize_multisig` + +Finalize a multisignature wallet + +Parameters: + + - `$multisig_info ` Multisignature info (from eg. prepare_multisig) + - `$password ` Multisignature info (from eg. prepare_multisig) + +Return: `` + +[//]: # (TODO example) + +#### `sign_multisig` + +Sign a multisignature transaction + +Parameters: + + - `$tx_data_hex ` Blob of transaction to sign + +Return: `` + +[//]: # (TODO example) + +#### `submit_multisig` + +Submit (relay) a multisignature transaction + +Parameters: + + - `$tx_data_hex ` Blob of transaction to submit + +Return: `` + +[//]: # (TODO example) + +#### `get_client` + +Return the `jsonRPCClient` used by the class + +Return: `jsonRPCClient` + +Alias: `getClient` + +### Credits + +Written by the [Monero Integrations team](https://github.com/monero-integrations/monerophp/graphs/contributors) () + +Using work from: + - CryptoChangements [Monero_RPC] () (https://github.com/cryptochangements34) + - Serhack [Monero Integrations] () (https://serhack.me) + - TheKoziTwo [xmr-integration] () + - Kacper Rowinski [jsonRPCClient] () diff --git a/vendor/monero-integrations/monerophp/example.php b/vendor/monero-integrations/monerophp/example.php new file mode 100644 index 0000000..1e182e9 --- /dev/null +++ b/vendor/monero-integrations/monerophp/example.php @@ -0,0 +1,166 @@ + '127.0.0.1', 'port' => 28081]) // Passing parameters in as array; parameters can be in any order and all are optional. +$getblockcount = $daemonRPC->getblockcount(); +$on_getblockhash = $daemonRPC->on_getblockhash(42069); +// $getblocktemplate = $daemonRPC->getblocktemplate('9sZABNdyWspcpsCPma1eUD5yM3efTHfsiCx3qB8RDYH9UFST4aj34s5Ygz69zxh8vEBCCqgxEZxBAEC4pyGkN4JEPmUWrxn', 60); +// $submitblock = $daemonRPC->submitblock($block_blob); +$getlastblockheader = $daemonRPC->getlastblockheader(); +// $getblockheaderbyhash = $daemonRPC->getblockheaderbyhash('fc7ba2a76071f609e39517dc0388a77f3e27cc2f98c8e933918121b729ee6f27'); +// $getblockheaderbyheight = $daemonRPC->getblockheaderbyheight(696969); +// $getblock_by_hash = $daemonRPC->getblock_by_hash('fc7ba2a76071f609e39517dc0388a77f3e27cc2f98c8e933918121b729ee6f27'); +// $getblock_by_height = $daemonRPC->getblock_by_height(696969); + +/*$get_connections = $daemonRPC->get_connections();*/ +$get_info = $daemonRPC->get_info(); +// $hardfork_info = $daemonRPC->hardfork_info(); +// $setbans = $daemonRPC->setbans('8.8.8.8'); +// $getbans = $daemonRPC->getbans(); + +require_once('src/walletRPC.php'); + +$walletRPC = new walletRPC('127.0.0.1', 18083, false); // Change to match your wallet (monero-wallet-rpc) IP address and port; 18083 is the customary port for mainnet, 28083 for testnet, 38083 for stagenet +// $daemonRPC = new walletRPC(['host' => '127.0.0.1', 'port' => 28081]) // Passing parameters in as array; parameters can be in any order and all are optional. +/*$create_wallet = $walletRPC->create_wallet('monero_wallet', ''); // Creates a new wallet named monero_wallet with no passphrase. Comment this line and edit the next line to use your own wallet*/ +/*$open_wallet = $walletRPC->open_wallet('monero_wallet', '');*/ +/*$get_address = $walletRPC->get_address();*/ +/*$get_accounts = $walletRPC->get_accounts();*/ +/*$get_balance = $walletRPC->get_balance();*/ +// $create_address = $walletRPC->create_address(0, 'This is an example subaddress label'); // Create a subaddress on account 0 +// $tag_accounts = $walletRPC->tag_accounts([0], 'This is an example account tag'); +// $get_height = $walletRPC->get_height(); +// $transfer = $walletRPC->transfer(1, '9sZABNdyWspcpsCPma1eUD5yM3efTHfsiCx3qB8RDYH9UFST4aj34s5Ygz69zxh8vEBCCqgxEZxBAEC4pyGkN4JEPmUWrxn'); // First account generated from mnemonic 'gang dying lipstick wonders howls begun uptight humid thirsty irony adept umpire dusted update grunt water iceberg timber aloof fudge rift clue umpire venomous thirsty' +// $transfer = $walletRPC->transfer(['address' => '9sZABNdyWspcpsCPma1eUD5yM3efTHfsiCx3qB8RDYH9UFST4aj34s5Ygz69zxh8vEBCCqgxEZxBAEC4pyGkN4JEPmUWrxn', 'amount' => 1, 'priority' => 1]); // Passing parameters in as array +// $transfer = $walletRPC->transfer(['destinations' => ['amount' => 1, 'address' => '9sZABNdyWspcpsCPma1eUD5yM3efTHfsiCx3qB8RDYH9UFST4aj34s5Ygz69zxh8vEBCCqgxEZxBAEC4pyGkN4JEPmUWrxn', 'amount' => 2, 'address' => 'BhASuWq4HcBL1KAwt4wMBDhkpwseFe6pNaq5DWQnMwjBaFL8isMZzcEfcF7x6Vqgz9EBY66g5UBrueRFLCESojoaHaTPsjh'], 'priority' => 1]); // Multiple payments in one transaction +// $sweep_all = $walletRPC->sweep_all('9sZABNdyWspcpsCPma1eUD5yM3efTHfsiCx3qB8RDYH9UFST4aj34s5Ygz69zxh8vEBCCqgxEZxBAEC4pyGkN4JEPmUWrxn'); +// $sweep_all = $walletRPC->sweep_all(['address' => '9sZABNdyWspcpsCPma1eUD5yM3efTHfsiCx3qB8RDYH9UFST4aj34s5Ygz69zxh8vEBCCqgxEZxBAEC4pyGkN4JEPmUWrxn', 'priority' => 1]); +// $get_transfers = $walletRPC->get_transfers('in', true); +// $incoming_transfers = $walletRPC->incoming_transfers('all'); +// $mnemonic = $walletRPC->mnemonic(); + +?> + + +

+ + + MoneroPHP + +

+

MoneroPHP was developed by SerHack and the Monero-Integrations team! Please report any issues or request additional features at github.com/monero-integrations/monerophp.

+ +

daemonRPC.php example

+

Note: not all methods shown, nor all results from each method.

+
+
getblockcount()
+
+

Status:

+

Height:

+
+
on_getblockhash(42069)
+
+

Block hash:

+
+
getlastblockheader()
+
+

Current block hash:

+

Previous block hash:

+
+
get_connections()
+
+

Connections:

+ ' . $peer['address'] . ' (' . ( $peer['height'] == $getblockcount['count'] ? 'synced' : ( $peer['height'] > $getblockcount['count'] ? 'ahead; syncing' : 'behind; syncing') ). ')

'; } ?> +
+
get_info()
+
+

Difficulty:

+

Cumulative difficulty:

+
+
+ +

walletRPC.php example

+

Note: not all methods shown, nor all results from each method.

+
+ +
get_accounts()
+
+

Accounts:

+ Account ' . $account['account_index'] . ': ' . $account['base_address'] . '
'; + echo 'Balance: ' . $account['balance'] / pow(10, 12) . ' (' . $account['unlocked_balance'] / pow(10, 12) . ' unlocked)
'; + echo ( $account['label'] ) ? 'Label: ' . $account['label'] . '
' : ''; + echo ( $account['tag'] ) ? 'Tag: ' . $account['tag'] . '
' : ''; + echo '

'; + } + ?> +
+
get_balance()
+
+

Balance:

+

Unlocked balance:

+
+
+ + + + + + + diff --git a/vendor/monero-integrations/monerophp/phpcs.xml b/vendor/monero-integrations/monerophp/phpcs.xml new file mode 100644 index 0000000..c5b6c24 --- /dev/null +++ b/vendor/monero-integrations/monerophp/phpcs.xml @@ -0,0 +1,14 @@ + + + + PHPCS configuration file. + + src + tests + + + + tests/reports + tests/_data + + \ No newline at end of file diff --git a/vendor/monero-integrations/monerophp/phpstan.neon b/vendor/monero-integrations/monerophp/phpstan.neon new file mode 100644 index 0000000..3e26cf7 --- /dev/null +++ b/vendor/monero-integrations/monerophp/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: 6 + paths: + - src + treatPhpDocTypesAsCertain: false \ No newline at end of file diff --git a/vendor/monero-integrations/monerophp/src/Cryptonote.php b/vendor/monero-integrations/monerophp/src/Cryptonote.php new file mode 100644 index 0000000..9f8269c --- /dev/null +++ b/vendor/monero-integrations/monerophp/src/Cryptonote.php @@ -0,0 +1,384 @@ + [ + "STANDARD" => dechex(18), + "INTEGRATED" => dechex(19), + "SUBADDRESS" => dechex(42), + ], + "stagenet" => [ + "STANDARD" => dechex(24), + "INTEGRATED" => dechex(25), + "SUBADDRESS" => dechex(36), + ], + "testnet" => [ + "STANDARD" => dechex(53), + "INTEGRATED" => dechex(54), + "SUBADDRESS" => dechex(63), + ] + ]; + if (array_key_exists($network, $networks_prefixes)) { + $this->network_prefixes = $networks_prefixes[$network]; + } else { + throw new Exception("Error: Invalid Network, should be one of " . join(", ", array_keys($networks_prefixes))); + } + + $this->ed25519 = new ed25519(); + $this->base58 = new base58(); + $this->varint = new Varint(); + } + + /* + * @param string Hex encoded string of the data to hash + * @return string Hex encoded string of the hashed data + * + */ + public function keccak_256($message) + { + $message_bin = hex2bin($message); + $hash = keccak::hash($message_bin, 256); + + return $hash; + } + + /* + * @return string A hex encoded string of 32 random bytes + * + */ + public function gen_new_hex_seed() + { + $bytes = random_bytes(32); + return bin2hex($bytes); + } + + public function sc_reduce($input) + { + $integer = $this->ed25519->decodeint(hex2bin($input)); + + $modulo = bcmod($integer , $this->ed25519->l); + + $result = bin2hex($this->ed25519->encodeint($modulo)); + return $result; + } + + /* + * Hs in the cryptonote white paper + * + * @param string Hex encoded data to hash + * + * @return string A 32 byte encoded integer + */ + public function hash_to_scalar($data) + { + $hash = $this->keccak_256($data); + $scalar = $this->sc_reduce($hash); + return $scalar; + } + + /* + * Derive a deterministic private view key from a private spend key + * @param string A private spend key represented as a 32 byte hex string + * + * @return string A deterministic private view key represented as a 32 byte hex string + */ + public function derive_viewKey($spendKey) + { + return $this->hash_to_scalar($spendKey); + } + + /* + * Generate a pair of random private keys + * + * @param string A hex string to be used as a seed (this should be random) + * + * @return array An array containing a private spend key and a deterministic view key + */ + public function gen_private_keys($seed) + { + $spendKey = $this->sc_reduce($seed); + $viewKey = $this->derive_viewKey($spendKey); + $result = array("spendKey" => $spendKey, + "viewKey" => $viewKey); + + return $result; + } + + /* + * Get a public key from a private key on the ed25519 curve + * + * @param string a 32 byte hex encoded private key + * + * @return string a 32 byte hex encoding of a point on the curve to be used as a public key + */ + public function pk_from_sk($privKey) + { + $keyInt = $this->ed25519->decodeint(hex2bin($privKey)); + $aG = $this->ed25519->scalarmult_base($keyInt); + return bin2hex($this->ed25519->encodepoint($aG)); + } + + /* + * Generate key derivation + * + * @param string a 32 byte hex encoding of a point on the ed25519 curve used as a public key + * @param string a 32 byte hex encoded private key + * + * @return string The hex encoded key derivation + */ + public function gen_key_derivation($public, $private) + { + $point = $this->ed25519->scalarmult($this->ed25519->decodepoint(hex2bin($public)), $this->ed25519->decodeint(hex2bin($private))); + $res = $this->ed25519->scalarmult($point, 8); + return bin2hex($this->ed25519->encodepoint($res)); + } + + public function derivation_to_scalar($der, $index) + { + $encoded = $this->varint->encode_varint($index); + $data = $der . $encoded; + return $this->hash_to_scalar($data); + } + + // this is a one way function used for both encrypting and decrypting 8 byte payment IDs + public function stealth_payment_id($payment_id, $tx_pub_key, $viewkey) + { + if(strlen($payment_id) != 16) + { + throw new Exception("Error: Incorrect payment ID size. Should be 8 bytes"); + } + $der = $this->gen_key_derivation($tx_pub_key, $viewkey); + $data = $der . '8d'; + $hash = $this->keccak_256($data); + $key = substr($hash, 0, 16); + $result = bin2hex(pack('H*',$payment_id) ^ pack('H*',$key)); + return $result; + } + + // takes transaction extra field as hex string and returns transaction public key 'R' as hex string + public function txpub_from_extra($extra) + { + $parsed = array_map("hexdec", str_split($extra, 2)); + + if($parsed[0] == 1) + { + return substr($extra, 2, 64); + } + + if($parsed[0] == 2) + { + if($parsed[0] == 2 || $parsed[2] == 1) + { + //$offset = (($parsed[1] + 2) *2) + 2; + return substr($extra, (($parsed[1] + 2) *2) + 2, 64); + } + } + } + + public function derive_public_key($der, $index, $pub) + { + $scalar = $this->derivation_to_scalar($der, $index); + $sG = $this->ed25519->scalarmult_base($this->ed25519->decodeint(hex2bin($scalar))); + $pubPoint = $this->ed25519->decodepoint(hex2bin($pub)); + $key = $this->ed25519->encodepoint($this->ed25519->edwards($pubPoint, $sG)); + return bin2hex($key); + } + + /* + * Perform the calculation P = P' as described in the cryptonote whitepaper + * + * @param string 32 byte transaction public key R + * @param string 32 byte receiver private view key a + * @param string 32 byte receiver public spend key B + * @param int output index + * @param string output you want to check against P + */ + public function is_output_mine($txPublic, $privViewkey, $publicSpendkey, $index, $P) + { + $derivation = $this->gen_key_derivation($txPublic, $privViewkey); + $Pprime = $this->derive_public_key($derivation, $index, $publicSpendkey); + + if($P == $Pprime) + { + return true; + } + else + return false; + } + + /* + * Create a valid base58 encoded Monero address from public keys + * + * @param string Public spend key + * @param string Public view key + * + * @return string Base58 encoded Monero address + */ + public function encode_address($pSpendKey, $pViewKey) + { + $data = $this->network_prefixes["STANDARD"] . $pSpendKey . $pViewKey; + $checksum = $this->keccak_256($data); + $encoded = $this->base58->encode($data . substr($checksum, 0, 8)); + + return $encoded; + } + + public function verify_checksum($address) + { + $decoded = $this->base58->decode($address); + $checksum = substr($decoded, -8); + $checksum_hash = $this->keccak_256(substr($decoded, 0, -8)); + $calculated = substr($checksum_hash, 0, 8); + return $checksum === $calculated; + } + + /* + * Decode a base58 encoded Monero address + * + * @param string A base58 encoded Monero address + * + * @return array An array containing the Address network byte, public spend key, and public view key + */ + public function decode_address($address) + { + $decoded = $this->base58->decode($address); + + if(!$this->verify_checksum($address)){ + throw new Exception("Error: invalid checksum"); + } + + $network_byte = substr($decoded, 0, 2); + $public_spendKey = substr($decoded, 2, 64); + $public_viewKey = substr($decoded, 66, 64); + + $result = array("networkByte" => $network_byte, + "spendKey" => $public_spendKey, + "viewKey" => $public_viewKey); + return $result; + } + + /* + * Get an integrated address from public keys and a payment id + * + * @param string A 32 byte hex encoded public spend key + * @param string A 32 byte hex encoded public view key + * @param string An 8 byte hex string to use as a payment id + */ + public function integrated_addr_from_keys($public_spendkey, $public_viewkey, $payment_id) + { + $data = $this->network_prefixes["INTEGRATED"].$public_spendkey.$public_viewkey.$payment_id; + $checksum = substr($this->keccak_256($data), 0, 8); + $result = $this->base58->encode($data.$checksum); + return $result; + } + + /* + * Generate a Monero address from seed + * + * @param string Hex string to use as seed + * + * @return string A base58 encoded Monero address + */ + public function address_from_seed($hex_seed) + { + $private_keys = $this->gen_private_keys($hex_seed); + $private_viewKey = $private_keys["viewKey"]; + $private_spendKey = $private_keys["spendKey"]; + + $public_spendKey = $this->pk_from_sk($private_spendKey); + $public_viewKey = $this->pk_from_sk($private_viewKey); + + $address = $this->encode_address($public_spendKey, $public_viewKey); + return $address; + } + + // m = Hs(a || i) + public function generate_subaddr_secret_key($major_index, $minor_index, $sec_key) + { + $prefix = "5375624164647200"; + $index = pack("II", $major_index, $minor_index); + return $this->hash_to_scalar($prefix . $sec_key . bin2hex($index)); + } + + public function generate_subaddress_spend_public_key($spend_public_key, $subaddr_secret_key) + { + $mInt = $this->ed25519->decodeint(hex2bin($subaddr_secret_key)); + $mG = $this->ed25519->scalarmult_base($mInt); + $D = $this->ed25519->edwards($this->ed25519->decodepoint(hex2bin($spend_public_key)), $mG); + return bin2hex($this->ed25519->encodepoint($D)); + } + + public function generate_subaddr_view_public_key($subaddr_spend_public_key, $view_secret_key) + { + $point = $this->ed25519->scalarmult($this->ed25519->decodepoint(hex2bin($subaddr_spend_public_key)), $this->ed25519->decodeint(hex2bin($view_secret_key))); + return bin2hex($this->ed25519->encodepoint($point)); + } + + public function generate_subaddress($major_index, $minor_index, $view_secret_key, $spend_public_key) + { + $subaddr_secret_key = $this->generate_subaddr_secret_key($major_index, $minor_index, $view_secret_key); + $subaddr_public_spend_key = $this->generate_subaddress_spend_public_key($spend_public_key, $subaddr_secret_key); + $subaddr_public_view_key = $this->generate_subaddr_view_public_key($subaddr_public_spend_key, $view_secret_key); + $data = $this->network_prefixes["SUBADDRESS"] . $subaddr_public_spend_key . $subaddr_public_view_key; + $checksum = $this->keccak_256($data); + $encoded = $this->base58->encode($data . substr($checksum, 0, 8)); + return $encoded; + } + + public function deserialize_block_header($block) + { + $data = str_split($block, 2); + + $major_version = $this->varint->decode_varint($data); + $data = $this->varint->pop_varint($data); + + $minor_version = $this->varint->decode_varint($data); + $data = $this->varint->pop_varint($data); + + $timestamp = $this->varint->decode_varint($data); + $data = $this->varint->pop_varint($data); + + $nonce = $this->varint->decode_varint($data); + $data = $this->varint->pop_varint($data); + + return array("major_version" => $major_version, + "minor_version" => $minor_version, + "timestamp" => $timestamp, + "nonce" => $nonce); + } + + } diff --git a/vendor/monero-integrations/monerophp/src/Varint.php b/vendor/monero-integrations/monerophp/src/Varint.php new file mode 100644 index 0000000..36ffb1b --- /dev/null +++ b/vendor/monero-integrations/monerophp/src/Varint.php @@ -0,0 +1,100 @@ + 0) + { + $encodedBytes[] = 0x80 | ($data & 0x7f); + $data >>= 7; + } + + $encodedBytes[count($encodedBytes)-1] &= 0x7f; + $bytes = call_user_func_array('pack', array_merge(array('C*'), $encodedBytes));; + return bin2hex($bytes); + } + + // https://github.com/monero-project/research-lab/blob/master/source-code/StringCT-java/src/how/monero/hodl/util/VarInt.java + public function decode_varint($data) + { + $result = 0; + $c = 0; + $pos = 0; + + while (true) + { + $isLastByteInVarInt = true; + $i = hexdec($data[$pos]); + if ($i >= 128) + { + $isLastByteInVarInt = false; + $i -= 128; + } + $result += ($i * (pow(128, $c))); + $c += 1; + $pos += 1; + + if ($isLastByteInVarInt) + break; + } + return $result; + } + + public function pop_varint($data) + { + $result = 0; + $c = 0; + $pos = 0; + + while (true) + { + $isLastByteInVarInt = true; + $i = hexdec($data[$pos]); + if ($i >= 128) + { + $isLastByteInVarInt = false; + $i -= 128; + } + $result += ($i * (pow(128, $c))); + $c += 1; + $pos += 1; + + if ($isLastByteInVarInt) + break; + } + for ($x = 0; $x < $pos; $x++) + array_shift($data); + return $data; + } + } diff --git a/vendor/monero-integrations/monerophp/src/base58.php b/vendor/monero-integrations/monerophp/src/base58.php new file mode 100644 index 0000000..1f0ef53 --- /dev/null +++ b/vendor/monero-integrations/monerophp/src/base58.php @@ -0,0 +1,395 @@ + (https://github.com/monero-integrations) + * @copyright 2018 + * @license MIT + * + * ============================================================================ + * + * // Initialize class + * $base58 = new base58(); + * + * // Encode a hexadecimal (base16) string as base58 + * $encoded = $base58->encode('0137F8F06C971B168745F562AA107B4D172F336271BC0F9D3B510C14D3460DFB27D8CEBE561E73AC1E11833D5EA40200EB3C82E9C66ACAF1AB1A6BB53C40537C0B7A22160B0E'); + * + * // Decode + * $decoded = $base58->decode('479cG5opa54beQWSyqNoWw5tna9sHUNmMTtiFqLPaUhDevpJ2YLwXAggSx5ePdeFrYF8cdbmVRSmp1Kn3t4Y9kFu7rZ7pFw'); + * + */ + +namespace MoneroIntegrations\MoneroPhp; +use Exception; + +class base58 +{ + static $alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; + static $encoded_block_sizes = [0, 2, 3, 5, 6, 7, 9, 10, 11]; + static $full_block_size = 8; + static $full_encoded_block_size = 11; + + /** + * + * Convert a hexadecimal string to a binary array + * + * @param string $hex A hexadecimal string to convert to a binary array + * + * @return array + * + */ + private function hex_to_bin($hex) + { + if (!is_string($hex)) { + throw new Exception('base58->hex_to_bin(): Invalid input type (must be a string)'); + } + if (strlen($hex) % 2 != 0) { + throw new Exception('base58->hex_to_bin(): Invalid input length (must be even)'); + } + + $res = array_fill(0, strlen($hex) / 2, 0); + for ($i = 0; $i < strlen($hex) / 2; $i++) { + $res[$i] = intval(substr($hex, $i * 2, $i * 2 + 2 - $i * 2), 16); + } + return $res; + } + + /** + * + * Convert a binary array to a hexadecimal string + * + * @param array $bin A binary array to convert to a hexadecimal string + * + * @return string + * + */ + private function bin_to_hex($bin) + { + if (!is_array($bin)) { + throw new Exception('base58->bin_to_hex(): Invalid input type (must be an array)'); + } + + $res = []; + for ($i = 0, $iMax = count($bin); $i < $iMax; $i++) { + $res[] = substr('0'.dechex($bin[$i]), -2); + } + return join($res); + } + + /** + * + * Convert a string to a binary array + * + * @param string $str A string to convert to a binary array + * + * @return array + * + */ + private function str_to_bin($str) + { + if (!is_string($str)) { + throw new Exception('base58->str_to_bin(): Invalid input type (must be a string)'); + } + + $res = array_fill(0, strlen($str), 0); + for ($i = 0, $iMax = strlen($str); $i < $iMax; $i++) { + $res[$i] = ord($str[$i]); + } + return $res; + } + + /** + * + * Convert a binary array to a string + * + * @param array $bin A binary array to convert to a string + * + * @return string + * + */ + private function bin_to_str($bin) + { + if (!is_array($bin)) { + throw new Exception('base58->bin_to_str(): Invalid input type (must be an array)'); + } + + $res = array_fill(0, count($bin), 0); + for ($i = 0, $iMax = count($bin); $i < $iMax; $i++) { + $res[$i] = chr($bin[$i]); + } + return preg_replace('/[[:^print:]]/', '', join($res)); // preg_replace necessary to strip errant non-ASCII characters eg. '' + } + + /** + * + * Convert a UInt8BE (one unsigned big endian byte) array to UInt64 + * + * @param array $data A UInt8BE array to convert to UInt64 + * + * @return number + * + */ + private function uint8_be_to_64($data) + { + if (!is_array($data)) { + throw new Exception ('base58->uint8_be_to_64(): Invalid input type (must be an array)'); + } + + $res = 0; + $i = 0; + switch (9 - count($data)) { + case 1: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 2: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 3: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 4: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 5: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 6: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 7: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + case 8: + $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); + break; + default: + throw new Exception('base58->uint8_be_to_64: Invalid input length (1 <= count($data) <= 8)'); + } + return $res; + } + + /** + * + * Convert a UInt64 (unsigned 64 bit integer) to a UInt8BE array + * + * @param number $num A UInt64 number to convert to a UInt8BE array + * @param integer $size Size of array to return + * + * @return array + * + */ + private function uint64_to_8_be($num, $size) + { + if (!is_numeric($num)) { + throw new Exception ('base58->uint64_to_8_be(): Invalid input type ($num must be a number)'); + } + if (!is_int($size)) { + throw new Exception ('base58->uint64_to_8_be(): Invalid input type ($size must be an integer)'); + } + if ($size < 1 || $size > 8) { + throw new Exception ('base58->uint64_to_8_be(): Invalid size (1 <= $size <= 8)'); + } + + $res = array_fill(0, $size, 0); + for ($i = $size - 1; $i >= 0; $i--) { + $res[$i] = bcmod($num, bcpow(2, 8)); + $num = bcdiv($num, bcpow(2, 8)); + } + return $res; + } + + /** + * + * Convert a hexadecimal (Base16) array to a Base58 string + * + * @param array $data + * @param array $buf + * @param number $index + * + * @return array + * + */ + private function encode_block($data, $buf, $index) + { + if (!is_array($data)) { + throw new Exception('base58->encode_block(): Invalid input type ($data must be an array)'); + } + if (!is_array($buf)) { + throw new Exception('base58->encode_block(): Invalid input type ($buf must be an array)'); + } + if (!is_int($index) && !is_float($index)) { + throw new Exception('base58->encode_block(): Invalid input type ($index must be a number)'); + } + if (count($data) < 1 or count($data) > self::$full_encoded_block_size) { + throw new Exception('base58->encode_block(): Invalid input length (1 <= count($data) <= 8)'); + } + + $num = self::uint8_be_to_64($data); + $i = self::$encoded_block_sizes[count($data)] - 1; + while ($num > 0) { + $remainder = bcmod($num, 58); + $num = bcdiv($num, 58); + $buf[$index + $i] = ord(self::$alphabet[$remainder]); + $i--; + } + return $buf; + } + + /** + * + * Encode a hexadecimal (Base16) string to Base58 + * + * @param string $hex A hexadecimal (Base16) string to convert to Base58 + * + * @return string + * + */ + public function encode($hex) + { + if (!is_string($hex)) { + throw new Exception ('base58->encode(): Invalid input type (must be a string)'); + } + + $data = self::hex_to_bin($hex); + if (count($data) == 0) { + return ''; + } + + $full_block_count = floor(count($data) / self::$full_block_size); + $last_block_size = count($data) % self::$full_block_size; + $res_size = $full_block_count * self::$full_encoded_block_size + self::$encoded_block_sizes[$last_block_size]; + + $res = array_fill(0, $res_size, ord(self::$alphabet[0])); + + for ($i = 0; $i < $full_block_count; $i++) { + $res = self::encode_block(array_slice($data, $i * self::$full_block_size, ($i * self::$full_block_size + self::$full_block_size) - ($i * self::$full_block_size)), $res, $i * self::$full_encoded_block_size); + } + + if ($last_block_size > 0) { + $res = self::encode_block(array_slice($data, $full_block_count * self::$full_block_size, $full_block_count * self::$full_block_size + $last_block_size), $res, $full_block_count * self::$full_encoded_block_size); + } + + return self::bin_to_str($res); + } + + /** + * + * Convert a Base58 input to hexadecimal (Base16) + * + * @param array $data + * @param array $buf + * @param integer $index + * + * @return array + * + */ + private function decode_block($data, $buf, $index) + { + if (!is_array($data)) { + throw new Exception('base58->decode_block(): Invalid input type ($data must be an array)'); + } + if (!is_array($buf)) { + throw new Exception('base58->decode_block(): Invalid input type ($buf must be an array)'); + } + if (!is_int($index) && !is_float($index)) { + throw new Exception('base58->decode_block(): Invalid input type ($index must be a number)'); + } + + $res_size = self::index_of(self::$encoded_block_sizes, count($data)); + if ($res_size <= 0) { + throw new Exception('base58->decode_block(): Invalid input length ($data must be a value from base58::$encoded_block_sizes)'); + } + + $res_num = 0; + $order = 1; + for ($i = count($data) - 1; $i >= 0; $i--) { + $digit = strpos(self::$alphabet, chr($data[$i])); + if ($digit < 0) { + throw new Exception("base58->decode_block(): Invalid character ($digit \"{$digit}\" not found in base58::\$alphabet)"); + } + + $product = bcadd(bcmul($order, $digit), $res_num); + if ($product > bcpow(2, 64)) { + throw new Exception('base58->decode_block(): Integer overflow ($product exceeds the maximum 64bit integer)'); + } + + $res_num = $product; + $order = bcmul($order, 58); + } + if ($res_size < self::$full_block_size && bcpow(2, 8 * $res_size) <= 0) { + throw new Exception('base58->decode_block(): Integer overflow (bcpow(2, 8 * $res_size) exceeds the maximum 64bit integer)'); + } + + $tmp_buf = self::uint64_to_8_be($res_num, $res_size); + for ($i = 0, $iMax = count($tmp_buf); $i < $iMax; $i++) { + $buf[$i + $index] = $tmp_buf[$i]; + } + return $buf; + } + + /** + * + * Decode a Base58 string to hexadecimal (Base16) + * + * @param string $hex A Base58 string to convert to hexadecimal (Base16) + * + * @return string + * + */ + public function decode($enc) + { + if (!is_string($enc)) { + throw new Exception ('base58->decode(): Invalid input type (must be a string)'); + } + + $enc = self::str_to_bin($enc); + if (count($enc) == 0) { + return ''; + } + $full_block_count = floor(bcdiv(count($enc), self::$full_encoded_block_size)); + $last_block_size = bcmod(count($enc), self::$full_encoded_block_size); + $last_block_decoded_size = self::index_of(self::$encoded_block_sizes, $last_block_size); + + $data_size = $full_block_count * self::$full_block_size + $last_block_decoded_size; + + if ($data_size == -1) { + return ''; + } + + $data = array_fill(0, $data_size, 0); + for ($i = 0; $i <= $full_block_count; $i++) { + $data = self::decode_block(array_slice($enc, $i * self::$full_encoded_block_size, ($i * self::$full_encoded_block_size + self::$full_encoded_block_size) - ($i * self::$full_encoded_block_size)), $data, $i * self::$full_block_size); + } + + if ($last_block_size > 0) { + $data = self::decode_block(array_slice($enc, $full_block_count * self::$full_encoded_block_size, $full_block_count * self::$full_encoded_block_size + $last_block_size), $data, $full_block_count * self::$full_block_size); + } + + return self::bin_to_hex($data); + } + + /** + * + * Search an array for a value + * Source: https://stackoverflow.com/a/30994678 + * + * @param array $haystack An array to search + * @param string $needle A string to search for + *) + * @return number The index of the element found (or -1 for no match) + * + */ + private function index_of($haystack, $needle) + { + if (!is_array($haystack)) { + throw new Exception ('base58->decode(): Invalid input type ($haystack must be an array)'); + } + // if (gettype($needle) != 'string') { + // throw new Exception ('base58->decode(): Invalid input type ($needle must be a string)'); + // } + + foreach ($haystack as $key => $value) if ($value === $needle) return $key; + return -1; + } +} diff --git a/vendor/monero-integrations/monerophp/src/daemonRPC.php b/vendor/monero-integrations/monerophp/src/daemonRPC.php new file mode 100644 index 0000000..0ec7b2c --- /dev/null +++ b/vendor/monero-integrations/monerophp/src/daemonRPC.php @@ -0,0 +1,678 @@ + (https://github.com/cryptochangements34) + * Serhack [Monero Integrations] (https://serhack.me) + * TheKoziTwo [xmr-integration] + * Andrew LeCody [EasyBitcoin-PHP] + * Kacper Rowinski [jsonRPCClient] + * + * @author Monero Integrations Team (https://github.com/monero-integrations) + * @copyright 2018 + * @license MIT + * + * ============================================================================ + * + * // See example.php for more examples + * + * // Initialize class + * $daemonRPC = new daemonRPC(); + * + * // Examples: + * $height = $daemonRPC->getblockcount(); + * $block = $daemonRPC->getblock_by_height(1); + * + */ + +namespace MoneroIntegrations\MoneroPhp; +use Exception; + +class daemonRPC +{ + private $client; + + private $protocol; + private $host; + private $port; + private $url; + private $user; + private $password; + private $check_SSL; + + /** + * + * Start a connection with the Monero daemon (monerod) + * + * @param string $host Monero daemon IP hostname (optional) + * @param int $port Monero daemon port (optional) + * @param string $protocol Monero daemon protocol (eg. 'http') (optional) + * @param string $user Monero daemon RPC username (optional) + * @param string $password Monero daemon RPC passphrase (optional) + * + */ + function __construct($host = '127.0.0.1', $port = 18081, $SSL = true, $user = null, $password = null) + { + if (is_array($host)) { // Parameters passed in as object/dictionary + $params = $host; + + if (array_key_exists('host', $params)) { + $host = $params['host']; + } else { + $host = '127.0.0.1'; + } + if (array_key_exists('port', $params)) { + $port = $params['port']; + } + if (array_key_exists('protocol', $params)) { + $protocol = $params['protocol']; + } + if (array_key_exists('user', $params)) { + $user = $params['user']; + } + if (array_key_exists('password', $params)) { + $password = $params['password']; + } + } + + if ($SSL) { + $protocol = 'https'; + } else { + $protocol = 'http'; + } + + $this->host = $host; + $this->port = $port; + $this->protocol = $protocol; + $this->user = $user; + $this->password = $password; + $this->check_SSL = $SSL; + + $this->url = $protocol.'://'.$host.':'.$port.'/'; + $this->client = new jsonRPCClient($this->url, $this->user, $this->password, $this->check_SSL); + } + + /** + * + * Execute command via jsonRPCClient + * + * @param string $method RPC method to call + * @param string $params Parameters to pass (optional) + * @param string $path Path of API (by default json_rpc) + * + * @return string Call result + * + */ + protected function _run($method, $params = null, $path = 'json_rpc') + { + return $this->client->_run($method, $params, $path); + } + + /** + * + * Look up how many blocks are in the longest chain known to the node + * + * @param none + * + * @return object Example: { + * "count": 993163, + * "status": "OK" + * } + * + */ + public function getblockcount() + { + return $this->_run('getblockcount'); + } + + /** + * + * Look up a block's hash by its height + * + * @param number $height Height of block to look up + * + * @return string Example: 'e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6' + * + */ + public function on_getblockhash($height) + { + $params = array($height); + + return $this->_run('on_getblockhash', $params); + } + + /** + * + * Construct a block template that can be mined upon + * + * @param string $wallet_address Address of wallet to receive coinbase transactions if block is successfully mined + * @param int $reserve_size Reserve size + * + * @return object Example: { + * "blocktemplate_blob": "01029af88cb70568b84a11dc9406ace9e635918ca03b008f7728b9726b327c1b482a98d81ed83000000000018bd03c01ffcfcf3c0493d7cec7020278dfc296544f139394e5e045fcda1ba2cca5b69b39c9ddc90b7e0de859fdebdc80e8eda1ba01029c5d518ce3cc4de26364059eadc8220a3f52edabdaf025a9bff4eec8b6b50e3d8080dd9da417021e642d07a8c33fbe497054cfea9c760ab4068d31532ff0fbb543a7856a9b78ee80c0f9decfae01023ef3a7182cb0c260732e7828606052a0645d3686d7a03ce3da091dbb2b75e5955f01ad2af83bce0d823bf3dbbed01ab219250eb36098c62cbb6aa2976936848bae53023c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f12d7c87346d6b84e17680082d9b4a1d84e36dd01bd2c7f3b3893478a8d88fb3", + * "difficulty": 982540729, + * "height": 993231, + * "prev_hash": "68b84a11dc9406ace9e635918ca03b008f7728b9726b327c1b482a98d81ed830", + * "reserved_offset": 246, + * "status": "OK" + * } + * + */ + public function getblocktemplate($wallet_address, $reserve_size) + { + $params = array('wallet_address' => $wallet_address, 'reserve_size' => $reserve_size); + + return $this->client->_run('getblocktemplate', $params, null); + } + + /** + * + * Submit a mined block to the network + * + * @param string $block Block blob + * + * @return // TODO: example + * + */ + public function submitblock($block) + { + return $this->_run('submitblock', $block); + } + + /** + * + * Look up the block header of the latest block in the longest chain known to the node + * + * @param none + * + * @return object Example: { + * "block_header": { + * "depth": 0, + * "difficulty": 746963928, + * "hash": "ac0f1e226268d45c99a16202fdcb730d8f7b36ea5e5b4a565b1ba1a8fc252eb0", + * "height": 990793, + * "major_version": 1, + * "minor_version": 1, + * "nonce": 1550, + * "orphan_status": false, + * "prev_hash": "386575e3b0b004ed8d458dbd31bff0fe37b280339937f971e06df33f8589b75c", + * "reward": 6856609225169, + * "timestamp": 1457589942 + * }, + * "status": "OK" + * } + * + */ + public function getlastblockheader() + { + return $this->_run('getlastblockheader'); + } + + /** + * + * Look up a block header from a block hash + * + * @param string $hash The block's SHA256 hash + * + * @return object Example: { + * "block_header": { + * "depth": 78376, + * "difficulty": 815625611, + * "hash": "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6", + * "height": 912345, + * "major_version": 1, + * "minor_version": 2, + * "nonce": 1646, + * "orphan_status": false, + * "prev_hash": "b61c58b2e0be53fad5ef9d9731a55e8a81d972b8d90ed07c04fd37ca6403ff78", + * "reward": 7388968946286, + * "timestamp": 1452793716 + * }, + * "status": "OK" + * } + * + */ + public function getblockheaderbyhash($hash) + { + $params = array('hash' => $hash); + + return $this->_run('getblockheaderbyhash', $params); + } + + /** + * + * Look up a block header by height + * + * @param int $height Height of block + * + * @return object Example: { + * "block_header": { + * "depth": 78376, + * "difficulty": 815625611, + * "hash": "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6", + * "height": 912345, + * "major_version": 1, + * "minor_version": 2, + * "nonce": 1646, + * "orphan_status": false, + * "prev_hash": "b61c58b2e0be53fad5ef9d9731a55e8a81d972b8d90ed07c04fd37ca6403ff78", + * "reward": 7388968946286, + * "timestamp": 1452793716 + * }, + * "status": "OK" + * } + * + */ + public function getblockheaderbyheight($height) + { + return $this->_run('getblockheaderbyheight', $height); + } + + /** + * + * Look up block information by SHA256 hash + * + * @param string $hash SHA256 hash of block + * + * @return object Example: { + * "blob": "...", + * "block_header": { + * "depth": 12, + * "difficulty": 964985344, + * "hash": "510ee3c4e14330a7b96e883c323a60ebd1b5556ac1262d0bc03c24a3b785516f", + * "height": 993056, + * "major_version": 1, + * "minor_version": 2, + * "nonce": 2036, + * "orphan_status": false, + * "prev_hash": "0ea4af6547c05c965afc8df6d31509ff3105dc7ae6b10172521d77e09711fd6d", + * "reward": 6932043647005, + * "timestamp": 1457720227 + * }, + * "json": "...", + * "status": "OK" + * } + * + */ + public function getblock_by_hash($hash) + { + $params = array('hash' => $hash); + + return $this->_run('getblock', $params); + } + + /** + * + * Look up block information by height + * + * @param int $height Height of block + * + * @return object Example: { + * "blob": "...", + * "block_header": { + * "depth": 80694, + * "difficulty": 815625611, + * "hash": "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6", + * "height": 912345, + * "major_version": 1, + * "minor_version": 2, + * "nonce": 1646, + * "orphan_status": false, + * "prev_hash": "b61c58b2e0be53fad5ef9d9731a55e8a81d972b8d90ed07c04fd37ca6403ff78", + * "reward": 7388968946286, + * "timestamp": 1452793716 + * }, + * "json": "...", + * "status": "OK" + * } + * + */ + public function getblock_by_height($height) + { + $params = array('height' => $height); + + return $this->_run('getblock', $params); + } + + /** + * + * Look up incoming and outgoing connections to your node + * + * @param none + * + * @return object Example: { + * "connections": [{ + * "avg_download": 0, + * "avg_upload": 0, + * "current_download": 0, + * "current_upload": 0, + * "incoming": false, + * "ip": "76.173.170.133", + * "live_time": 1865, + * "local_ip": false, + * "localhost": false, + * "peer_id": "3bfe29d6b1aa7c4c", + * "port": "18080", + * "recv_count": 116396, + * "recv_idle_time": 23, + * "send_count": 176893, + * "send_idle_time": 1457726610, + * "state": "state_normal" + * },{ + * .. + * }], + * "status": "OK" + * } + * + */ + public function get_connections() + { + return $this->_run('get_connections'); + } + + /** + * + * Look up general information about the state of your node and the network + * + * @param none + * + * @return object Example: { + * "alt_blocks_count": 5, + * "difficulty": 972165250, + * "grey_peerlist_size": 2280, + * "height": 993145, + * "incoming_connections_count": 0, + * "outgoing_connections_count": 8, + * "status": "OK", + * "target": 60, + * "target_height": 993137, + * "testnet": false, + * "top_block_hash": "", + * "tx_count": 564287, + * "tx_pool_size": 45, + * "white_peerlist_size": 529 + * } + * + */ + public function get_info() + { + return $this->_run('get_info'); + } + + /** + * + * Look up information regarding hard fork voting and readiness + * + * @param none + * + * @return object Example: { + * "alt_blocks_count": 0, + * "block_size_limit": 600000, + * "block_size_median": 85, + * "bootstrap_daemon_address": ?, + * "cumulative_difficulty": 40859323048, + * "difficulty": 57406, + * "free_space": 888592449536, + * "grey_peerlist_size": 526, + * "height": 1066107, + * "height_without_bootstrap": 1066107, + * "incoming_connections_count": 1, + * "offline": ?, + * "outgoing_connections_count": 1, + * "rpc_connections_count": 1, + * "start_time": 1519963719, + * "status": OK, + * "target": 120, + * "target_height": 1066073, + * "testnet": 1, + * "top_block_hash": e438aae56de8e5e5c8e0d230167fcb58bc8dde09e369ff7689a4af146040a20e, + * "tx_count": 52632, + * "tx_pool_size": 0, + * "untrusted": ?, + * "was_bootstrap_ever_used: ?, + * "white_peerlist_size": 5 + * } + * + */ + public function hardfork_info() + { + return $this->_run('hard_fork_info'); + } + + /** + * + * Ban another node by IP + * + * @param array $bans Array of IP addresses to ban + * + * @return object Example: { + * "status": "OK" + * } + * + */ + public function set_bans($bans) + { + if (is_string($bans)) { + $bans = array($bans); + } + $params = array('bans' => $bans); + + return $this->_run('set_bans', $params); + } + + /** + * + * Alias of set_bans + * } + * + */ + public function setbans($bans) + { + return $this->set_bans($bans); + } + + /** + * + * Get list of banned IPs + * + * @param none + * + * @return object Example: { + * "bans": [{ + * "ip": 838969536, + * "seconds": 1457748792 + * }], + * "status": "OK" + * } + * + */ + public function get_bans() + { + return $this->_run('get_bans'); + } + + /** + * + * Alias of get_bans + * + */ + public function getbans() + { + return $this->get_bans(); + } + + /** + * + *Flush Transaction Pool + * + * @param $txids - array + * Optional, list of transactions IDs to flush from pool (all tx ids flushed if empty). + * + * @return status - string; General RPC error code. "OK" means everything looks good. + */ + public function flush_txpool($txids) + { + return $this->_run('flush_txpool', $txids); + } + + /** + * Alias of flush_txpool + */ + public function flushtxpool($txids) + { + return $this->flush_txpool($txids); + } + + /** + * + * Get height + * + */ + public function get_height() + { + return $this->_run(null, null, 'getheight'); + } + + /** + * + * Get transactions + * + */ + public function get_transactions($txs_hashes = NULL) + { + $params = array('txs_hashes' => $txs_hashes, 'decode_as_json' => true); + return $this->_run(null, null, 'gettransactions'); + } + + + public function get_alt_blocks_hashes() + { + return $this->_run(null, null, 'get_alt_blocks_hashes'); + } + + public function is_key_image_spent($key_images) + { + if (is_string($key_images)) { + $key_images = array($key_images); + } + if(!is_array($key_images)){ + throw new Exception('Error: key images must be an array or a string'); + } + $params = array('key_images' => $key_images); + return $this->_run(null, $params, 'is_key_image_spent'); + } + + public function send_raw_transaction($tx_as_hex, $do_not_relay = false, $do_sanity_checks = true) + { + $params = array('tx_as_hex' => $tx_as_hex, 'do_not_relay' => $do_not_relay, 'do_sanity_checks' => $do_sanity_checks); + return $this->_run(null, $params, 'send_raw_transaction'); + } + + public function start_mining($background_mining, $ignore_battery = false, $miner_address, $threads_count = 1) + { + if($threads_count < 0){ + throw new Exception('Error: threads_count must be a positive integer'); + } + $params = array('do_background_mining' => $background_mining, 'ignore_battery' => $ignore_battery, 'miner_address' => $miner_address, 'threads_count' => $threads_count); + return $this->_run(null, $params, 'start_mining'); + } + + public function stop_mining() + { + return $this->_run(null, null, 'stop_mining'); + } + + public function mining_status() + { + return $this->_run(null, null, 'mining_status'); + } + + public function save_bc() + { + return $this->_run(null, null, 'save_bc'); + } + + public function get_peer_list($public_only = true) + { + $params = array('public_only' => $public_only); + return $this->_run(null, $params, 'get_peer_list'); + } + + public function set_log_hash_rate($visible = true) + { + $params = array('visible' => $visible); + return $this->_run(null, $params, 'set_log_hash_rate'); + } + + public function set_log_level($log_level = 0) + { + if(!is_int($log_level)){ + throw new Exception('Error: log_level must be an integer'); + } + $params = array('level' => $log_level); + return $this->_run(null, $params, 'set_log_level'); + } + + public function set_log_categories($category) + { + $params = array('categories' => $category); + return $this->_run(null, $params, 'set_log_categories'); + } + + public function get_transaction_pool() + { + return $this->_run(null, null, 'get_transaction_pool'); + } + + public function get_transaction_pool_stats(){ + return $this->_run(null, null, 'get_transaction_pool_stats'); + } + + public function stop_daemon() + { + return $this->_run(null, null, 'stop_daemon'); + } + + public function get_limit() + { + return $this->_run(null, null, 'get_limit'); + } + + public function set_limit($limit_down, $limit_up) + { + $params = array('limit_down' => $limit_down, 'limit_up' => $limit_up); + return $this->_run(null, $params, 'set_limit'); + } + + public function out_peers() + { + return $this->_run(null, null, 'out_peers'); + } + + public function in_peers() + { + return $this->_run(null, null, 'in_peers'); + } + + public function start_save_graph() + { + return $this->_run(null, null, 'start_save_graph'); + } + + public function stop_save_graph() + { + return $this->_run(null, null, 'stop_save_graph'); + } + + public function get_outs($outputs) + { + $params = array('outputs' => $outputs); + return $this->_run(null, null, 'get_outs'); + } + +} diff --git a/vendor/monero-integrations/monerophp/src/ed25519.php b/vendor/monero-integrations/monerophp/src/ed25519.php new file mode 100644 index 0000000..df8c8e3 --- /dev/null +++ b/vendor/monero-integrations/monerophp/src/ed25519.php @@ -0,0 +1,502 @@ +b = 256; + $this->q = "57896044618658097711785492504343953926634992332820282019728792003956564819949"; //bcsub(bcpow(2, 255),19); + $this->l = "7237005577332262213973186563042994240857116359379907606001950938285454250989"; //bcadd(bcpow(2,252),27742317777372353535851937790883648493); + $this->d = "-4513249062541557337682894930092624173785641285191125241628941591882900924598840740"; //bcmul(-121665,$this->inv(121666)); + $this->I = "19681161376707505956807079304988542015446066515923890162744021073123829784752"; //$this->expmod(2, bcdiv((bcsub($this->q,1)),4),$this->q); + $this->By = "46316835694926478169428394003475163141307993866256225615783033603165251855960"; //bcmul(4,$this->inv(5)); + $this->Bx = "15112221349535400772501151409588531511454012693041857206046113283949847762202"; //$this->xrecover($this->By); + $this->B = array( + "15112221349535400772501151409588531511454012693041857206046113283949847762202", + "46316835694926478169428394003475163141307993866256225615783033603165251855960" + ); //array(bcmod($this->Bx,$this->q),bcmod($this->By,$this->q)); + + $this->gmp = extension_loaded('gmp'); + } + + public function H($m) + { + return hash('sha512', $m, true); + } + + //((n % M) + M) % M //python modulus craziness + public function pymod($x, $m) + { + if ($this->gmp) { + $mod = gmp_mod($x, $m); + if ($mod < 0) { + $mod = gmp_add($mod, $m); + } + } else { + $mod = bcmod($x, $m); + if ($mod < 0) { + $mod = bcadd($mod, $m); + } + } + + return $mod; + } + + public function expmod($b, $e, $m) + { + //if($e==0){return 1;} + if ($this->gmp) { + $t = gmp_powm($b, $e, $m); + if ($t < 0) { + $t = gmp_add($t, $m); + } + } else { + $t = bcpowmod($b, $e, $m); + if ($t[0] === '-') { + $t = bcadd($t, $m); + } + } + + return $t; + } + + public function inv($x) + { + if ($this->gmp) { + return $this->expmod($x, gmp_sub($this->q, 2), $this->q); + } else { + return $this->expmod($x, bcsub($this->q, 2), $this->q); + } + } + + public function xrecover($y) + { + if ($this->gmp) { + $y2 = gmp_pow($y, 2); + $xx = gmp_mul(gmp_sub($y2, 1), $this->inv(gmp_add(gmp_mul($this->d, $y2), 1))); + $x = $this->expmod($xx, gmp_div(gmp_add($this->q, 3), 8, 0), $this->q); + if ($this->pymod(gmp_sub(gmp_pow($x, 2), $xx), $this->q) != 0) { + $x = $this->pymod(gmp_mul($x, $this->I), $this->q); + } + if (substr($x, -1)%2 != 0) { + $x = gmp_sub($this->q, $x); + } + } else { + $y2 = bcpow($y, 2); + $xx = bcmul(bcsub($y2, 1), $this->inv(bcadd(bcmul($this->d, $y2), 1))); + $x = $this->expmod($xx, bcdiv(bcadd($this->q, 3), 8, 0), $this->q); + if ($this->pymod(bcsub(bcpow($x, 2), $xx), $this->q) != 0) { + $x = $this->pymod(bcmul($x, $this->I), $this->q); + } + if (substr($x, -1)%2 != 0) { + $x = bcsub($this->q, $x); + } + } + + return $x; + } + + public function edwards($P, $Q) + { + if ($this->gmp) { + list($x1, $y1) = $P; + list($x2, $y2) = $Q; + $xmul = gmp_mul($x1, $x2); + $ymul = gmp_mul($y1, $y2); + $com = gmp_mul($this->d, gmp_mul($xmul, $ymul)); + $x3 = gmp_mul(gmp_add(gmp_mul($x1, $y2), gmp_mul($x2, $y1)), $this->inv(gmp_add(1, $com))); + $y3 = gmp_mul(gmp_add($ymul, $xmul), $this->inv(gmp_sub(1, $com))); + + return array($this->pymod($x3, $this->q), $this->pymod($y3, $this->q)); + } else { + list($x1, $y1) = $P; + list($x2, $y2) = $Q; + $xmul = bcmul($x1, $x2); + $ymul = bcmul($y1, $y2); + $com = bcmul($this->d, bcmul($xmul, $ymul)); + $x3 = bcmul(bcadd(bcmul($x1, $y2), bcmul($x2, $y1)), $this->inv(bcadd(1, $com))); + $y3 = bcmul(bcadd($ymul, $xmul), $this->inv(bcsub(1, $com))); + + return array($this->pymod($x3, $this->q), $this->pymod($y3, $this->q)); + } + } + + public function scalarmult($P, $e) + { + if ($this->gmp) { + if ($e == 0) { + return array(0, 1); + } + $Q = $this->scalarmult($P, gmp_div($e, 2, 0)); + $Q = $this->edwards($Q, $Q); + if (substr($e, -1)%2 == 1) { + $Q = $this->edwards($Q, $P); + } + } else { + if ($e == 0) { + return array(0, 1); + } + $Q = $this->scalarmult($P, bcdiv($e, 2, 0)); + $Q = $this->edwards($Q, $Q); + if (substr($e, -1)%2 == 1) { + $Q = $this->edwards($Q, $P); + } + } + + return $Q; + } + + public function scalarloop($P, $e) + { + if ($this->gmp) { + $temp = array(); + $loopE = $e; + while ($loopE > 0) { + array_unshift($temp, $loopE); + $loopE = gmp_div($loopE, 2, 0); + } + $Q = array(); + foreach ($temp as $e) { + if ($e == 1) { + $Q = $this->edwards(array(0, 1), $P); + } elseif (substr($e, -1)%2 == 1) { + $Q = $this->edwards($this->edwards($Q, $Q), $P); + } else { + $Q = $this->edwards($Q, $Q); + } + } + } else { + $temp = array(); + $loopE = $e; + while ($loopE > 0) { + array_unshift($temp, $loopE); + $loopE = bcdiv($loopE, 2, 0); + } + $Q = array(); + foreach ($temp as $e) { + if ($e == 1) { + $Q = $this->edwards(array(0, 1), $P); + } elseif (substr($e, -1)%2 == 1) { + $Q = $this->edwards($this->edwards($Q, $Q), $P); + } else { + $Q = $this->edwards($Q, $Q); + } + } + } + + return $Q; + } + + public function bitsToString($bits) + { + $string = ''; + for ($i = 0; $i < $this->b/8; $i++) { + $sum = 0; + for ($j = 0; $j < 8; $j++) { + $bit = $bits[$i*8+$j]; + $sum += (int) $bit << $j; + } + $string .= chr($sum); + } + + return $string; + } + + public function dec2bin_i($decimal_i) + { + if ($this->gmp) { + $binary_i = ''; + do { + $binary_i = substr($decimal_i, -1)%2 .$binary_i; + $decimal_i = gmp_div($decimal_i, '2', 0); + } while (gmp_cmp($decimal_i, '0')); + } else { + $binary_i = ''; + do { + $binary_i = substr($decimal_i, -1)%2 .$binary_i; + $decimal_i = bcdiv($decimal_i, '2', 0); + } while (bccomp($decimal_i, '0')); + } + + return ($binary_i); + } + + public function encodeint($y) + { + $bits = substr(str_pad(strrev($this->dec2bin_i($y)), $this->b, '0', STR_PAD_RIGHT), 0, $this->b); + + return $this->bitsToString($bits); + } + + public function encodepoint($P) + { + list($x, $y) = $P; + $bits = substr(str_pad(strrev($this->dec2bin_i($y)), $this->b-1, '0', STR_PAD_RIGHT), 0, $this->b-1); + $bits .= (substr($x, -1)%2 == 1 ? '1' : '0'); + + return $this->bitsToString($bits); + } + + public function bit($h, $i) + { + if ($this->gmp) { + return (ord($h[(int) gmp_div($i, 8, 0)]) >> substr($i, -3)%8) & 1; + } else { + return (ord($h[(int) bcdiv($i, 8, 0)]) >> substr($i, -3)%8) & 1; + } + } + + /** + * Generates the public key of a given private key + * + * @param string $sk the secret key + * + * @return string + */ + public function publickey($sk) + { + if ($this->gmp) { + $h = $this->H($sk); + $sum = 0; + for ($i = 3; $i < $this->b-2; $i++) { + $sum = gmp_add($sum, gmp_mul(gmp_pow(2, $i), $this->bit($h, $i))); + } + $a = gmp_add(gmp_pow(2, $this->b-2), $sum); + $A = $this->scalarmult($this->B, $a); + $data = $this->encodepoint($A); + } else { + $h = $this->H($sk); + $sum = 0; + for ($i = 3; $i < $this->b-2; $i++) { + $sum = bcadd($sum, bcmul(bcpow(2, $i), $this->bit($h, $i))); + } + $a = bcadd(bcpow(2, $this->b-2), $sum); + $A = $this->scalarmult($this->B, $a); + $data = $this->encodepoint($A); + } + + return $data; + } + + public function Hint($m) + { + if ($this->gmp) { + $h = $this->H($m); + $sum = 0; + for ($i = 0; $i < $this->b*2; $i++) { + $sum = gmp_add($sum, gmp_mul(gmp_pow(2, $i), $this->bit($h, $i))); + } + } else { + $h = $this->H($m); + $sum = 0; + for ($i = 0; $i < $this->b*2; $i++) { + $sum = bcadd($sum, bcmul(bcpow(2, $i), $this->bit($h, $i))); + } + } + + return $sum; + } + + public function signature($m, $sk, $pk) + { + if ($this->gmp) { + $h = $this->H($sk); + $a = gmp_pow(2, (gmp_sub($this->b, 2))); + for ($i = 3; $i < $this->b-2; $i++) { + $a = gmp_add($a, gmp_mul(gmp_pow(2, $i), $this->bit($h, $i))); + } + $r = $this->Hint(substr($h, $this->b/8, ($this->b/4-$this->b/8)).$m); + $R = $this->scalarmult($this->B, $r); + $encR = $this->encodepoint($R); + $S = $this->pymod(gmp_add($r, gmp_mul($this->Hint($encR.$pk.$m), $a)), $this->l); + } else { + $h = $this->H($sk); + $a = bcpow(2, (bcsub($this->b, 2))); + for ($i = 3; $i < $this->b-2; $i++) { + $a = bcadd($a, bcmul(bcpow(2, $i), $this->bit($h, $i))); + } + $r = $this->Hint(substr($h, $this->b/8, ($this->b/4-$this->b/8)).$m); + $R = $this->scalarmult($this->B, $r); + $encR = $this->encodepoint($R); + $S = $this->pymod(bcadd($r, bcmul($this->Hint($encR.$pk.$m), $a)), $this->l); + } + + return $encR.$this->encodeint($S); + } + + public function isoncurve($P) + { + if ($this->gmp) { + list($x, $y) = $P; + $x2 = gmp_pow($x, 2); + $y2 = gmp_pow($y, 2); + + return $this->pymod(gmp_sub(gmp_sub(gmp_sub($y2, $x2), 1), gmp_mul($this->d, gmp_mul($x2, $y2))), $this->q) == 0; + } else { + list($x, $y) = $P; + $x2 = bcpow($x, 2); + $y2 = bcpow($y, 2); + + return $this->pymod(bcsub(bcsub(bcsub($y2, $x2), 1), bcmul($this->d, bcmul($x2, $y2))), $this->q) == 0; + } + } + + public function decodeint($s) + { + if ($this->gmp) { + $sum = 0; + for ($i = 0; $i < $this->b; $i++) { + $sum = gmp_add($sum, gmp_mul(gmp_pow(2, $i), $this->bit($s, $i))); + } + } else { + $sum = 0; + for ($i = 0; $i < $this->b; $i++) { + $sum = bcadd($sum, bcmul(bcpow(2, $i), $this->bit($s, $i))); + } + } + + return $sum; + } + + /* + * def decodepoint(s): + y = sum(2**i * bit(s,i) for i in range(0,b-1)) + x = xrecover(y) + if x & 1 != bit(s,b-1): x = q-x + P = [x,y] + if not isoncurve(P): raise Exception("decoding point that is not on curve") + return P + + */ + public function decodepoint($s) + { + if ($this->gmp) { + $y = 0; + for ($i = 0; $i < $this->b-1; $i++) { + $y = gmp_add($y, gmp_mul(gmp_pow(2, $i), $this->bit($s, $i))); + } + $x = $this->xrecover($y); + if (substr($x, -1)%2 != $this->bit($s, $this->b-1)) { + $x = gmp_sub($this->q, $x); + } + $P = array($x, $y); + if (!$this->isoncurve($P)) { + throw new Exception("Decoding point that is not on curve"); + } + } else { + $y = 0; + for ($i = 0; $i < $this->b-1; $i++) { + $y = bcadd($y, bcmul(bcpow(2, $i), $this->bit($s, $i))); + } + $x = $this->xrecover($y); + if (substr($x, -1)%2 != $this->bit($s, $this->b-1)) { + $x = bcsub($this->q, $x); + } + $P = array($x, $y); + if (!$this->isoncurve($P)) { + throw new Exception("Decoding point that is not on curve"); + } + } + + return $P; + } + + public function checkvalid($s, $m, $pk) + { + if (strlen($s) != $this->b/4) { + throw new Exception('Signature length is wrong'); + } + if (strlen($pk) != $this->b/8) { + throw new Exception('Public key length is wrong: '.strlen($pk)); + } + $R = $this->decodepoint(substr($s, 0, $this->b/8)); + try { + $A = $this->decodepoint($pk); + } catch (Exception $e) { + return false; + } + $S = $this->decodeint(substr($s, $this->b/8, $this->b/4)); + $h = $this->Hint($this->encodepoint($R).$pk.$m); + + return $this->scalarmult($this->B, $S) == $this->edwards($R, $this->scalarmult($A, $h)); + } + + // The code below is by the Monero-Integrations team + + public function scalarmult_base($e) + { + if ($this->gmp) { + if ($e == 0) { + return array(0, 1); + } + $Q = $this->scalarmult($this->B, gmp_div($e, 2, 0)); + $Q = $this->edwards($Q, $Q); + if (substr($e, -1)%2 == 1) { + $Q = $this->edwards($Q, $this->B); + } + } else { + if ($e == 0) { + return array(0, 1); + } + $Q = $this->scalarmult($this->B, bcdiv($e, 2, 0)); + $Q = $this->edwards($Q, $Q); + if (substr($e, -1)%2 == 1) { + $Q = $this->edwards($Q, $this->B); + } + } + + return $Q; + } +} diff --git a/vendor/monero-integrations/monerophp/src/jsonRPCClient.php b/vendor/monero-integrations/monerophp/src/jsonRPCClient.php new file mode 100644 index 0000000..87874a0 --- /dev/null +++ b/vendor/monero-integrations/monerophp/src/jsonRPCClient.php @@ -0,0 +1,217 @@ + + * http://implix.com + */ +namespace MoneroIntegrations\MoneroPhp; + +use InvalidArgumentException; +use RuntimeException; + +class jsonRPCClient +{ + protected $url = null, $is_debug = false, $parameters_structure = 'array'; + private $username; + private $password; + private $SSL; + protected $curl_options = array( + CURLOPT_CONNECTTIMEOUT => 1200, + CURLOPT_TIMEOUT => 1200 + ); + + + private $httpErrors = array( + 400 => '400 Bad Request', + 401 => '401 Unauthorized', + 403 => '403 Forbidden', + 404 => '404 Not Found', + 405 => '405 Method Not Allowed', + 406 => '406 Not Acceptable', + 408 => '408 Request Timeout', + 500 => '500 Internal Server Error', + 502 => '502 Bad Gateway', + 503 => '503 Service Unavailable' + ); + + public function __construct($pUrl, $pUser, $pPass, $check_SSL) + { + $this->validate(false === extension_loaded('curl'), 'The curl extension must be loaded to use this class!'); + $this->validate(false === extension_loaded('json'), 'The json extension must be loaded to use this class!'); + + $this->url = $pUrl; + $this->username = $pUser; + $this->password = $pPass; + $this->SSL = $check_SSL; + } + + public function setDebug($pIsDebug) + { + $this->is_debug = !empty($pIsDebug); + return $this; + } + + public function setCurlOptions($pOptionsArray) + { + if (is_array($pOptionsArray)) + { + $this->curl_options = $pOptionsArray + $this->curl_options; + } + else + { + throw new InvalidArgumentException('Invalid options type.'); + } + return $this; + } + + public function _run($pMethod, $pParams, $path) + { + // check if given params are correct + $this->validate(false === is_scalar($pMethod), 'Method name has no scalar value'); + // send params as an object or an array + // Request (method invocation) + $request = json_encode(array('jsonrpc' => '2.0', 'method' => $pMethod, 'params' => $pParams)); + // if is_debug mode is true then add url and request to is_debug + $this->debug('Url: ' . $this->url . "\r\n", false); + $this->debug('Request: ' . $request . "\r\n", false); + $responseMessage = $this->getResponse($request, $path); + // if is_debug mode is true then add response to is_debug and display it + $this->debug('Response: ' . $responseMessage . "\r\n", true); + // decode and create array ( can be object, just set to false ) + $responseDecoded = json_decode($responseMessage, true); + // check if decoding json generated any errors + $jsonErrorMsg = json_last_error_msg(); + $this->validate( !is_null($jsonErrorMsg) && $jsonErrorMsg !== 'No error' , $jsonErrorMsg . ': ' . $responseMessage); + if (isset($responseDecoded['error'])) + { + $errorMessage = 'Request have return error: ' . $responseDecoded['error']['message'] . '; ' . "\n" . + 'Request: ' . $request . '; '; + if (isset($responseDecoded['error']['data'])) + { + $errorMessage .= "\n" . 'Error data: ' . $responseDecoded['error']['data']; + } + $this->validate( !is_null($responseDecoded['error']), $errorMessage); + } + return $responseDecoded['result'] ?? -1; + } + + protected function & getResponse(&$pRequest, &$path) + { + + /*ob_start(); */ + /*$out = fopen('php://output', 'w');*/ + + + // do the actual connection + $ch = curl_init(); + if (!$ch) + { + throw new RuntimeException('Could\'t initialize a cURL session'); + } + curl_setopt($ch, CURLOPT_URL, $this->url.$path); + curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); + curl_setopt($ch, CURLOPT_USERPWD, $this->username . ":" . $this->password); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $pRequest); + curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/json')); + curl_setopt($ch, CURLOPT_ENCODING, 'gzip,deflate'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + + + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); + curl_setopt($ch, CURLOPT_PIPEWAIT, true); + curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); + /*curl_setopt($ch, CURLOPT_VERBOSE, true);*/ + /*curl_setopt($ch, CURLOPT_STDERR, $out);*/ + /*curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);*/ + curl_setopt($ch, CURLOPT_TCP_KEEPALIVE, 1); + curl_setopt($ch, CURLOPT_DNS_CACHE_TIMEOUT, 3600); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); +/* + var_dump($this->url); + var_dump($path); + var_dump($this->username); + var_dump($this->password); + var_dump($this->SSL); +*/ + /*Vilyaem's doing*/ + if ($this->SSL) + { + /*curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, '2');*/ + /*curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);*/ + }else{ + /*curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);*/ + } + if (!curl_setopt_array($ch, $this->curl_options)) + { + throw new RuntimeException('Error while setting curl options'); + } + // send the request + $response = curl_exec($ch); + // check http status code + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + if (isset($this->httpErrors[$httpCode])) + { + throw new RuntimeException('Response Http Error - ' . $this->httpErrors[$httpCode]); + } + // check for curl error + if (0 < curl_errno($ch)) + { + throw new RuntimeException('Unable to connect to '.$this->url . ' Error: ' . curl_error($ch)); + } + // close the connection + curl_close($ch); + + /*vilyaem debugging*/ + /* + $verboseLog = stream_get_contents($out); + fclose($out); + $curldebug = ob_get_clean(); + */ + /*echo "cUrl verbose information:\n","
", htmlspecialchars($verboseLog), "
\n";*/ + /*var_dump($verboseLog);*/ + /*var_dump($curldebug);*/ + /*echo "
$curldebug
";*/ + + return $response; + } + + public function validate($pFailed, $pErrMsg) + { + if ($pFailed) + { + throw new RuntimeException($pErrMsg); + } + } + + protected function debug($pAdd, $pShow = false) + { + static $debug, $startTime; + // is_debug off return + if (false === $this->is_debug) + { + return; + } + // add + $debug .= $pAdd; + // get starttime + $startTime = empty($startTime) ? array_sum(explode(' ', microtime())) : $startTime; + if (true === $pShow and !empty($debug)) + { + // get endtime + $endTime = array_sum(explode(' ', microtime())); + // performance summary + $debug .= 'Request time: ' . round($endTime - $startTime, 3) . ' s Memory usage: ' . round(memory_get_usage() / 1024) . " kb\r\n"; + echo nl2br($debug); + // send output immediately + flush(); + // clean static + $debug = $startTime = null; + } + } +} diff --git a/vendor/monero-integrations/monerophp/src/mnemonic.php b/vendor/monero-integrations/monerophp/src/mnemonic.php new file mode 100644 index 0000000..15476ca --- /dev/null +++ b/vendor/monero-integrations/monerophp/src/mnemonic.php @@ -0,0 +1,297 @@ + 13 ? 24 : 12); + + $wstr = ''; + foreach($words as $word) { + $wstr .= ($plen == 0 ? $word : mb_substr($word, 0, $plen)); + } + + $checksum = crc32($wstr); + $idx = $checksum % count($words); + return $words[$idx]; + } + + /** + * Given a mnemonic seed word list, check if checksum word is valid. + * Returns boolean value. + */ + static function validate_checksum($words, $prefix_len) { + return (self::checksum($words, $prefix_len) == $words[count($words)-1]) ? true : false; + } + + /** + * Given an 8 byte word (or shorter), + * pads to 8 bytes (adds 0 at left) and reverses endian byte order. + */ + static function swap_endian($word) { + $word = str_pad ( $word, 8, 0, STR_PAD_LEFT); + return implode('', array_reverse(str_split($word, 2))); + } + + /** + * Given a hexadecimal key string (seed), + * return it's mnemonic representation. + * + * @todo if anyone can make this work reliably with + * pure PHP math (no gmp or bcmath), please submit a + * pull request. + */ + static function encode($seed, $wordset_name = null) { + assert(mb_strlen($seed) % 8 == 0); + $out = []; + + $wordset = self::get_wordset_by_name( $wordset_name ); + $words = $wordset['words']; + + $ng = count($words); + for($i = 0; $i < mb_strlen($seed) / 8; $i ++) { + $word = self::swap_endian(mb_substr($seed, 8*$i, (8*$i+8) - (8*$i) )); + $x = gmp_init($word, 16); + $w1 = gmp_mod($x,$ng); + $w2 = gmp_mod(gmp_add(gmp_div($x, $ng), $w1), $ng); + $w3 = gmp_mod(gmp_add(gmp_div(gmp_div($x, $ng), $ng), $w2), $ng); + $out[] = $words[gmp_strval($w1)]; + $out[] = $words[gmp_strval($w2)]; + $out[] = $words[gmp_strval($w3)]; + } + return $out; + } + + /** + * Given a hexadecimal key string (seed), + * return it's mnemonic representation plus an + * extra checksum word. + */ + static function encode_with_checksum($message, $wordset_name = null) { + $list = self::encode($message, $wordset_name); + + $wordset = self::get_wordset_by_name($wordset_name); + $list[] = self::checksum($list, $wordset['prefix_len']); + return $list ; + } + + /** + * Given a mnemonic word list, return a hexadecimal encoded string (seed). + * + * @todo if anyone can make this work reliably with + * pure PHP math (no gmp or bcmath), please submit a + * pull request. + */ + static function decode($wlist, $wordset_name = null) { + $wordset = self::get_wordset_by_name( $wordset_name ); + + $plen = $wordset['prefix_len']; + $tw = $wordset['trunc_words']; + $wcount = count($tw); + + if (($plen === 0 && ($wcount % 3 !== 0)) || ($plen > 0 && ($wcount % 3 === 2))) { + throw new \Exception("too few words"); + } + if ($plen > 0 && (count($wlist) % 3 === 0)) { + throw new \Exception("last word missing"); + } + + $out = ''; + + for ($i = 0; $i < count($wlist)-1; $i += 3) { + + if($plen == 0) { + $w1 = @$tw[$wlist[$i]]; + $w2 = @$tw[$wlist[$i + 1]]; + $w3 = @$tw[$wlist[$i + 2]]; + } + else { + $w1 = @$tw[mb_substr($wlist[$i], 0, $plen)]; + $w2 = @$tw[mb_substr($wlist[$i + 1], 0, $plen)]; + $w3 = @$tw[mb_substr($wlist[$i + 2], 0, $plen)]; + } + + if ($w1 === null || $w2 === null || $w3 === null) { + throw new \Exception("invalid word in mnemonic"); + } + // $x = (($w1 + ($n * (($w2 - $w1) % $n))) + (($n * $n) * (($w3 - $w2) % $n))); + $x = gmp_add(gmp_add($w1, gmp_mul($wcount, (gmp_mod(gmp_sub($w2, $w1), $wcount)))), gmp_mul((gmp_mul($wcount,$wcount)), (gmp_mod(gmp_sub($w3, $w2), $wcount)))); + $out .= self::swap_endian(gmp_strval($x, 16)); + } + return $out; + } + + /** + * Given a wordset identifier, returns the full wordset + */ + static public function get_wordset_by_name($name = null) { + $name = $name ?: 'english'; + $wordset = self::get_wordsets(); + $ws = @$wordset[$name]; + if( !$ws ) { + throw new \Exception("Invalid wordset $name"); + } + return $ws; + } + + /** + * Given a mnemonic array of words, returns name of matching + * wordset that contains all words, or null if not found. + * + * throws an exception if more than one wordset matches all words, + * but in theory that should never happen. + */ + static public function find_wordset_by_mnemonic($mnemonic) { + $sets = self::get_wordsets(); + $matched_wordsets = []; + foreach($sets as $ws_name => $ws) { + + // note, to make the search faster, we truncate each word + // according to prefix_len of the wordset, and lookup + // by key in trunc_words, rather than searching through + // entire wordset array. + $allmatch = true; + foreach($mnemonic as $word) { + $tw = $ws['prefix_len'] == 0 ? $word : mb_substr($word, 0, $ws['prefix_len']); + if( @$ws['trunc_words'][$tw] === null) { + $allmatch = false; + break; + } + } + if( $allmatch) { + $matched_wordsets[] = $ws_name; + } + } + + $cnt = count($matched_wordsets); + if($cnt > 1) { + throw new \Exception("Ambiguous match. mnemonic matches $cnt wordsets."); + } + + return @$matched_wordsets[0]; + } + + + /** + * returns list of available wordsets + */ + static public function get_wordset_list() { + return array_keys( self::get_wordsets() ); + } + + /** + * This function returns all available wordsets. + * + * Each wordset is in a separate file in wordsets/*.ws.php + */ + static public function get_wordsets() { + + static $wordsets = null; + if( $wordsets ) { + return $wordsets; + } + + $wordsets = []; + $files = glob(__DIR__ . 'wordsets/*.ws.php'); + foreach($files as $f) { + require_once($f); + + list($wordset) = explode('.', basename($f)); + $classname = __NAMESPACE__ . '\\' . $wordset; + + $wordsets[$wordset] = [ + 'name' => $classname::name(), + 'english_name' => $classname::english_name(), + 'prefix_len' => $classname::prefix_length(), + 'words' => $classname::words(), + ]; + } + + // This loop adds the key 'trunc_words' to each wordset, which contains + // a pre-generated list of words truncated to length prefix_len. + // This list is optimized for fast lookup of the truncated word + // with the format being [ => ]. + // This optimization assumes/requires that each truncated word is unique. + // A further optimization could be to only pre-generate trunc_words on the fly + // when a wordset is actually used, rather than for all wordsets. + foreach($wordsets as &$ws) { + + $tw = []; + $plen = $ws['prefix_len']; + $i = 0; + foreach( $ws['words'] as $w) { + $key = $plen == 0 ? $w : mb_substr($w, 0, $plen); + $tw[$key] = $i++; + } + + $ws['trunc_words'] = $tw; + } + return $wordsets; + } + +} + + +interface wordset { + + /* Returns name of wordset in the wordset's native language. + * This is a human-readable string, and should be capitalized + * if the language supports it. + */ + static public function name() : string; + + /* Returns name of wordset in english. + * This is a human-readable string, and should be capitalized + */ + static public function english_name() : string; + + /* Returns integer indicating length of unique prefix, + * such that each prefix of this length is unique across + * the entire set of words. + * + * A value of 0 indicates that there is no unique prefix + * and the entire word must be used instead. + */ + static public function prefix_length() : int; + + /* Returns an array of all words in the wordset. + */ + static public function words() : array; +}; \ No newline at end of file diff --git a/vendor/monero-integrations/monerophp/src/subaddress.php b/vendor/monero-integrations/monerophp/src/subaddress.php new file mode 100644 index 0000000..e7ebf3f --- /dev/null +++ b/vendor/monero-integrations/monerophp/src/subaddress.php @@ -0,0 +1,127 @@ +ed25519 = new ed25519(); + $this->base58 = new base58(); + $this->gmp = extension_loaded('gmp'); + } + + private function sc_reduce($input) + { + $integer = $this->ed25519->decodeint(hex2bin($input)); + if($this->gmp) + $modulo = gmp_mod($integer , $this->ed25519->l); + else + $modulo = bcmod($integer , $this->ed25519->l); + $result = bin2hex($this->ed25519->encodeint($modulo)); + return $result; + } + + private function ge_add($point1, $point2) + { + $point3 = $this->ed25519->edwards($this->ed25519->decodepoint(hex2bin($point1)), $this->ed25519->decodepoint(hex2bin($point2))); + return bin2hex($this->ed25519->encodepoint($point3)); + } + + private function ge_scalarmult($public, $secret) + { + $point = $this->ed25519->decodepoint(hex2bin($public)); + $scalar = $this->ed25519->decodeint(hex2bin($secret)); + $res = $this->ed25519->scalarmult($point, $scalar); + return bin2hex($this->ed25519->encodepoint($res)); + } + + private function ge_scalarmult_base($scalar) + { + $decoded = $this->ed25519->decodeint(hex2bin($scalar)); + $res = $this->ed25519->scalarmult_base($decoded); + return bin2hex($this->ed25519->encodepoint($res)); + } + + /* + * @param string Hex encoded string of the data to hash + * @return string Hex encoded string of the hashed data + * + */ + private function keccak_256($message) + { + $message_bin = hex2bin($message); + $hash = keccak::hash($message_bin, 256); + return $hash; + } + + /* + * Hs in the cryptonote white paper + * + * @param string Hex encoded data to hash + * + * @return string A 32 byte encoded integer + */ + private function hash_to_scalar($data) + { + $hash = $this->keccak_256($data); + $scalar = $this->sc_reduce($hash); + return $scalar; + } + + public function generate_subaddr_secret_key($major_index, $minor_index, $sec_key) + { + $prefix = "5375624164647200"; // hex encoding of string "SubAddr" + $index = pack("II", $major_index, $minor_index); + return $this->hash_to_scalar($prefix . $sec_key . bin2hex($index)); + } + + public function generate_subaddress_spend_public_key($spend_public_key, $subaddr_secret_key) + { + $mG = $this->ge_scalarmult_base($subaddr_secret_key); + $D = $this->ge_add($spend_public_key, $mG); + return $D; + } + + public function generate_subaddr_view_public_key($subaddr_spend_public_key, $view_secret_key) + { + return $this->ge_scalarmult($subaddr_spend_public_key, $view_secret_key); + } + + public function generate_subaddress($major_index, $minor_index, $view_secret_key, $spend_public_key) + { + $subaddr_secret_key = $this->generate_subaddr_secret_key($major_index, $minor_index, $view_secret_key); + $subaddr_public_spend_key = $this->generate_subaddress_spend_public_key($spend_public_key, $subaddr_secret_key); + $subaddr_public_view_key = $this->generate_subaddr_view_public_key($subaddr_public_spend_key, $view_secret_key); + // mainnet subaddress network byte is 42 (0x2a) + $data = "2a" . $subaddr_public_spend_key . $subaddr_public_view_key; + $checksum = $this->keccak_256($data); + $encoded = $this->base58->encode($data . substr($checksum, 0, 8)); + return $encoded; + } +} diff --git a/vendor/monero-integrations/monerophp/src/walletRPC.php b/vendor/monero-integrations/monerophp/src/walletRPC.php new file mode 100644 index 0000000..3f52b6a --- /dev/null +++ b/vendor/monero-integrations/monerophp/src/walletRPC.php @@ -0,0 +1,1987 @@ + (https://github.com/cryptochangements34) + * Serhack [Monero Integrations] (https://serhack.me) + * TheKoziTwo [xmr-integration] + * Kacper Rowinski [jsonRPCClient] + * + * @author Monero Integrations Team (https://github.com/monero-integrations) + * @copyright 2018 + * @license MIT + * + * ============================================================================ + * + * // See example.php for more examples + * + * // Initialize class + * $walletRPC = new walletRPC(); + * + * // Examples: + * $address = $walletRPC->get_address(); + * $signed = $walletRPC->sign('The Times 03/Jan/2009 Chancellor on brink of second bailout for banks'); + * + */ + +namespace MoneroIntegrations\MoneroPhp; + +use Exception; + +class walletRPC +{ + private $client; + + private $protocol; + private $host; + private $port; + private $url; + private $user; + private $password; + private $check_SSL; + + /** + * + * Start a connection with the Monero wallet RPC interface (monero-wallet-rpc) + * + * @param string $host monero-wallet-rpc hostname (optional) + * @param int $port monero-wallet-rpc port (optional) + * @param string $protocol monero-wallet-rpc protocol (eg. 'http') (optional) + * @param string $user monero-wallet-rpc username (optional) + * @param string $password monero-wallet-rpc passphrase (optional) + * + */ + function __construct($host = '127.0.0.1', $port = 18081, $SSL = true, $user = null, $password = null) + { + if (is_array($host)) { // Parameters passed in as object/dictionary + $params = $host; + + if (array_key_exists('host', $params)) { + $host = $params['host']; + } else { + $host = '127.0.0.1'; + } + if (array_key_exists('port', $params)) { + $port = $params['port']; + } + if (array_key_exists('protocol', $params)) { + $protocol = $params['protocol']; + } + if (array_key_exists('user', $params)) { + $user = $params['user']; + } + if (array_key_exists('password', $params)) { + $password = $params['password']; + } + } + + if ($SSL) { + $protocol = 'https'; + } else { + $protocol = 'http'; + } + + $this->host = $host; + $this->port = $port; + $this->protocol = $protocol; + $this->user = $user; + $this->password = $password; + $this->check_SSL = $SSL; + + $this->url = $protocol.'://'.$host.':'.$port.'/'; + $this->client = new jsonRPCClient($this->url, $this->user, $this->password, $this->check_SSL); + } + + /** + * + * Execute command via jsonRPCClient + * + * @param string $method RPC method to call + * @param object $params Parameters to pass (optional) + * + * @return string Call result + * + */ + private function _run($method, $params = null, $path = 'json_rpc') + { + $result = $this->client->_run($method, $params, $path); + return $result; + } + + /** + * + * Print JSON object (for API) + * + * @param object $json JSON object to print + * + * @return none + * + */ + public function _print($json) + { + echo json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + } + + /** + * + * Convert from moneroj to tacoshi (piconero) + * + * @param number $method Amount (in monero) to transform to tacoshi (piconero) (optional) + * + * @return number + * + */ + public function _transform($amount = 0) + { + return intval(bcmul($amount, 1000000000000)); + } + + /** + * + * Look up an account's balance + * + * @param number $account_index Index of account to look up (optional) + * + * @return object Example: { + * "balance": 140000000000, + * "unlocked_balance": 50000000000 + * } + * + */ + public function get_balance($account_index = 0) + { + $params = array('account_index' => $account_index); + return $this->_run('get_balance', $params); + } + + /** + * + * Alias of get_balance() + * + */ + public function getbalance($account_index = 0) + { + return $this->get_balance($account_index); + } + + /** + * + * Look up wallet address(es) + * + * @param number $account_index Index of account to look up (optional) + * @param number $address_index Index of subaddress to look up (optional) + * + * @return object Example: { + * "address": "A2XE6ArhRkVZqepY2DQ5QpW8p8P2dhDQLhPJ9scSkW6q9aYUHhrhXVvE8sjg7vHRx2HnRv53zLQH4ATSiHHrDzcSFqHpARF", + * "addresses": [ + * { + * "address": "A2XE6ArhRkVZqepY2DQ5QpW8p8P2dhDQLhPJ9scSkW6q9aYUHhrhXVvE8sjg7vHRx2HnRv53zLQH4ATSiHHrDzcSFqHpARF", + * "address_index": 0, + * "label": "Primary account", + * "used": true + * }, { + * "address": "Bh3ttLbjGFnVGCeGJF1HgVh4DfCaBNpDt7PQAgsC2GFug7WKskgfbTmB6e7UupyiijiHDQPmDC7wSCo9eLoGgbAFJQaAaDS", + * "address_index": 1, + * "label": "", + * "used": true + * } + * ] + * } + * + */ + public function get_address($account_index = 0, $address_index = 0) + { + $params = array('account_index' => $account_index, 'address_index' => $address_index); + return $this->_run('get_address', $params); + } + + /** + * @param string $address Monero address + * @return object Example: { + * "index": { + * "major": 0, + * "minor": 1 + * } + * } + */ + public function get_address_index($address){ + $params = array('address' => $address); + return $this->_run('get_address_index', $params); + } + + /** + * + * Alias of get_address() + * + * @param number $account_index Index of account to look up (optional) + * @param number $address_index Index of subaddress to look up (optional) + * + * @return object Example: { + * "address": "427ZuEhNJQRXoyJAeEoBaNW56ScQaLXyyQWgxeRL9KgAUhVzkvfiELZV7fCPBuuB2CGuJiWFQjhnhhwiH1FsHYGQGaDsaBA" + * } + * + */ + public function getaddress($account_index = 0, $address_index = 0) + { + return $this->get_address($account_index = 0, $address_index = 0); + } + + /** + * + * Create a new subaddress + * + * @param number $account_index The subaddress account index + * @param string $label A label to apply to the new subaddress + * + * @return object Example: { + * "address": "Bh3ttLbjGFnVGCeGJF1HgVh4DfCaBNpDt7PQAgsC2GFug7WKskgfbTmB6e7UupyiijiHDQPmDC7wSCo9eLoGgbAFJQaAaDS" + * "address_index": 1 + * } + * + */ + public function create_address($account_index = 0, $label = '') + { + $params = array('account_index' => $account_index, 'label' => $label); + $create_address_method = $this->_run('create_address', $params); + + $save = $this->store(); // Save wallet state after subaddress creation + + return $create_address_method; + } + + /** + * + * Label a subaddress + * + * @param number The index of the subaddress to label + * @param string The label to apply + * + * @return none + * + */ + public function label_address($index, $label) + { + $params = array('index' => $index ,'label' => $label); + return $this->_run('label_address', $params); + } + + /** + * + * Look up wallet accounts + * + * @param string $tag Optional filtering by tag + * + * @return object Example: { + * "subaddress_accounts": { + * "0": { + * "account_index": 0, + * "balance": 2808597352948771, + * "base_address": "A2XE6ArhRkVZqepY2DQ5QpW8p8P2dhDQLhPJ9scSkW6q9aYUHhrhXVvE8sjg7vHRx2HnRv53zLQH4ATSiHHrDzcSFqHpARF", + * "label": "Primary account", + * "tag": "", + * "unlocked_balance": 2717153096298162 + * }, + * "1": { + * "account_index": 1, + * "balance": 0, + * "base_address": "BcXKsfrvffKYVoNGN4HUFfaruAMRdk5DrLZDmJBnYgXrTFrXyudn81xMj7rsmU5P9dX56kRZGqSaigUxUYoaFETo9gfDKx5", + * "label": "Secondary account", + * "tag": "", + * "unlocked_balance": 0 + * }, + * "total_balance": 2808597352948771, + * "total_unlocked_balance": 2717153096298162 + * } + * + */ + public function get_accounts($tag = null) + { + return (is_null($tag)) ? $this->_run('get_accounts') : $this->_run('get_accounts', array('tag' => $tag)); + } + + /** + * + * Create a new account + * + * @param string $label Label to apply to new account + * + * @return none + * + */ + public function create_account($label = '') + { + $params = array('label' => $label); + $create_account_method = $this->_run('create_account', $params); + + $save = $this->store(); // Save wallet state after account creation + + return $create_account_method; + } + + /** + * + * Label an account + * + * @param number $account_index Index of account to label + * @param string $label Label to apply + * + * @return none + * + */ + public function label_account($account_index, $label) + { + $params = array('account_index' => $account_index, 'label' => $label); + $label_account_method = $this->_run('label_account', $params); + + $save = $this->store(); // Save wallet state after account label + + return $label_account_method; + } + + /** + * + * Look up account tags + * + * @param none + * + * @return object Example: { + * "account_tags": { + * "0": { + * "accounts": { + * "0": 0, + * "1": 1 + * }, + * "label": "", + * "tag": "Example tag" + * } + * } + * } + * + */ + public function get_account_tags() + { + return $this->_run('get_account_tags'); + } + + /** + * + * Tag accounts + * + * @param array $accounts The indices of the accounts to tag + * @param string $tag Tag to apply + * + * @return none + * + */ + public function tag_accounts($accounts, $tag) + { + $params = array('accounts' => $accounts, 'tag' => $tag); + $tag_accounts_method = $this->_run('tag_accounts', $params); + + $save = $this->store(); // Save wallet state after account tagging + + return $tag_accounts_method; + } + + /** + * + * Untag accounts + * + * @param array $accounts The indices of the accounts to untag + * + * @return none + * + */ + public function untag_accounts($accounts) + { + $params = array('accounts' => $accounts); + $untag_accounts_method = $this->_run('untag_accounts', $params); + + $save = $this->store(); // Save wallet state after untagging accounts + + return $untag_accounts_method; + } + + /** + * + * Describe a tag + * + * @param string $tag Tag to describe + * @param string $description Description to apply to tag + * + * @return object Example: { + * // TODO example + * } + * + */ + public function set_account_tag_description($tag, $description) + { + $params = array('tag' => $tag, 'description' => $description); + $set_account_tag_description_method = $this->_run('set_account_tag_description', $params); + + $save = $this->store(); // Save wallet state after describing tag + + return $set_account_tag_description_method; + } + + /** + * + * Look up how many blocks are in the longest chain known to the wallet + * + * @param none + * + * @return object Example: { + * "height": 994310 + * } + * + */ + public function get_height() + { + return $this->_run('get_height'); + } + + /** + * + * Alias of get_height() + * + */ + public function getheight() + { + return $this->get_height(); + } + + /** + * + * Send monero + * Parameters can be passed in individually (as listed below) or as an object/dictionary (as listed at bottom) + * To send to multiple recipients, use the object/dictionary (bottom) format and pass an array of recipient addresses and amount arrays in the destinations field (as in "destinations = [['amount' => 1, 'address' => ...], ['amount' => 2, 'address' => ...]]") + * + * @param string $amount Amount of monero to send + * @param string $address Address to receive funds + * @param string $payment_id Payment ID (optional) + * @param number $mixin Mixin number (ringsize - 1) (optional) + * @param number $account_index Account to send from (optional) + * @param string $subaddr_indices Comma-separated list of subaddress indices to spend from (optional) + * @param number $priority Transaction priority (optional) + * @param number $unlock_time UNIX time or block height to unlock output (optional) + * @param boolean $do_not_relay Do not relay transaction (optional) + * + * OR + * + * @param object $params Array containing any of the options listed above, where only amount and address or a destination's array are required + * + * @return object Example: { + * "amount": "1000000000000", + * "fee": "1000020000", + * "tx_hash": "c60a64ddae46154a75af65544f73a7064911289a7760be8fb5390cb57c06f2db", + * "tx_key": "805abdb3882d9440b6c80490c2d6b95a79dbc6d1b05e514131a91768e8040b04" + * } + * + */ + public function transfer($amount, $address = '', $payment_id = '', $mixin = 15, $account_index = 0, $subaddr_indices = '', $priority = 2, $unlock_time = 0, $do_not_relay = false, $ringsize = 11) + { + if (is_array($amount)) { // Parameters passed in as object/dictionary + $params = $amount; + + if (array_key_exists('destinations', $params)) { + $destinations = $params['destinations']; + + if (!is_array($destinations)) { + throw new Exception('Error: destinations must be an array'); + } + + foreach ($destinations as $destination_index => $destination) { + if (array_key_exists('amount', $destination)) { + $destinations[$destination_index]['amount'] = $this->_transform($destination['amount']); + } else { + throw new Exception('Error: Amount required'); + } + if (!array_key_exists('address', $destination)) { + throw new Exception('Error: Address required'); + } + } + } else { + if (array_key_exists('amount', $params)) { + $amount = $params['amount']; + } else { + throw new Exception('Error: Amount required'); + } + if (array_key_exists('address', $params)) { + $address = $params['address']; + } else { + throw new Exception('Error: Address required'); + } + $destinations = array(array('amount' => $this->_transform($amount), 'address' => $address)); + } + if (array_key_exists('payment_id', $params)) { + throw new Exception('Error: Payment ids have been deprecated.'); + } + if (array_key_exists('mixin', $params)) { + $mixin = $params['mixin']; + } + if (array_key_exists('account_index', $params)) { + $account_index = $params['account_index']; + } + if (array_key_exists('subaddr_indices', $params)) { + $subaddr_indices = $params['subaddr_indices']; + } + if (array_key_exists('priority', $params)) { + $priority = $params['priority']; + } + if (array_key_exists('unlock_time', $params)) { + $unlock_time = $params['unlock_time']; + } + if (array_key_exists('do_not_relay', $params)) { + $do_not_relay = $params['do_not_relay']; + } + } else { // Legacy parameters used + $destinations = array(array('amount' => $this->_transform($amount), 'address' => $address)); + } + + $params = array('destinations' => $destinations, 'mixin' => $mixin, 'get_tx_key' => true, 'account_index' => $account_index, 'subaddr_indices' => $subaddr_indices, 'priority' => $priority, 'do_not_relay' => $do_not_relay, 'ringsize' => $ringsize); + $transfer_method = $this->_run('transfer', $params); + + $save = $this->store(); // Save wallet state after transfer + + return $transfer_method; + } + + /** + * + * Same as transfer, but splits transfer into more than one transaction if necessary + * + */ + public function transfer_split($amount, $address = '', $payment_id = '', $mixin = 15, $account_index = 0, $subaddr_indices = '', $priority = 2, $unlock_time = 0, $do_not_relay = false) + { + if (is_array($amount)) { // Parameters passed in as object/dictionary + $params = $amount; + + if (array_key_exists('destinations', $params)) { + $destinations = $params['destinations']; + + if (!is_array($destinations)) { + throw new Exception('Error: destinations must be an array'); + } + + foreach ($destinations as $destination) { + if (array_key_exists('amount', $destinations[$destination])) { + $destinations[$destination]['amount'] = $this->_transform($destinations[$destination]['amount']); + } else { + throw new Exception('Error: Amount required'); + } + if (!array_key_exists('address', $destinations[$destination])) { + throw new Exception('Error: Address required'); + } + } + } else { + if (array_key_exists('amount', $params)) { + $amount = $params['amount']; + } else { + throw new Exception('Error: Amount required'); + } + if (array_key_exists('address', $params)) { + $address = $params['address']; + } else { + throw new Exception('Error: Address required'); + } + $destinations = array(array('amount' => $this->_transform($amount), 'address' => $address)); + } + if (array_key_exists('mixin', $params)) { + $mixin = $params['mixin']; + } + if (array_key_exists('payment_id', $params)) { + $payment_id = $params['payment_id']; + } + if (array_key_exists('account_index', $params)) { + $account_index = $params['account_index']; + } + if (array_key_exists('subaddr_indices', $params)) { + $subaddr_indices = $params['subaddr_indices']; + } + if (array_key_exists('priority', $params)) { + $priority = $params['priority']; + } + if (array_key_exists('unlock_time', $params)) { + $unlock_time = $params['unlock_time']; + } + if (array_key_exists('do_not_relay', $params)) { + $do_not_relay = $params['do_not_relay']; + } + } else { // Legacy parameters used + $destinations = array(array('amount' => $this->_transform($amount), 'address' => $address)); + } + + $params = array('destinations' => $destinations, 'mixin' => $mixin, 'get_tx_key' => true, 'account_index' => $account_index, 'subaddr_indices' => $subaddr_indices, 'payment_id' => $payment_id, 'priority' => $priority, 'unlock_time' => $unlock_time, 'do_not_relay' => $do_not_relay); + $transfer_method = $this->_run('transfer_split', $params); + + $save = $this->store(); // Save wallet state after transfer + + return $transfer_method; + } + + /** + * + * Send all dust outputs back to the wallet + * + * @param none + * + * @return object Example: { + * // TODO example + * } + * + */ + public function sweep_dust() + { + return $this->_run('sweep_dust'); + } + + /** + * + * Send all unmixable outputs back to the wallet + * + * @param none + * + * @return object Example: { + * // TODO example + * } + * + */ + public function sweep_unmixable() + { + return $this->_run('sweep_unmixable'); + } + + /** + * + * Send all unlocked outputs from an account to an address + * + * @param string $address Address to receive funds + * @param string $subaddr_indices Comma-separated list of subaddress indices to sweep (optional) + * @param number $account_index Index of the account to sweep (optional) + * @param string $payment_id Payment ID (optional) + * @param number $mixin Mixin number (ringsize - 1) (optional) + * @param number $priority Payment ID (optional) + * @param number $below_amount Only send outputs below this amount (optional) + * @param number $unlock_time UNIX time or block height to unlock output (optional) + * @param boolean $do_not_relay Do not relay transaction (optional) + * + * OR + * + * @param object $params Array containing any of the options listed above, where only address is required + * + * @return object Example: { + * "amount": "1000000000000", + * "fee": "1000020000", + * "tx_hash": "c60a64ddae46154a75af65544f73a7064911289a7760be8fb5390cb57c06f2db", + * "tx_key": "805abdb3882d9440b6c80490c2d6b95a79dbc6d1b05e514131a91768e8040b04" + * } + * + */ + public function sweep_all($address, $subaddr_indices = '', $account_index = 0, $payment_id = '', $mixin = 15, $priority = 2, $below_amount = 0, $unlock_time = 0, $do_not_relay = false) + { + if (is_array($address)) { // Parameters passed in as object/dictionary + $params = $address; + + if (array_key_exists('address', $params)) { + $address = $params['address']; + } else { + throw new Exception('Error: Address required'); + } + if (array_key_exists('subaddr_indices', $params)) { + $subaddr_indices = $params['subaddr_indices']; + } + if (array_key_exists('account_index', $params)) { + $account_index = $params['account_index']; + } + if (array_key_exists('payment_id', $params)) { + $payment_id = $params['payment_id']; + } + if (array_key_exists('mixin', $params)) { + $mixin = $params['mixin']; + } + if (array_key_exists('priority', $params)) { + $priority = $params['priority']; + } + if (array_key_exists('below_amount', $params)) { + $below_amount = $params['below_amount']; + } + if (array_key_exists('unlock_time', $params)) { + $unlock_time = $params['unlock_time']; + } + if (array_key_exists('do_not_relay', $params)) { + $do_not_relay = $params['do_not_relay']; + } + } + + $params = array('address' => $address, 'mixin' => $mixin, 'get_tx_key' => true, 'subaddr_indices' => $subaddr_indices, 'account_index' => $account_index, 'payment_id' => $payment_id, 'priority' => $priority, 'below_amount' => $this->_transform($below_amount), 'unlock_time' => $unlock_time, 'do_not_relay' => $do_not_relay); + $sweep_all_method = $this->_run('sweep_all', $params); + + $save = $this->store(); // Save wallet state after transfer + + return $sweep_all_method; + } + + /** + * + * Sweep a single key image to an address + * + * @param string $key_image Key image to sweep + * @param string $address Address to receive funds + * @param string $payment_id Payment ID (optional) + * @param number $below_amount Only send outputs below this amount (optional) + * @param number $mixin Mixin number (ringsize - 1) (optional) + * @param number $priority Payment ID (optional) + * @param number $unlock_time UNIX time or block height to unlock output (optional) + * @param boolean $do_not_relay Do not relay transaction (optional) + * + * OR + * + * @param object $params Array containing any of the options listed above, where only address is required + * + * @return object Example: { + * "amount": "1000000000000", + * "fee": "1000020000", + * "tx_hash": "c60a64ddae46154a75af65544f73a7064911289a7760be8fb5390cb57c06f2db", + * "tx_key": "805abdb3882d9440b6c80490c2d6b95a79dbc6d1b05e514131a91768e8040b04" + * } + * + */ + public function sweep_single($key_image, $address, $payment_id = '', $mixin = 15, $priority = 2, $below_amount = 0, $unlock_time = 0, $do_not_relay = 0) + { + if (is_array($key_image)) { // Parameters passed in as object/dictionary + $params = $key_image; + + if (array_key_exists('key_image', $params)) { + $key_image = $params['key_image']; + } else { + throw new Exception('Error: Key image required'); + } + if (array_key_exists('address', $params)) { + $address = $params['address']; + } else { + throw new Exception('Error: Address required'); + } + + if (array_key_exists('payment_id', $params)) { + $payment_id = $params['payment_id']; + } + if (array_key_exists('mixin', $params)) { + $mixin = $params['mixin']; + } + if (array_key_exists('account_index', $params)) { + $account_index = $params['account_index']; + } + if (array_key_exists('priority', $params)) { + $priority = $params['priority']; + } + if (array_key_exists('unlock_time', $params)) { + $unlock_time = $params['unlock_time']; + } + if (array_key_exists('below_amount', $params)) { + $below_amount = $params['below_amount']; + } + if (array_key_exists('do_not_relay', $params)) { + $do_not_relay = $params['do_not_relay']; + } + } + + $params = array('address' => $address, 'mixin' => $mixin, 'get_tx_key' => true, 'account_index' => $account_index, 'payment_id' => $payment_id, 'priority' => $priority, 'below_amount' => $this->_transform($below_amount), 'unlock_time' => $unlock_time, 'do_not_relay' => $do_not_relay); + $sweep_single_method = $this->_run('sweep_single', $params); + + $save = $this->store(); // Save wallet state after transfer + + return $sweep_single_method; + } + + /** + * + * Relay a transaction + * + * @param string $hex Blob of transaction to relay + * + * @return object // TODO example + * + */ + public function relay_tx($hex) + { + $params = array('hex' => $hex); + $relay_tx_method = $this->_run('relay_tx_method', $params); + + $save = $this->store(); // Save wallet state after transaction relay + + return $this->_run('relay_tx'); + } + + /** + * + * Save wallet + * + * @param none + * + * @return object Example: + * + */ + public function store() + { + return $this->_run('store'); + } + + /** + * + * Look up incoming payments by payment ID + * + * @param string $payment_id Payment ID to look up + * + * @return object Example: { + * "payments": [{ + * "amount": 10350000000000, + * "block_height": 994327, + * "payment_id": "4279257e0a20608e25dba8744949c9e1caff4fcdafc7d5362ecf14225f3d9030", + * "tx_hash": "c391089f5b1b02067acc15294e3629a463412af1f1ed0f354113dd4467e4f6c1", + * "unlock_time": 0 + * }] + * } + * + */ + public function get_payments($payment_id) + { + // $params = array('payment_id' => $payment_id); // does not work + $params = []; + $params['payment_id'] = $payment_id; + return $this->_run('get_payments', $params); + } + + /** + * + * Look up incoming payments by payment ID (or a list of payments IDs) from a given height + * + * @param array $payment_ids Array of payment IDs to look up + * @param string $min_block_height Height to begin search + * + * @return object Example: { + * "payments": [{ + * "amount": 10350000000000, + * "block_height": 994327, + * "payment_id": "4279257e0a20608e25dba8744949c9e1caff4fcdafc7d5362ecf14225f3d9030", + * "tx_hash": "c391089f5b1b02067acc15294e3629a463412af1f1ed0f354113dd4467e4f6c1", + * "unlock_time": 0 + * }] + * } + * + */ + public function get_bulk_payments($payment_ids, $min_block_height) + { + // $params = array('payment_ids' => $payment_ids, 'min_block_height' => $min_block_height); // does not work + //$params = array('min_block_height' => $min_block_height); // does not work + $params = []; + if (!is_array($payment_ids)) { + throw new Exception('Error: Payment IDs must be array.'); + } + if ($payment_ids) { + $params['payment_ids'] = []; + foreach ($payment_ids as $payment_id) { + $params['payment_ids'][] = $payment_id; + } + } + return $this->_run('get_bulk_payments', $params); + } + + /** + * + * Look up incoming transfers + * + * @param string $type Type of transfer to look up; must be 'all', 'available', or 'unavailable' (incoming transfers which have already been spent) + * @param number $account_index Index of account to look up (optional) + * @param string $subaddr_indices Comma-separated list of subaddress indices to look up (optional) + * + * @return object Example: { + * "transfers": [{ + * "amount": 10000000000000, + * "global_index": 711506, + * "spent": false, + * "tx_hash": "c391089f5b1b02067acc15294e3629a463412af1f1ed0f354113dd4467e4f6c1", + * "tx_size": 5870 + * },{ + * "amount": 300000000000, + * "global_index": 794232, + * "spent": false, + * "tx_hash": "c391089f5b1b02067acc15294e3629a463412af1f1ed0f354113dd4467e4f6c1", + * "tx_size": 5870 + * },{ + * "amount": 50000000000, + * "global_index": 213659, + * "spent": false, + * "tx_hash": "c391089f5b1b02067acc15294e3629a463412af1f1ed0f354113dd4467e4f6c1", + * "tx_size": 5870 + * }] + * } + */ + public function incoming_transfers($type = 'all', $account_index = 0, $subaddr_indices = '') + { + $params = array('transfer_type' => $type, 'account_index' => $account_index, 'subaddr_indices' => $subaddr_indices); + return $this->_run('incoming_transfers', $params); + } + + /** + * + * Look up a wallet key + * + * @param string $key_type Type of key to look up; must be 'view_key', 'spend_key', or 'mnemonic' + * + * @return object Example: { + * "key": "7e341d..." + * } + * + */ + public function query_key($key_type) + { + $params = array('key_type' => $key_type); + return $this->_run('query_key', $params); + } + + /** + * + * Look up wallet view key + * + * @param none + * + * @return object Example: { + * "key": "7e341d..." + * } + * + */ + public function view_key() + { + $params = array('key_type' => 'view_key'); + return $this->_run('query_key', $params); + } + + /** + * + * Look up wallet spend key + * + * @param none + * + * @return object Example: { + * "key": "2ab810..." + * } + * + */ + public function spend_key() + { + $params = array('key_type' => 'spend_key'); + return $this->_run('query_key', $params); + } + + /** + * + * Look up wallet mnemonic seed + * + * @param none + * + * @return object Example: { + * "key": "2ab810..." + * } + * + */ + public function mnemonic() + { + $params = array('key_type' => 'mnemonic'); + return $this->_run('query_key', $params); + } + + /** + * + * Create an integrated address from a given payment ID + * + * @param string $payment_id Payment ID (optional) + * + * @return object Example: { + * "integrated_address": "4BpEv3WrufwXoyJAeEoBaNW56ScQaLXyyQWgxeRL9KgAUhVzkvfiELZV7fCPBuuB2CGuJiWFQjhnhhwiH1FsHYGQQ8H2RRJveAtUeiFs6J" + * } + * + */ + public function make_integrated_address($payment_id = null) + { + $params = array('payment_id' => $payment_id); + return $this->_run('make_integrated_address', $params); + } + + /** + * + * Look up the wallet address and payment ID corresponding to an integrated address + * + * @param string $integrated_address Integrated address to split + * + * @return object Example: { + * "payment_id": "420fa29b2d9a49f5", + * "standard_address": "427ZuEhNJQRXoyJAeEoBaNW56ScQaLXyyQWgxeRL9KgAUhVzkvfiELZV7fCPBuuB2CGuJiWFQjhnhhwiH1FsHYGQGaDsaBA" + * } + * + */ + public function split_integrated_address($integrated_address) + { + $params = array('integrated_address' => $integrated_address); + return $this->_run('split_integrated_address', $params); + } + + /** + * + * Stop the wallet, saving the state + * + * @param none + * + * @return none + * + */ + public function stop_wallet() + { + return $this->_run('stop_wallet'); + } + + /* + * + * Rescan the blockchain from scratch + * + * @param none + * + * @return none + * + */ + + public function rescan_blockchain() + { + return $this->_run('rescan_blockchain'); + } + + /** + * + * Add notes to transactions + * + * @param array $txids Array of transaction IDs to note + * @param array $notes Array of notes (strings) to add + * + * @return none + * + */ + public function set_tx_notes($txids, $notes) + { + $params = array('txids' => $txids, 'notes' => $notes); + return $this->_run('set_tx_notes', $params); + } + + /** + * + * Look up transaction note + * + * @param array $txids Array of transaction IDs (strings) to look up + * + * @return obect Example: { + * // TODO example + * } + * + */ + public function get_tx_notes($txids) + { + $params = array('txids' => $txids); + return $this->_run('get_tx_notes', $params); + } + + /** + * + * Set a wallet option + * + * @param string $key Option to set + * @param string $value Value to set + * + * @return none + * + */ + public function set_attribute($key, $value) + { + $params = array('key' => $key, 'value' => $value); + return $this->_run('set_attribute', $params); + } + + /** + * + * Look up a wallet option + * + * @param string $key Wallet option to query + * + * @return object Example: { + * // TODO example + * } + * + */ + public function get_attribute($key) + { + $params = array('key' => $key); + return $this->_run('get_attribute', $params); + } + + /** + * + * Look up a transaction key + * + * @param string $txid Transaction ID to look up + * + * @return object Example: { + * "tx_key": "e8e97866b1606bd87178eada8f995bf96d2af3fec5db0bc570a451ab1d589b0f" + * } + * + */ + public function get_tx_key($txid) + { + $params = array('txid' => $txid); + return $this->_run('get_tx_key', $params); + } + + /** + * + * Check a transaction key + * + * @param string $address Address that sent transaction + * @param string $txid Transaction ID + * @param string $tx_key Transaction key + * + * @return object Example: { + * "confirmations": 1, + * "in_pool": , + * "received": 0 + * } + * + */ + public function check_tx_key($address, $txid, $tx_key) + { + $params = array('address' => $address, 'txid' => $txid, 'tx_key' => $tx_key); + return $this->_run('check_tx_key', $params); + } + + /** + * + * Create proof (signature) of transaction + * + * @param string $address Address that spent funds + * @param string $txid Transaction ID + * + * @return object Example: { + * "signature": "InProofV1Lq4nejMXxMnAdnLeZhHe3FGCmFdnSvzVM1AiGcXjngTRi4hfHPcDL9D4th7KUuvF9ZHnzCDXysNBhfy7gFvUfSbQWiqWtzbs35yUSmtW8orRZzJpYKNjxtzfqGthy1U3puiF" + * } + * + */ + public function get_tx_proof($address, $txid) + { + $params = array('address' => $address, 'txid' => $txid); + return $this->_run('get_tx_proof', $params); + } + + /** + * + * Verify transaction proof + * + * @param string $address Address that spent funds + * @param string $txid Transaction ID + * @param string $signature Signature (tx_proof) + * + * @return Example: { + * "confirmations": 2, + * "good": 1, + * "in_pool": , + * "received": 15752471409492, + * } + * + */ + public function check_tx_proof($address, $txid, $signature) + { + $params = array('address' => $address, 'txid' => $txid, 'signature' => $signature); + return $this->_run('check_tx_proof', $params); + } + + /** + * + * Create proof of a spend + * + * @param string $txid Transaction ID + * + * @return object Example: { + * "signature": "SpendProofV1RnP6ywcDQHuQTBzXEMiHKbe5ErzRAjpUB1h4RUMfGPNv4bbR6V7EFyiYkCrURwbbrYWWxa6Kb38ZWWYTQhr2Y1cRHVoDBkK9GzBbikj6c8GWyKbu3RKi9hoYp2fA9zze7UEdeNrYrJ3tkoE6mkR3Lk5HP6X2ixnjhUTG65EzJgfCS4qZ85oGkd17UWgQo6fKRC2GRgisER8HiNwsqZdUTM313RmdUX7AYaTUNyhdhTinVLuaEw83L6hNHANb3aQds5CwdKCUQu4pkt5zn9K66z16QGDAXqL6ttHK6K9TmDHF17SGNQVPHzffENLGUf7MXqS3Pb6eijeYirFDxmisZc1n2mh6d5EW8ugyHGfNvbLEd2vjVPDk8zZYYr7NyJ8JjaHhDmDWeLYy27afXC5HyWgJH5nDyCBptoCxxDnyRuAnNddBnLsZZES399zJBYHkGb197ZJm85TV8SRC6cuYB4MdphsFdvSzygnjFtbAcZWHy62Py3QCTVhrwdUomAkeNByM8Ygc1cg245Se1V2XjaUyXuAFjj8nmDNoZG7VDxaD2GT9dXDaPd5dimCpbeDJEVoJXkeEFsZF85WwNcd67D4s5dWySFyS8RbsEnNA5UmoF3wUstZ2TtsUhiaeXmPwjNvnyLif3ASBmFTDDu2ZEsShLdddiydJcsYFJUrN8L37dyxENJN41RnmEf1FaszBHYW1HW13bUfiSrQ9sLLtqcawHAbZWnq4ZQLkCuomHaXTRNfg63hWzMjdNrQ2wrETxyXEwSRaodLmSVBn5wTFVzJe5LfSFHMx1FY1xf8kgXVGafGcijY2hg1yw8ru9wvyba9kdr16Lxfip5RJGFkiBDANqZCBkgYcKUcTaRc1aSwHEJ5m8umpFwEY2JtakvNMnShjURRA3yr7GDHKkCRTSzguYEgiFXdEiq55d6BXDfMaKNTNZzTdJXYZ9A2j6G9gRXksYKAVSDgfWVpM5FaZNRANvaJRguQyqWRRZ1gQdHgN4DqmQ589GPmStrdfoGEhk1LnfDZVwkhvDoYfiLwk9Z2JvZ4ZF4TojUupFQyvsUb5VPz2KNSzFi5wYp1pqGHKv7psYCCodWdte1waaWgKxDken44AB4k6wg2V8y1vG7Nd4hrfkvV4Y6YBhn6i45jdiQddEo5Hj2866MWNsdpmbuith7gmTmfat77Dh68GrRukSWKetPBLw7Soh2PygGU5zWEtgaX5g79FdGZg" + * } + * + */ + public function get_spend_proof($txid, $message=null) + { + $params = array('txid' => $txid); + if( $message !== null ) { + $params['message'] = $message; + } + return $this->_run('get_spend_proof', $params); + } + + /** + * + * Verify spend proof + * + * @param string $txid Transaction ID + * @param string $signature Spend proof to verify + * + * @return object Example: { + * "good": 1 + * } + * + */ + public function check_spend_proof($txid, $signature, $message=null) + { + $params = array('txid' => $txid, 'signature' => $signature); + if( $message !== null ) { + $params['message'] = $message; + } + return $this->_run('check_spend_proof', $params); + } + + /** + * + * Create proof of reserves + * + * @param string $account_index Comma-separated list of account indices of which to prove reserves (proves reserve of all accounts if empty) (optional) + * + * @return Example: { + * "signature": "ReserveProofV11BZ23sBt9sZJeGccf84mzyAmNCP3KzYbE111111111111AjsVgKzau88VxXVGACbYgPVrDGC84vBU61Gmm2eiYxdZULAE4yzBxT1D9epWgCT7qiHFvFMbdChf3CpR2YsZj8CEhp8qDbitsfdy7iBdK6d5pPUiMEwCNsCGDp8AiAc6sLRiuTsLEJcfPYEKe" + * } + * + */ + public function get_reserve_proof($account_index = 'all') + { + if ($account_index == 'all') { + $params = array('all' => true); + } else { + $params = array('account_index' => $account_index); + } + + return $this->_run('get_reserve_proof'); + } + + /** + * + * Verify a reserve proof + * + * @param string $address Wallet address + * @param string $signature Reserve proof + * + * @return object Example: { + * "good": 1, + * "spent": 0, + * "total": 0 + * } + * + */ + public function check_reserve_proof($address, $signature) + { + $params = array('address' => $address, 'signature' => $signature); + return $this->_run('check_reserve_proof', $params); + } + + /** + * + * Look up transfers + * + * @param array $input_types Array of transfer type strings; possible values include 'all', 'in', 'out', 'pending', 'failed', and 'pool' (optional) + * @param number $account_index Index of account to look up (optional) + * @param string $subaddr_indices Comma-separated list of subaddress indices to look up (optional) + * @param number $min_height Minimum block height to use when looking up transfers (optional) + * @param number $max_height Maximum block height to use when looking up transfers (optional) + * + * OR + * + * @param object $inputs_types Array containing any of the options listed above, where only an input types array is required + * + * @return object Example: { + * "pool": [{ + * "amount": 500000000000, + * "fee": 0, + * "height": 0, + * "note": "", + * "payment_id": "758d9b225fda7b7f", + * "timestamp": 1488312467, + * "txid": "da7301d5423efa09fabacb720002e978d114ff2db6a1546f8b820644a1b96208", + * "type": "pool" + * }] + * } + * + */ + public function get_transfers($input_types = ['all'], $account_index = 0, $subaddr_indices = '', $min_height = 0, $max_height = 4206931337) + { + if (is_string($input_types)) { // If user is using old method + $params = array('subaddr_indices' => $subaddr_indices, 'min_height' => $min_height, 'max_height' => $max_height); + if (is_bool($account_index)) { // If user passed eg. get_transfers('in', true) + $params['account_index'] = 0; + $params[$input_types] = $account_index; // $params = array($input_type => $input_value); + } else { // If user passed eg. get_transfers('in') + $params['account_index'] = $account_index; + $params[$input_types] = true; + } + } else { + if (is_object($input_types) || is_array($input_types)) { // Parameters passed in as object/dictionary + $params = $input_types; + + if (array_key_exists('input_types', $params)) { + $input_types = $params['input_types']; + } else { + $input_types = ['all']; + } + if (array_key_exists('account_index', $params)) { + $account_index = $params['account_index']; + } + if (array_key_exists('subaddr_indices', $params)) { + $subaddr_indices = $params['subaddr_indices']; + } + if (array_key_exists('min_height', $params)) { + $min_height = $params['min_height']; + } + if (array_key_exists('max_height', $params)) { + $max_height = $params['max_height']; + } + } + + $params = array('account_index' => $account_index, 'subaddr_indices' => $subaddr_indices, 'min_height' => $min_height, 'max_height' => $max_height); + for ($i = 0, $iMax = count($input_types); $i < $iMax; $i++) { + $params[$input_types[$i]] = true; + } + } + + if (array_key_exists('all', $params)) { + unset($params['all']); + $params['in'] = true; + $params['out'] = true; + $params['pending'] = true; + $params['failed'] = true; + $params['pool'] = true; + } + + if (($min_height || $max_height) && $max_height != 4206931337) { + $params['filter_by_height'] = true; + } + + return $this->_run('get_transfers', $params); + } + + /** + * + * Look up transaction by transaction ID + * + * @param string $txid Transaction ID to look up + * @param string $account_index Index of account to query (optional) + * + * @return object Example: { + * "transfer": { + * "amount": 10000000000000, + * "fee": 0, + * "height": 1316388, + * "note": "", + * "payment_id": "0000000000000000", + * "timestamp": 1495539310, + * "txid": "f2d33ba969a09941c6671e6dfe7e9456e5f686eca72c1a94a3e63ac6d7f27baf", + * "type": "in" + * } + * } + * + */ + public function get_transfer_by_txid($txid, $account_index = 0) + { + $params = array('txid' => $txid, 'account_index' => $account_index); + return $this->_run('get_transfer_by_txid', $params); + } + + /** + * + * Sign a string + * + * @param string $data Data to sign + * + * @return object Example: { + * "signature": "SigV1Xp61ZkGguxSCHpkYEVw9eaWfRfSoAf36PCsSCApx4DUrKWHEqM9CdNwjeuhJii6LHDVDFxvTPijFsj3L8NDQp1TV" + * } + * + */ + public function sign($data) + { + $params = array('string' => $data); + return $this->_run('sign', $params); + } + + /** + * + * Verify a signature + * + * @param string $data Signed data + * @param string $address Address that signed data + * @param string $signature Signature to verify + * + * @return object Example: { + * "good": true + * } + * + */ + public function verify($data, $address, $signature) + { + $params = array('data' => $data, 'address' => $address, 'signature' => $signature); + return $this->_run('verify', $params); + } + + /** + * + * Export an array of signed key images + * + * @param none + * + * @return array Example: { + * // TODO example + * } + * + */ + public function export_key_images() + { + return $this->_run('export_key_images'); + } + + /** + * + * Import a signed set of key images + * + * @param array $signed_key_images Array of signed key images + * + * @return object Example: { + * // TODO example + * height: , + * spent: , + * unspent: + * } + * + */ + public function import_key_images($signed_key_images) + { + $params = array('signed_key_images' => $signed_key_images); + return $this->_run('import_key_images', $params); + } + + /** + * + * Create a payment URI using the official URI specification + * + * @param string $address Address to receive funds + * @param string $amount Amount of monero to request + * @param string $payment_id Payment ID (optional) + * @param string $recipient_name Name of recipient (optional) + * @param string $tx_description Payment description (optional) + * + * @return object Example: { + * // TODO example + * } + * + */ + public function make_uri($address, $amount, $payment_id = null, $recipient_name = null, $tx_description = null) + { + $params = array('address' => $address, 'amount' => $this->_transform($amount), 'payment_id' => $payment_id, 'recipient_name' => $recipient_name, 'tx_description' => $tx_description); + return $this->_run('make_uri', $params); + } + + /** + * + * Parse a payment URI + * + * @param string $uri Payment URI + * + * @return object Example: { + * "uri": { + * "address": "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A", + * "amount": 10, + * "payment_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + * "recipient_name": "Monero Project donation address", + * "tx_description": "Testing out the make_uri function" + * } + * } + * + */ + public function parse_uri($uri) + { + $params = array('uri' => $uri); + return $this->_run('parse_uri', $params); + } + + /** + * + * Look up address book entries + * + * @param array $entries Array of address book entry indices to look up + * + * @return object Example: { + * // TODO example + * } + * + */ + public function get_address_book($entries) + { + $params = array('entries' => $entries); + return $this->_run('get_address_book', $params); + } + + /** + * + * Add entry to the address book + * + * @param string $address Address to add to address book + * @param string $payment_id Payment ID to use with address in address book (optional) + * @param string $description Description of address (optional) + * + * @return object Example: { + * // TODO example + * } + * + */ + public function add_address_book($address, $payment_id, $description) + { + $params = array('address' => $address, 'payment_id' => $payment_id, 'description' => $description); + return $this->_run('add_address_book', $params); + } + + /** + * + * Delete an entry from the address book + * + * @param array $index Index of the address book entry to remove + * + * @return none + * + */ + public function delete_address_book($index) + { + $params = array('index' => $index); + return $this->_run('delete_address_book', $params); + } + + /** + * + * Refresh the wallet after opening + * + * @param int $start_height Block height from which to start (optional) + * + * @return object Example: { + * // TODO example + * } + * + */ + public function refresh($start_height = null) + { + $params = array('start_height' => $start_height); + return $this->_run('refresh', $params); + } + + /** + * + * Rescan the blockchain for spent outputs + * + */ + public function rescan_spent() + { + return $this->_run('rescan_spent'); + } + + /** + * + * Start mining + * + * @param number $threads_count Number of threads with which to mine + * @param boolean $do_background_mining Mine in background? + * @param boolean $ignore_battery Ignore battery? + * + * @return none + * + */ + public function start_mining($threads_count, $do_background_mining, $ignore_battery) + { + $params = array('threads_count' => $threads_count, 'do_background_mining' => $do_background_mining, 'ignore_battery' => $ignore_battery); + return $this->_run('start_mining', $params); + } + + /** + * + * Stop mining + * + * @param none + * + * @return none + * + */ + public function stop_mining() + { + return $this->_run('stop_mining'); + } + + /** + * + * Look up a list of available languages for your wallet's seed + * + * @param none + * + * @return object Example: { + * // TODO example + * } + * + */ + public function get_languages() + { + return $this->_run('get_languages'); + } + + /** + * + * Create a new wallet + * + * @param string $filename Filename of new wallet to create + * @param string $password Password of new wallet to create + * @param string $language Language of new wallet to create + * + * @return none + * + */ + public function create_wallet($filename = 'monero_wallet', $password = null, $language = 'English') + { + $params = array('filename' => $filename, 'password' => $password, 'language' => $language); + return $this->_run('create_wallet', $params); + } + + /** + * + * Open a wallet + * + * @param string $filename Filename of wallet to open + * @param string $password Password of wallet to open + * + * @return none + * + */ + public function open_wallet($filename = 'monero_wallet', $password = null) + { + $params = array('filename' => $filename, 'password' => $password); + return $this->_run('open_wallet', $params); + } + + /** + * + * Check if wallet is multisig + * + * @param none + * + * @return object Example: (non-multisignature wallet) { + * "multisig": , + * "ready": , + * "threshold": 0, + * "total": 0 + * } // TODO multisig wallet example + * + */ + public function is_multisig() + { + return $this->_run('is_multisig'); + } + + /** + * + * Create information needed to create a multisignature wallet + * + * @param none + * + * @return object Example: { + * "multisig_info": "MultisigV1WBnkPKszceUBriuPZ6zoDsU6RYJuzQTiwUqE5gYSAD1yGTz85vqZGetawVvioaZB5cL86kYkVJmKbXvNrvEz7o5kibr7tHtenngGUSK4FgKbKhKSZxVXRYjMRKEdkcbwFBaSbsBZxJFFVYwLUrtGccSihta3F4GJfYzbPMveCFyT53oK" + * } + * + */ + public function prepare_multisig() + { + return $this->_run('prepare_multisig'); + } + + /** + * + * Create a multisignature wallet + * + * @param string $multisig_info Multisignature information (from eg. prepare_multisig) + * @param string $threshold Threshold required to spend from multisignature wallet + * @param string $password Passphrase to apply to multisignature wallet + * + * @return object Example: { + * // TODO example + * } + * + */ + public function make_multisig($multisig_info, $threshold, $password = '') + { + $params = array('multisig_info' => $multisig_info, 'threshold' => $threshold, 'password' => $password); + return $this->_run('make_multisig', $params); + } + + /** + * + * Export multisignature information + * + * @param none + * + * @return object Example: { + * // TODO example + * } + * + */ + public function export_multisig_info() + { + return $this->_run('export_multisig_info'); + } + + /** + * + * Import mutlisignature information + * + * @param string $info Multisignature info (from eg. prepare_multisig) + * + * @return Example: { + * // TODO example + * } + * + */ + public function import_multisig_info($info) + { + $params = array('info' => $info); + return $this->_run('import_multisig_info', $params); + } + + /** + * + * Finalize a multisignature wallet + * + * @param string $multisig_info Multisignature info (from eg. prepare_multisig) + * @param string $password Multisignature info (from eg. prepare_multisig) + * + * @return Example: { + * // TODO example + * } + * + */ + public function finalize_multisig($multisig_info, $password = '') + { + $params = array('multisig_info' => $multisig_info, 'password' => $password); + return $this->_run('finalize_multisig', $params); + } + + /** + * + * Sign a multisignature transaction + * + * @param string $tx_data_hex Blob of transaction to sign + * + * @return object Example: { + * // TODO example + * } + * + */ + public function sign_multisig($tx_data_hex) + { + $params = array('tx_data_hex' => $tx_data_hex); + return $this->_run('sign_multisig', $params); + } + + /** + * + * Submit (relay) a multisignature transaction + * + * @param string $tx_data_hex Blob of transaction to submit + * + * @return Example: { + * // TODO example + * } + * + */ + public function submit_multisig($tx_data_hex) + { + $params = array('tx_data_hex' => $tx_data_hex); + return $this->_run('submit_multisig', $params); + } + + /** + * @return jsonRPCClient + */ + public function get_client() + { + return $this->client; + } + + /** + * @return jsonRPCClient + */ + public function getClient() + { + return $this->client; + } + + /** + * + * Validate a wallet address + * + * @param address - string; The address to validate. + * any_net_type - boolean (Optional); If true, consider addresses belonging to any of the three Monero networks (mainnet, stagenet, and testnet) valid. Otherwise, only consider an address valid if it belongs to the network on which the rpc-wallet's current daemon is running (Defaults to false). + * allow_openalias - boolean (Optional); If true, consider OpenAlias-formatted addresses valid (Defaults to false). + * + * @return valid - boolean; True if the input address is a valid Monero address. + * integrated - boolean; True if the given address is an integrated address. + * subaddress - boolean; True if the given address is a subaddress + * nettype - string; Specifies which of the three Monero networks (mainnet, stagenet, and testnet) the address belongs to. + * openalias_address - boolean; True if the address is OpenAlias-formatted. + * + */ + public function validate_address($address, $strict_nettype = false, $allow_openalias = false) + { + $params = array( + 'address' => $address, + 'any_net_type' => $strict_nettype, + 'allow_openalias' => $allow_openalias + ); + return $this->_run('validate_address', $params); + } + + /** + * + * Create a wallet on the RPC server from an address, view key, and (optionally) spend key. + * + * @param filename is the name of the wallet to create on the RPC server + * @param password is the password encrypt the wallet + * @param address is the address of the wallet to construct + * @param viewKey is the view key of the wallet to construct + * @param spendKey is the spend key of the wallet to construct or null to create a view-only wallet + * @param language is the wallet and mnemonic's language (default = "English") + * @param restoreHeight is the block height to restore (i.e. scan the chain) from (default = 0) + * @param saveCurrent specifies if the current RPC wallet should be saved before being closed (default = true) + * + * @return TODO + * + */ + public function generate_from_keys($filename, $password, $address, $viewKey, $spendKey = '', $language = 'English', $restoreHeight = 0, $saveCurrent = true) + { + $params = array( + 'filename' => $filename, + 'password' => $password, + 'address' => $address, + 'viewkey' => $viewKey, + 'spendkey' => $spendKey, + 'language' => $language, + 'restore_height' => $restoreHeight, + 'autosave_current' => $saveCurrent + ); + return $this->_run('generate_from_keys', $params); + } + + /** + * + * Exchange mutlisignature information + * + * @param password wallet password + * @param multisig_info info (from eg. prepare_multisig) + * + */ + public function exchange_multisig_keys($password, $multisig_info) + { + $params = array( + 'password' => $password, + 'multisig_info' => $multisig_info + ); + return $this->_run('exchange_multisig_keys', $params); + } + + /** + * + * Obtain information (destination, amount) about a transfer + * + * @param txinfo txinfo + * + */ + public function describe_transfer($txinfo) + { + $params = array( + 'multisig_txset' => $txinfo, + ); + return $this->_run('describe_transfer', $params); + } + + /** + * Export all outputs in hex format + */ + public function export_outputs() + { + return $this->_run('export_outputs'); + } + + /** + * + * Import outputs in hex format + * + * @param outputs_data_hex wallet outputs in hex format + * + * + */ + public function import_outputs($outputs_data_hex) + { + $params = array( + 'outputs_data_hex' => $outputs_data_hex, + ); + return $this->_run('import_outputs', $params); + } + + /** + * Set whether and how often to automatically refresh the current wallet + * + * @param enable Enable or disable automatic refreshing (default = true) + * @param period The period of the wallet refresh cycle (i.e. time between refreshes) in seconds + * + */ + public function auto_refresh($enable = true, $period = 10) + { + $params = array( + 'enable' => $enable, + 'period' => $period + ); + return $this->_run('auto_refresh', $params); + } + + /** + * Change a wallet password + * + * @param old_password old password or blank + * @param new_password new password or blank + */ + public function change_wallet_password($old_password = '', $new_password = '') + { + $params = array( + 'old_password' => $old_password, + 'new_password' => $new_password + ); + return $this->_run('change_wallet_password', $params); + } + + /** + * Close wallet + */ + public function close_wallet() + { + return $this->_run('close_wallet'); + } + + /** + * Get RPC version Major & Minor integer-format, where Major is the first 16 bits and Minor the last 16 bits. + */ + public function get_version() + { + return $this->_run('get_version'); + } +} diff --git a/vendor/monero-integrations/monerophp/src/wordsets/chinese_simplified.ws.php b/vendor/monero-integrations/monerophp/src/wordsets/chinese_simplified.ws.php new file mode 100644 index 0000000..bb0c943 --- /dev/null +++ b/vendor/monero-integrations/monerophp/src/wordsets/chinese_simplified.ws.php @@ -0,0 +1,1665 @@ +setHost(self::$host) + ->setPort(self::$port) + ->setUsername(self::$username) + ->setPassword(self::$password); + + $client = new XmppClient($options); + $client->connect(); + + $client->iq->getRoster(); + + $client->message->send('Hello world', 'test@jabber.com'); + + // Uncomment if you want to manually enter raw XML (or call a function) and see a server response +// (new self)->sendRawXML($client); + + do { + $response = $client->getResponse(); + $client->prettyPrint($response); + } while (true); + + $client->disconnect(); + } + + public function sendRawXML(XmppClient $client) + { + do { + $response = $client->getResponse(); + $client->prettyPrint($response); + + // if you provide a function name here, (i.e. getRoster ...arg) + // the function will be called instead of sending raw XML + $line = readline("\nEnter XML: "); + + if ($line == 'exit') { + break; + } + + $parsedLine = explode(' ', $line); + + if (method_exists($client, $parsedLine[0])) { + if (count($parsedLine) < 2) { + $client->{$parsedLine[0]}(); + continue; + } + $client->{$parsedLine[0]}($parsedLine[1]); + continue; + } + + if (@simplexml_load_string($line)) { + $client->send($line); + continue; + } + + echo "This is not a method nor a valid XML"; + } while ($line != 'exit'); + } +} + +ini_set('display_errors', 1); +ini_set('display_startup_errors', 1); +error_reporting(E_ALL); + +require __DIR__ . '/vendor/autoload.php'; +Example::test(); diff --git a/vendor/norgul/xmpp-php/LICENSE b/vendor/norgul/xmpp-php/LICENSE new file mode 100644 index 0000000..1b955a0 --- /dev/null +++ b/vendor/norgul/xmpp-php/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Dupor Marko + +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/vendor/norgul/xmpp-php/README.md b/vendor/norgul/xmpp-php/README.md new file mode 100644 index 0000000..d766929 --- /dev/null +++ b/vendor/norgul/xmpp-php/README.md @@ -0,0 +1,280 @@ +# PHP client library for XMPP (Jabber) protocol + +[![Latest Stable Version](https://poser.pugx.org/norgul/xmpp-php/v/stable)](https://packagist.org/packages/norgul/xmpp-php) +[![Total Downloads](https://poser.pugx.org/norgul/xmpp-php/downloads)](https://packagist.org/packages/norgul/xmpp-php) +[![Latest Unstable Version](https://poser.pugx.org/norgul/xmpp-php/v/unstable)](https://packagist.org/packages/norgul/xmpp-php) +[![Build Status](https://travis-ci.org/Norgul/xmpp-php.svg?branch=master)](https://travis-ci.org/Norgul/xmpp-php) +[![License](https://poser.pugx.org/norgul/xmpp-php/license)](https://packagist.org/packages/norgul/xmpp-php) + +This is low level socket implementation for enabling PHP to +communicate with XMPP due to lack of such libraries online (at least ones I +could find that had decent documentation). + +XMPP core documentation can be found [here](https://xmpp.org/rfcs/rfc6120.html). + +# Installation requirements and example + +Project requirements are given in `composer.json` ( +[Composer website](https://getcomposer.org)): + +You can use this library in your project by running: + +``` +composer require norgul/xmpp-php +``` + +You can see usage example in `Example.php` file by changing credentials to +point to your XMPP server and from project root run `php Example.php`. + +# Library usage +## Initialization +In order to start using the library you first need to instantiate a new `Options` +class. Host, username and password are mandatory fields, while port number, if omitted, +will default to `5222` which is XMPP default. + +Username can be either bare `JID` or in `JID/resource` form. If you are using a bare `JID` +the resource will be added automatically. You can override this by explicitly setting a +resource with `$client->iq->setResource()`. In the second case the username will be automatically +parsed to `username` and `resource` variables. In case of `JID/resource/xyz` format, everything +after second slash will be ignored. If both `JID/resource` is present as well as using the +`$client->iq->setResource()` method, which ever was defined last will take precedence. + +``` +$options = new Options(); + +$options + ->setHost($host) // required + ->setPort($port) // not required, defaults to 5222 + ->setUsername($username) // required + ->setPassword($password); // required +``` + +`Options` object is required for establishing the connection and every other subsequent +request, so once set it should not be changed. + +Once this is set you can instantiate a new `XmppClient` object and pass the `Options` object in. + +## XMPP client class explanation +Since XMPP is all about 3 major stanzas, (**IQ, Message and Presence**), I've +created separate classes which are dependant on socket implementation so that you +can directly send XML by calling a stanza method. + +This means that 3 stanzas have been made available from `XmppClient` class constructor +to be used like a chained method on client's concrete class. + +Current logic thus is `$client->STANZA->METHOD()`. For example: + +``` +$client->iq->getRoster(); +$client->message->send(); +$client->presence->subscribe(); +``` + +## Connecting to the server +Beside being a stanza wrapper, `XmppClient` class offers a few public methods. + +`$client->connect()` method does a few things: +1. Connects to the socket which was initialized in `XmppClient` constructor +2. Opens an XML stream to exchange with the XMPP server +3. Tries to authenticate with the server based on provided credentials +4. Starts the initial communication with the server, bare minimum to get you started + +Current version supports `PLAIN` and `DIGEST-MD5` auth methods. + +TLS is supported by default. If server has support for TLS, library will +automatically try to connect with TLS and make the connection secure. + +If you'd like to explicitly disable this functionality, you can use `setUseTls(false)` +function on the `Options` instance so that TLS communication is disabled. Note +that this will function in environments where TLS is supported but not required. +If TLS is required, program will connect to it independently of the option you +set. + +## Sending raw data +`send()` message is exposed as being public in `XmppClient` class, and its intention +is to send raw XML data to the server. For it to work correctly, XML which you send +has to be valid XML. + +## Getting raw response +Server responses (or server side continuous XML session to be exact) can be retrieved with +`$client->getResponse()`. This should be used in an infinite loop or for more sustainable +solution in some WebSocket solution like [Ratchet](http://socketo.me/) if you'd like to +see continuous stream of everything coming from the server. + +If you would like to see the output of the received response in the console you can call the +`$client->prettyPrint($response)` method. + +## Receiving messages and other responses + +In case you are not interested in complete response which comes from server, you may also use +`$client->message->receive()` (`$client->getMessages()` was removed because it was just a shorthand +method for this one) which will match message tags with regex and return array of matched messages. +In case you'd like to see the response in the terminal, you can do something like this: + +``` +do { + $response = $client->message->receive(); + if($response) + echo print_r($response); +} while (true); +``` + +## Disconnect +Disconnect method sends closing XML to the server to end the currently open session and +closes the open socket. + +# Stanza method breakdown +Remember from [here](#xmpp-client-class-explanation) -> `$client->STANZA->METHOD()` + +## Message +`send()` - sending a message to someone. Takes 3 parameters of which the last one is +optional. First parameter is the actual message you'd like to send (body), second +one is recipient of the message and third one is type of message to be sent. This +defaults to `chat`. + +You can find possible types in [this RFC document](https://xmpp.org/rfcs/rfc3921.html#stanzas) + +`receive()` - covered in [this section](#receiving-messages-and-other-responses) + +## IQ +`getRoster()` - takes no arguments and fetches current authenticated user roster. + +`setGroup()` - puts a given user in group you provide. Method takes two arguments: +first one being the group name which you will attach to given user, and other +being JID of that user. + +## Presence + +`setPriority()` - sets priority for given resource. First argument is an integer +`-128 <> 127`. If no second argument is given, priority will be set for currently used resource. +Other resource can be provided as a second argument whereas the priority will be set for that +specific resource. + +`subscribe()` - takes JID as an argument and asks that user for presence. + +`acceptSubscription()` - takes JID as an argument and accepts presence from that user. + +`declineSubscription()` - takes JID as an argument and declines presence from that user. + +## Sessions + +Sessions are currently being used only to differentiate logs if multiple connections +are being made. + +`XmppClient` class takes in second optional parameter `$sessionId` to which you can +forward session ID from your system, or it will be assigned automatically. + +You can disable sessions through `Options` object (`$options->setSessionManager(false)`), +as they can cause collision with already established sessions if being used inside +frameworks or similar. Needless to say if this is disabled, forwarding a second parameter +to `XmppClient` will not establish a new session. + +# More options (not required) + +`Options` object can take more options which may be chained but are not required. These are explained +and commented in the code directly in the `Options` class: + +``` +$options + ->setProtocol($protocol) // defaults to TCP + ->setResource($resource) // defaults to 'norgul_machine' string + timestamp + ->setLogger($logger) // logger instance (logging explained below) + ->setAuthType($authType) // Takes on classes which implement Authenticable +``` + +## Socket options +Most of the socket options are set by default so there is no need to temper +with this class, however you can additionally change the timeout for the period +the socket will be alive when doing a `socket_read()`, and you can do that with +`$socket->setTimeout()`. + +## Logging + +Upon new established session the library is creating a `xmpp.log` log file in `logs/` folder: + +You can manually set logger when instantiating `Options` with `setLogger($logger)`. The method accepts +any object which implements `Loggable` interface so you can create your own implementation. + +# Other + +`Example.php` has a `sendRawXML()` method which can be helpful with debugging. Method works in a way +that you can provide hand-written XML and send it to the server. On the other hand you can +also trigger a method by providing method name instead of XML. + +``` +Enter XML: foo <-- will send XML +Enter XML: getRoster <-- will run getRoster() method +Enter XML: requestPresence x@x.com <-- will run with argument requestPresence(x@x.com) +``` + +Some valid XMPP XML will be declined (like sending ``) because `simplexml_load_string()` +is not able to parse it as being a valid XML. In cases you need to do some custom stuff like +that and you are sure it is a XMPP valid XML, you can remove the parsing line and just let the +`send()` method do its magic. + + **Be aware! Be very aware!** sending an invalid XML to the server +will probably invalidate currently open XML session and you will probably need to restart the +script. + +# Dev documentation +For anyone willing to contribute, a quick breakdown of the structure: + +--- +- `Options.php` - everything that is variable about the library +- `Socket.php` - socket related implementation (connecting, reading, writing etc.) +- `XmppClient.php` - user friendly methods to interact with the library and stanza wrapper enabling users to call stanza methods through +instantiated class. This should contain as little logic as possible, turns out it's not so easy :) +--- +- `AuthTypes` - contains methods to authenticate yourself to XMPP server. Besides concrete implementations there is also an +abstract class with minor logic to avoid duplication and interface having all the necessary methods should the need for new +auth type arise. + +- `Buffers` - implementation of buffer (or should I say a simple array) which gets filled when socket is calling the `receive()` +method, and it flushes on any read, which happens when calling `getResponse()` method for example. A brief history of why: I had +issues when a non-recoverable error would be thrown. In this situation I had to do 2 things: try to reconnect, show the error +to the user. The thing is that `getResponse()` returns string, and in case of reconnection the program execution would continue +returning either nothing or returning error string after the server already connected for the second time, thus misinforming the +user of the error which occurred before reconnection. Buffer was born. + +- `Exceptions` - this is more or less a standard. I am just overriding constructors so I can get my message in. + +- `Loggers` - containing logic to store logs to the `logs/xmpp.log` file. The idea was to keep several log types inside (full, simple, +no logger), but I found the one made to be sufficient. + +--- + +`Xml` + +- `Xml.php` - a trait consisting of way too many regex matching. This should be reformatted. +- `Stanzas` - main logic for all stanza communication with the server. This used to be just plain XML, but I have decided to +forward a socket dependency inside so that when you call the method, you actually also send it to the server. That used to be +something like `$this->socket->send($this->iq->getRoster())` which is correct from the programming perspective, but for the +simplicity sake, I like the `$client->iq->getRoster()` more. I'm open to other suggestions. + +--- + +## CI +Continuous integration is done through [Travis CI](https://travis-ci.org/), and each push goes through a process which is currently +as simple as: +- check unit tests (and god knows I have them) +- check for syntax errors +- run `phpcs` (configuration in `phpcs.xml`) +- run `phpmd` (configuration in `phpmd.xml`) + +## TODO's + +- **unit testing** - unfortunately I have been prolonging this for far too long, maybe there is a good soul out there who enjoys +writing tests. +- **throttling** - when an unrecoverable error occurs (currently I am catching `` ones which break the stream) +reconnect is being made automatically. In case this is happening over and over again, program will try connecting indefinitely, +which is fine to any aspect except logs which will get clogged. I would like to throttle the connection so that it increases the +connection time each time it is unsuccessful. Problem here is that I can only catch the error when getting the response, and +response can be successful on the first XML exchange (for example when you send opening stream request), while breaking on the +second request. With this in mind my only idea was to implement throttling with timestamps or something. +- **sessions** - I presume this part is working correctly but should be tested from a framework +- **multiple connections** - I think this part works fine, but I am worried that triggering `getRoster()` while simultaneously +fetching a message may delete one server response. If you get in one batch both roster and message, it will be added to the buffer. +Calling back the response will get either roster or message, not both. And then buffer will be flushed. This is something that +needs thinking. +- **structure of XmppClient** - in order to enable the `$client->stanza->method` I need to instantiate all stanzas within the +class. I feel as this could be simplified. diff --git a/vendor/norgul/xmpp-php/composer.json b/vendor/norgul/xmpp-php/composer.json new file mode 100644 index 0000000..129c4d4 --- /dev/null +++ b/vendor/norgul/xmpp-php/composer.json @@ -0,0 +1,32 @@ +{ + "name": "norgul/xmpp-php", + "description": "PHP library for XMPP.", + "keywords": [ + "php", + "xmpp", + "jabber", + "library" + ], + "license": "MIT", + "authors": [ + { + "name": "Marko Dupor", + "email": "marko.dupor@gmail.com" + } + ], + "type": "project", + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "6.*", + "phpmd/phpmd": "2.6.0", + "squizlabs/php_codesniffer": "3.4.2", + "friendsofphp/php-cs-fixer": "3.0.x-dev" + }, + "autoload": { + "psr-4": { + "Norgul\\Xmpp\\": "src/" + } + } +} diff --git a/vendor/norgul/xmpp-php/phpcs.xml b/vendor/norgul/xmpp-php/phpcs.xml new file mode 100644 index 0000000..fdb81af --- /dev/null +++ b/vendor/norgul/xmpp-php/phpcs.xml @@ -0,0 +1,23 @@ + + + + + A custom coding standard + + + ./src + + */tests/* + */vendor/* + */.idea/* + */Example* + + + + + + + + + + diff --git a/vendor/norgul/xmpp-php/phpmd.xml b/vendor/norgul/xmpp-php/phpmd.xml new file mode 100644 index 0000000..8a60cbc --- /dev/null +++ b/vendor/norgul/xmpp-php/phpmd.xml @@ -0,0 +1,31 @@ + + + + PHP mess detector rules + + + + + + + + + + + + + + + 3 + + + + + + \ No newline at end of file diff --git a/vendor/norgul/xmpp-php/phpunit.xml b/vendor/norgul/xmpp-php/phpunit.xml new file mode 100644 index 0000000..5e7ba30 --- /dev/null +++ b/vendor/norgul/xmpp-php/phpunit.xml @@ -0,0 +1,12 @@ + + + + tests + + + \ No newline at end of file diff --git a/vendor/norgul/xmpp-php/src/AuthTypes/Authenticable.php b/vendor/norgul/xmpp-php/src/AuthTypes/Authenticable.php new file mode 100644 index 0000000..3e7884c --- /dev/null +++ b/vendor/norgul/xmpp-php/src/AuthTypes/Authenticable.php @@ -0,0 +1,13 @@ +options = $options; + } + + public function getName(): string + { + return $this->name; + } +} diff --git a/vendor/norgul/xmpp-php/src/AuthTypes/DigestMD5.php b/vendor/norgul/xmpp-php/src/AuthTypes/DigestMD5.php new file mode 100644 index 0000000..900101b --- /dev/null +++ b/vendor/norgul/xmpp-php/src/AuthTypes/DigestMD5.php @@ -0,0 +1,14 @@ +options->getUsername()}\x00{$this->options->getPassword()}"; + return self::quote(sha1($credentials)); + } +} diff --git a/vendor/norgul/xmpp-php/src/AuthTypes/Plain.php b/vendor/norgul/xmpp-php/src/AuthTypes/Plain.php new file mode 100644 index 0000000..b87d658 --- /dev/null +++ b/vendor/norgul/xmpp-php/src/AuthTypes/Plain.php @@ -0,0 +1,14 @@ +options->getUsername()}\x00{$this->options->getPassword()}"; + return self::quote(base64_encode($credentials)); + } +} diff --git a/vendor/norgul/xmpp-php/src/Buffers/Buffer.php b/vendor/norgul/xmpp-php/src/Buffers/Buffer.php new file mode 100644 index 0000000..d0278f6 --- /dev/null +++ b/vendor/norgul/xmpp-php/src/Buffers/Buffer.php @@ -0,0 +1,17 @@ +response[] = $data; + } + } + + public function read() + { + $implodedResponse = $this->response ? implode('', $this->response) : ''; + $this->flush(); + return $implodedResponse; + } + + protected function flush() + { + $this->response = []; + } +} diff --git a/vendor/norgul/xmpp-php/src/Exceptions/DeadSocket.php b/vendor/norgul/xmpp-php/src/Exceptions/DeadSocket.php new file mode 100644 index 0000000..2b12af4 --- /dev/null +++ b/vendor/norgul/xmpp-php/src/Exceptions/DeadSocket.php @@ -0,0 +1,17 @@ +createLogFile(); + $this->log = fopen(self::LOG_FOLDER . '/' . self::LOG_FILE, 'a'); + } + + protected function createLogFile(): void + { + if (!file_exists(self::LOG_FOLDER)) { + mkdir(self::LOG_FOLDER, 0777, true); + } + } + + public function log($message) + { + $this->writeToLog($message); + } + + public function logRequest($message) + { + $this->writeToLog($message, "REQUEST"); + } + + public function logResponse($message) + { + $this->writeToLog($message, "RESPONSE"); + } + + public function error($message) + { + $this->writeToLog($message, "ERROR"); + } + + protected function writeToLog($message, $type = ''): void + { + $prefix = date("Y.m.d H:m:s") . " " . session_id() . ($type ? " {$type}::" : " "); + $this->writeToFile($this->log, $prefix . "$message\n"); + } + + protected function writeToFile($file, $message) + { + try { + fwrite($file, $message); + } catch (Exception $e) { + // silent fail + } + } + + public function getFilePathFromResource($resource): string + { + $metaData = stream_get_meta_data($resource); + return $metaData["uri"]; + } +} diff --git a/vendor/norgul/xmpp-php/src/Options.php b/vendor/norgul/xmpp-php/src/Options.php new file mode 100644 index 0000000..670afb7 --- /dev/null +++ b/vendor/norgul/xmpp-php/src/Options.php @@ -0,0 +1,218 @@ +host) { + $this->getLogger()->error(__METHOD__ . '::' . __LINE__ . + " No host found, please set the host variable"); + throw new InvalidArgumentException(); + } + + return $this->host; + } + + public function setHost(string $host): Options + { + $this->host = trim($host); + return $this; + } + + public function getPort() + { + return $this->port; + } + + public function setPort(int $port): Options + { + $this->port = $port; + return $this; + } + + public function getUsername() + { + if (!$this->username) { + $this->getLogger()->error(__METHOD__ . '::' . __LINE__ . + " No username found, please set the username variable"); + throw new InvalidArgumentException(); + } + + return $this->username; + } + + /** + * Try to assign a resource if it exists. If bare JID is forwarded, this will default to your username + * + * @param string $username + * @return Options + */ + public function setUsername(string $username): Options + { + $usernameResource = explode('/', $username); + + if (count($usernameResource) > 1) { + $this->setResource($usernameResource[1]); + $username = $usernameResource[0]; + } + + $this->username = trim($username); + + return $this; + } + + public function getPassword() + { + if (!$this->password) { + $this->getLogger()->error(__METHOD__ . '::' . __LINE__ . + " No password found, please set the password variable"); + throw new InvalidArgumentException(); + } + + return $this->password; + } + + public function setPassword(string $password): Options + { + $this->password = $password; + return $this; + } + + public function getResource() + { + if (!$this->resource) { + $this->resource = 'norgul_machine_' . time(); + } + + return $this->resource; + } + + public function setResource(string $resource): Options + { + $this->resource = trim($resource); + return $this; + } + + public function getProtocol() + { + return $this->protocol; + } + + public function setProtocol(string $protocol) + { + $this->protocol = $protocol; + return $this; + } + + public function fullSocketAddress() + { + $protocol = $this->getProtocol(); + $host = $this->getHost(); + $port = $this->getPort(); + + return "$protocol://$host:$port"; + } + + public function fullJid() + { + $username = $this->getUsername(); + $resource = $this->getResource(); + $host = $this->getHost(); + + return "$username@$host/$resource"; + } + + public function bareJid() + { + $username = $this->getUsername(); + $host = $this->getHost(); + + return "$username@$host"; + } + + public function setLogger(Loggable $logger) + { + $this->logger = $logger; + } + + public function getLogger() + { + if (!$this->logger) { + $this->logger = new Logger(); + } + + return $this->logger; + } + + public function setUseTls(bool $enable) + { + $this->useTls = $enable; + } + + public function usingTls(): bool + { + return $this->useTls; + } + + public function getAuthType() + { + if (!$this->authType) { + $this->setAuthType(new Plain($this)); + } + + return $this->authType; + } + + public function setAuthType(Authenticable $authType) + { + $this->authType = $authType; + return $this; + } +} diff --git a/vendor/norgul/xmpp-php/src/Socket.php b/vendor/norgul/xmpp-php/src/Socket.php new file mode 100644 index 0000000..4852fd0 --- /dev/null +++ b/vendor/norgul/xmpp-php/src/Socket.php @@ -0,0 +1,111 @@ +responseBuffer = new Response(); + $this->connection = stream_socket_client($options->fullSocketAddress()); + + if (!$this->isAlive($this->connection)) { + throw new DeadSocket(); + } + + //stream_set_blocking($this->connection, true); + stream_set_timeout($this->connection, 0, $this->timeout); + $this->options = $options; + } + + public function disconnect() + { + fclose($this->connection); + } + + /** + * Sending XML stanzas to open socket + * @param $xml + */ + public function send(string $xml) + { + try { + fwrite($this->connection, $xml); + $this->options->getLogger()->logRequest(__METHOD__ . '::' . __LINE__ . " $xml"); + //$this->checkSocketStatus(); + } catch (Exception $e) { + $this->options->getLogger()->error(__METHOD__ . '::' . __LINE__ . " fwrite() failed " . $e->getMessage()); + return; + } + + $this->receive(); + } + + public function receive() + { + $response = ''; + while ($out = fgets($this->connection)) { + $response .= $out; + } + + if (!$response) { + return; + } + + $this->responseBuffer->write($response); + $this->options->getLogger()->logResponse(__METHOD__ . '::' . __LINE__ . " $response"); + } + + protected function isAlive($socket) + { + return $socket !== false; + } + + public function setTimeout($timeout) + { + $this->timeout = $timeout; + } + + public function getResponseBuffer(): Response + { + return $this->responseBuffer; + } + + public function getOptions(): Options + { + return $this->options; + } + + protected function checkSocketStatus(): void + { + $status = socket_get_status($this->connection); + + //echo print_r($status); + + if ($status['eof']) { + $this->options->getLogger()->logResponse( + __METHOD__ . '::' . __LINE__ . + " ---Probably a broken pipe, restart connection\n" + ); + } + } +} diff --git a/vendor/norgul/xmpp-php/src/Xml/Stanzas/Auth.php b/vendor/norgul/xmpp-php/src/Xml/Stanzas/Auth.php new file mode 100644 index 0000000..a6df778 --- /dev/null +++ b/vendor/norgul/xmpp-php/src/Xml/Stanzas/Auth.php @@ -0,0 +1,49 @@ +socket->getResponseBuffer()->read(); + $options = $this->socket->getOptions(); + + $tlsSupported = self::isTlsSupported($response); + $tlsRequired = self::isTlsRequired($response); + + if ($tlsSupported && ($tlsRequired || (!$tlsRequired && $options->usingTls()))) { + $this->startTls(); + $this->socket->send(self::openXmlStream($options->getHost())); + } + + $xml = $this->generateAuthXml($options->getAuthType()); + $this->socket->send($xml); + $this->socket->send(self::openXmlStream($options->getHost())); + } + + protected function startTls() + { + $this->socket->send(""); + $response = $this->socket->getResponseBuffer()->read(); + + if (!self::canProceed($response)) { + $this->socket->getOptions()->getLogger()->error(__METHOD__ . '::' . __LINE__ . + " TLS authentication failed. Trying to continue but will most likely fail."); + return; + } + + stream_socket_enable_crypto($this->socket->connection, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT); + } + + protected function generateAuthXml(Authenticable $authType): string + { + $mechanism = $authType->getName(); + $encodedCredentials = $authType->encodedCredentials(); + $nameSpace = "urn:ietf:params:xml:ns:xmpp-sasl"; + + return "{$encodedCredentials}"; + } +} diff --git a/vendor/norgul/xmpp-php/src/Xml/Stanzas/Iq.php b/vendor/norgul/xmpp-php/src/Xml/Stanzas/Iq.php new file mode 100644 index 0000000..03f4244 --- /dev/null +++ b/vendor/norgul/xmpp-php/src/Xml/Stanzas/Iq.php @@ -0,0 +1,97 @@ +"; + $xml = "{$query}"; + + $this->socket->send($xml); + } + + public function addToRoster(string $name, string $forJid, string $from, string $groupName = null) + { + $group = $groupName ? "{$groupName}" : null; + $item = "{$group}"; + $query = "{$item}"; + $xml = "{$query}"; + + $this->socket->send($xml); + } + + public function removeFromRoster(string $jid, string $myJid) + { + + $item = ""; + $query = "{$item}"; + $xml = "{$query}"; + + $this->socket->send($xml); + } + + public function setResource(string $name) + { + if (!trim($name)) { + return; + } + + $resource = "{$name}"; + $bind = "{$resource}"; + $xml = "{$bind}"; + + $this->socket->send($xml); + } + + public function setGroup(string $name, string $forJid) + { + $group = "{$name}"; + $item = "{$group}"; + $query = "{$item}"; + $xml = "{$query}"; + + $this->socket->send($xml); + } + + public function getServerVersion() + { + $query = ""; + $xml = "{$query}"; + + $this->socket->send($xml); + } + + public function getServerFeatures() + { + $query = ""; + $xml = "{$query}"; + + $this->socket->send($xml); + } + + public function getServerTime() + { + $query = ""; + $xml = "{$query}"; + + $this->socket->send($xml); + } + + public function getFeatures(string $forJid) + { + $query = ""; + $xml = "{$query}"; + + $this->socket->send($xml); + } + + public function ping() + { + $query = ""; + $xml = "{$query}"; + + $this->socket->send($xml); + } +} diff --git a/vendor/norgul/xmpp-php/src/Xml/Stanzas/Message.php b/vendor/norgul/xmpp-php/src/Xml/Stanzas/Message.php new file mode 100644 index 0000000..e6aebd0 --- /dev/null +++ b/vendor/norgul/xmpp-php/src/Xml/Stanzas/Message.php @@ -0,0 +1,29 @@ +generateMessageXml($body, $to, $type); + $this->socket->send($xml); + } + + public function receive() + { + $this->socket->receive(); + $rawResponse = $this->socket->getResponseBuffer()->read(); + return self::parseTag($rawResponse, "message"); + } + + protected function generateMessageXml(string $body, string $to, string $type): string + { + $to = self::quote($to); + $body = self::quote($body); + + $bodyXml = "{$body}"; + + return "{$bodyXml}"; + } +} diff --git a/vendor/norgul/xmpp-php/src/Xml/Stanzas/Presence.php b/vendor/norgul/xmpp-php/src/Xml/Stanzas/Presence.php new file mode 100644 index 0000000..5578763 --- /dev/null +++ b/vendor/norgul/xmpp-php/src/Xml/Stanzas/Presence.php @@ -0,0 +1,66 @@ +setPresence($to, 'subscribe'); + } + + public function unsubscribe(string $from) + { + $this->setPresence($from, 'unsubscribe'); + } + + public function acceptSubscription(string $from) + { + $this->setPresence($from, 'subscribed'); + } + + public function declineSubscription(string $from) + { + $this->setPresence($from, 'unsubscribed'); + } + + protected function setPresence(string $to, string $type = "subscribe") + { + $xml = ""; + $this->socket->send($xml); + } + + /** + * Set priority to current resource by default, or optional other resource tied to the + * current username + * @param int $value + * @param string|null $forResource + */ + public function setPriority(int $value, string $forResource = null) + { + $from = self::quote($this->socket->getOptions()->fullJid()); + + if ($forResource) { + $from = $this->socket->getOptions()->getUsername() . "/$forResource"; + } + + $priority = "{$this->limitPriority($value)}"; + $xml = "{$priority}"; + + $this->socket->send($xml); + } + + protected function limitPriority(int $value): int + { + if ($value > self::PRIORITY_UPPER_BOUND) { + return self::PRIORITY_UPPER_BOUND; + } elseif ($value < self::PRIORITY_LOWER_BOUND) { + return self::PRIORITY_LOWER_BOUND; + } + + return $value; + } +} diff --git a/vendor/norgul/xmpp-php/src/Xml/Stanzas/Stanza.php b/vendor/norgul/xmpp-php/src/Xml/Stanzas/Stanza.php new file mode 100644 index 0000000..8d65165 --- /dev/null +++ b/vendor/norgul/xmpp-php/src/Xml/Stanzas/Stanza.php @@ -0,0 +1,32 @@ +socket = $socket; + } + + protected function uniqueId(): string + { + return uniqid(); + } + + protected function readResponseFile() + { + $logger = $this->socket->getOptions()->getLogger(); + $responseFilePath = $logger->getFilePathFromResource($logger->log); + $responseFile = fopen($responseFilePath, 'r'); + + return fread($responseFile, filesize($responseFilePath)); + } +} diff --git a/vendor/norgul/xmpp-php/src/Xml/Xml.php b/vendor/norgul/xmpp-php/src/Xml/Xml.php new file mode 100644 index 0000000..0d1eadf --- /dev/null +++ b/vendor/norgul/xmpp-php/src/Xml/Xml.php @@ -0,0 +1,117 @@ +"; + $to = "to='{$host}'"; + $stream = "xmlns:stream='http://etherx.jabber.org/streams'"; + $client = "xmlns='jabber:client'"; + $version = "version='1.0'"; + + return "{$xmlOpen}"; + } + + /** + * Closing tag for one XMPP stream session + */ + public static function closeXmlStream() + { + return ''; + } + + public static function quote($input) + { + return htmlspecialchars($input, ENT_XML1, 'utf-8'); + } + + public static function parseTag($rawResponse, string $tag): array + { + preg_match_all("#(<$tag.*?>.*?<\/$tag>)#si", $rawResponse, $matched); + + return count($matched) <= 1 ? [] : array_map(function ($match) { + return @simplexml_load_string($match); + }, $matched[1]); + } + + public static function parseFeatures($xml) + { + return self::matchInsideOfTag($xml, "stream:features"); + } + + public static function isTlsSupported($xml) + { + $matchTag = self::matchCompleteTag($xml, "starttls"); + return !empty($matchTag); + } + + public static function isTlsRequired($xml) + { + if (!self::isTlsSupported($xml)) { + return false; + } + + $tls = self::matchCompleteTag($xml, "starttls"); + preg_match("#required#", $tls, $match); + return count($match) > 0; + } + + public static function matchCompleteTag($xml, $tag) + { + $match = self::matchTag($xml, $tag); + return is_array($match) && count($match) > 0 ? $match[0] : []; + } + + public static function matchInsideOfTag($xml, $tag) + { + $match = self::matchTag($xml, $tag); + return is_array($match) && count($match) > 1 ? $match[1] : []; + } + + private static function matchTag($xml, $tag) + { + preg_match("#<$tag.*?>(.*)<\/$tag>#", $xml, $match); + return count($match) < 1 ? '' : $match; + } + + public static function canProceed($xml) + { + preg_match("##", $xml, $match); + return count($match) > 0; + } + + public static function supportedAuthMethods($xml) + { + preg_match_all("#(.*?)<\/mechanism>#", $xml, $match); + return count($match) < 1 ? [] : $match[1]; + } + + public static function roster($xml) + { + preg_match_all("#(.*?)<\/iq>#", $xml, $match); + return count($match) < 1 ? [] : $match[1]; + } + + /** + * @param string $response + * @throws StreamError + */ + public static function checkForUnrecoverableErrors(string $response) + { + preg_match_all("#(<(.*?) (.*?)\/>)<\/stream:error>#", $response, $streamErrors); + + if ((!empty($streamErrors[0])) && count($streamErrors[2]) > 0) { + throw new StreamError($streamErrors[2][0]); + } + } +} diff --git a/vendor/norgul/xmpp-php/src/XmppClient.php b/vendor/norgul/xmpp-php/src/XmppClient.php new file mode 100644 index 0000000..54cc7c1 --- /dev/null +++ b/vendor/norgul/xmpp-php/src/XmppClient.php @@ -0,0 +1,145 @@ +options = $options; + $this->initDependencies(); + $this->initSession(); + } + + protected function initDependencies(): void + { + $this->socket = $this->initSocket(); + $this->initStanzas($this->socket); + } + + public function connect() + { + $this->openStream(); + $this->auth->authenticate(); + $this->iq->setResource($this->options->getResource()); + $this->sendInitialPresenceStanza(); + } + + public function send(string $xml) + { + $this->socket->send($xml); + } + + public function getResponse(): string + { + $this->socket->receive(); + $response = $this->socket->getResponseBuffer()->read(); + $finalResponse = $this->checkForErrors($response); + + return $finalResponse; + } + + public function prettyPrint($response) + { + if ($response) { + $separator = "\n-------------\n"; + echo "{$separator} $response {$separator}"; + } + } + + public function disconnect() + { + $this->socket->send(self::closeXmlStream()); + $this->socket->disconnect(); + } + + protected function openStream() + { + $openStreamXml = self::openXmlStream($this->options->getHost()); + $this->socket->send($openStreamXml); + } + + protected function sendInitialPresenceStanza() + { + $this->socket->send(''); + } + + protected function initStanzas($socket) + { + $this->auth = new Auth($socket); + $this->iq = new Iq($socket); + $this->presence = new Presence($socket); + $this->message = new Message($socket); + } + + protected function initSession() + { + if (session_status() === PHP_SESSION_NONE) { + session_id(uniqid()); + session_start(); + } + } + + protected function initSocket(): Socket + { + try { + return new Socket($this->options); + } catch (DeadSocket $e) { + $this->options->getLogger()->error(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage()); + return null; + } + } + + protected function checkForErrors(string $response): string + { + try { + self::checkForUnrecoverableErrors($response); + } catch (StreamError $e) { + $this->options->getLogger()->logResponse(__METHOD__ . '::' . __LINE__ . " $response"); + $this->options->getLogger()->error(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage()); + $this->reconnect(); + $response = ''; + } + return $response; + } + + protected function reconnect() + { + $this->disconnect(); + $this->initDependencies(); + $this->connect(); + } +} diff --git a/vendor/norgul/xmpp-php/tests/OptionsTest.php b/vendor/norgul/xmpp-php/tests/OptionsTest.php new file mode 100644 index 0000000..8b3317b --- /dev/null +++ b/vendor/norgul/xmpp-php/tests/OptionsTest.php @@ -0,0 +1,121 @@ +host = 'www.host.com'; + $this->username = 'foo'; + $this->password = 'bar'; + $this->port = 5222; + $this->options = new Options(); + + $this->options + ->setHost($this->host) + ->setPort($this->port) + ->setUsername($this->username) + ->setPassword($this->password); + } + + public function testIfNoHostThrowsError() + { + $this->expectException(InvalidArgumentException::class); + $this->options->setHost(''); + $this->options->getHost(); + } + + public function testIfNoUsernameThrowsError() + { + $this->expectException(InvalidArgumentException::class); + $this->options->setUsername(''); + $this->options->getUsername(); + } + + public function testIfNoPasswordThrowsError() + { + $this->expectException(InvalidArgumentException::class); + $this->options->setPassword(''); + $this->options->getPassword(); + } + + public function testIfHostGetsTrimmed() + { + $this->options->setHost(' host'); + $this->assertEquals('host', $this->options->getHost()); + + $this->options->setHost('host '); + $this->assertEquals('host', $this->options->getHost()); + + $this->options->setHost(' host '); + $this->assertEquals('host', $this->options->getHost()); + } + + public function testIfUsernameSplitResource() + { + $this->options->setUsername('user/resource'); + $this->assertEquals('user', $this->options->getUsername()); + $this->assertEquals('resource', $this->options->getResource()); + + $this->options->setUsername('user'); + $this->assertEquals('user', $this->options->getUsername()); + + $this->options->setUsername('user/resource/resource2/resource3'); + $this->assertEquals('user', $this->options->getUsername()); + $this->assertEquals('resource', $this->options->getResource()); + } + + public function testResourcePrecedence() + { + $this->options->setUsername('user/resource'); + $this->options->setResource('resource2'); + $this->assertEquals('user', $this->options->getUsername()); + $this->assertEquals('resource2', $this->options->getResource()); + + $this->options->setResource('resource2'); + $this->options->setUsername('user/resource'); + $this->assertEquals('user', $this->options->getUsername()); + $this->assertEquals('resource', $this->options->getResource()); + } + + public function testIfResourceGetsTrimmed() + { + $this->options->setResource(' resource'); + $this->assertEquals('resource', $this->options->getResource()); + + $this->options->setResource('resource '); + $this->assertEquals('resource', $this->options->getResource()); + + $this->options->setResource(' resource '); + $this->assertEquals('resource', $this->options->getResource()); + } + + public function testFullSocketAddress() + { + $this->assertEquals("tcp://www.host.com:5222", $this->options->fullSocketAddress()); + } + + public function testFullJid() + { + $this->options->setResource('resource'); + $this->assertEquals("foo@www.host.com/resource", $this->options->fullJid()); + } + + public function testBareJid() + { + $this->assertEquals("foo@www.host.com", $this->options->bareJid()); + } +} diff --git a/vendor/norgul/xmpp-php/tests/XmlTest.php b/vendor/norgul/xmpp-php/tests/XmlTest.php new file mode 100644 index 0000000..246f34c --- /dev/null +++ b/vendor/norgul/xmpp-php/tests/XmlTest.php @@ -0,0 +1,16 @@ +"; + $this->assertEquals($expected, $this->openXmlStream($this->host)); + } +}