Last minute changes.
This commit is contained in:
commit
7f67f244f4
157 changed files with 46467 additions and 0 deletions
121
LICENSE
Normal file
121
LICENSE
Normal file
|
@ -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.
|
179
README.TXT
Normal file
179
README.TXT
Normal file
|
@ -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
|
||||
|
||||
|
||||
-----------------------------------------------------------------------------
|
7
composer.json
Normal file
7
composer.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"require": {
|
||||
"norgul/xmpp-php": "^2.2",
|
||||
"chillerlan/php-qrcode": "^5.0",
|
||||
"monero-integrations/monerophp": "@dev"
|
||||
}
|
||||
}
|
92
data/Doxyfile
Executable file
92
data/Doxyfile
Executable file
|
@ -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
|
2
data/items.txt
Normal file
2
data/items.txt
Normal file
|
@ -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
|
0
data/payments.db
Normal file
0
data/payments.db
Normal file
BIN
files/IndustrialSocietyItsFuture.pdf
Normal file
BIN
files/IndustrialSocietyItsFuture.pdf
Normal file
Binary file not shown.
560
index.php
Normal file
560
index.php
Normal file
|
@ -0,0 +1,560 @@
|
|||
<?php
|
||||
/*********************************************
|
||||
* Description - Neropay payment system
|
||||
*
|
||||
* index.php -- The main script responsible for
|
||||
* everything.
|
||||
* Author - Vilyaem
|
||||
* *******************************************/
|
||||
|
||||
/*CONSTANTS*/
|
||||
define("VILXMPPADDR", "YOUR XMPP ADDRESS");
|
||||
define("XMPPHOST", "YOUR XMPP HOST (something.xyz)");
|
||||
define("XMPPUSER", "YOUR XMPP USERNAME (vilyaem)");
|
||||
define("XMPPPASS", "YOUR XMPP PASSWORD");
|
||||
define("SQLDBPATH", "data/payments.db");
|
||||
|
||||
|
||||
/*GET THE BLOAT*/
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
require_once('vendor/monero-integrations/monerophp/src/jsonRPCClient.php');
|
||||
require_once('vendor/monero-integrations/monerophp/src/walletRPC.php');
|
||||
|
||||
use MoneroIntegrations\MoneroPhp\walletRPC;
|
||||
use chillerlan\QRCode\Data\QRMatrix;
|
||||
use chillerlan\QRCode\Output\QRStringText;
|
||||
use chillerlan\QRCode\{QRCode, QROptions};
|
||||
|
||||
/*
|
||||
ini_set('display_errors',1);
|
||||
ini_set('display_startup_errors',1);
|
||||
error_reporting(E_ALL);
|
||||
*/
|
||||
|
||||
$db = new SQLite3(SQLDBPATH);
|
||||
$db->enableExceptions(true);
|
||||
|
||||
|
||||
$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;
|
||||
|
||||
|
||||
?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Neropay</title>
|
||||
<meta http-equiv="refresh" content="25">
|
||||
</head>
|
||||
<body>
|
||||
<center>
|
||||
<h1>Neropay</h1>
|
||||
<small>A tiny Monero payment system</small></br>
|
||||
<?php
|
||||
|
||||
/*Is this a Wownero transaction?*/
|
||||
|
||||
/*Get out*/
|
||||
if (!isset($_SERVER['REMOTE_ADDR'])) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if($_SERVER['REQUEST_METHOD'] === 'GET'){
|
||||
|
||||
if(!isset($_GET['item'])){
|
||||
echo '<p>Welcome to Neropay, a tiny payment processsing system for Monero</p>';
|
||||
exit;
|
||||
}
|
||||
|
||||
if(isset($_GET['wow'])){
|
||||
$wow = true;
|
||||
echo '<p style="color:pink;">You are making a Wownero transaction!</p>';
|
||||
}
|
||||
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 '<p style="color:red;">Error, please report the following to kenyaz [at] vilyaem.xyz via Email or XMPP: ' . $msg . '</p>';
|
||||
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) . "</br>";*/
|
||||
/*echo var_dump($res) . "</br>";*/
|
||||
|
||||
/*echo var_dump($res->numColumns()) . "</br>";*/
|
||||
/*echo var_dump($res->columnType(0)) . "</br>";*/
|
||||
|
||||
|
||||
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) . "</br>";*/
|
||||
/*echo var_dump($res) . "</br>";*/
|
||||
/*echo var_dump($row) . "</br>";*/
|
||||
|
||||
if($row === false){
|
||||
NeropayError("Error code 1006");
|
||||
}
|
||||
/*
|
||||
else{
|
||||
echo "<p>AskPayment got a valid row!</p>";
|
||||
}
|
||||
*/
|
||||
|
||||
/*$db = null;*/
|
||||
|
||||
if($newwaladdr === NULL){
|
||||
$wallet = $walletRPC->open_wallet($row[2],'');
|
||||
$addr = $walletRPC->get_address();
|
||||
}
|
||||
else{
|
||||
$addr = $newwaladdr;
|
||||
}
|
||||
|
||||
$amnt = $row[4];
|
||||
|
||||
/*echo "</br></br>" . var_dump($addr) . "</br>";*/
|
||||
|
||||
echo '<img src="'.(new QRCode)->render($addr['address']).'" width=25% height=25% alt="QR Code" />';
|
||||
|
||||
/*var_dump($wow);*/
|
||||
|
||||
if(!$wow){
|
||||
echo '<p>Please pay ' . $amnt . ' XMR to ' . $addr['address'] . '</p>';
|
||||
}
|
||||
else{
|
||||
echo '<p>Please pay ' . $amnt * 4005 . ' WOW to ' . $addr['address'] . '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************
|
||||
* 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 "<p>Your shipping/contact information has been sent.</p>";
|
||||
}
|
||||
}
|
||||
|
||||
/*Congratulate the user*/
|
||||
echo "<p style=\"color:green\">Congratulations on your purchase of $item!</p>";
|
||||
}
|
||||
|
||||
/*********************************************
|
||||
* 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) . "<br>";*/
|
||||
|
||||
|
||||
/*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) . "<br>";*/
|
||||
/*echo var_dump($mybalance) . "<br>";*/
|
||||
|
||||
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 '<p>' . $walletRPC->transfer() . '</p>'*/
|
||||
|
||||
/*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 "<p>Found the payment!</p>";*/
|
||||
/*If payment is already ongoing, check if it has been paid,
|
||||
if so, show success, otherwise ask for payment*/
|
||||
if(CheckPayment() === true){
|
||||
/*echo "<p>Check payment succeeded</p>";*/
|
||||
Success();
|
||||
}
|
||||
else{
|
||||
/*echo "<p>Check payment falied, ask for payment</p>";*/
|
||||
AskPayment();
|
||||
}
|
||||
}
|
||||
/*If it is not, create it and ask for payment*/
|
||||
else{
|
||||
/*echo "<p>Failed to find the payment!</p>";*/
|
||||
NewPayment();
|
||||
AskPayment();
|
||||
}
|
||||
}
|
||||
|
||||
Main();
|
||||
/*$xmppclient->disconnect();*/
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<p>
|
||||
<small>Note: Payments may take a couple minutes to go through.
|
||||
</br>Report irregularities or problems to kenyaz [at] vilyaem.xyz
|
||||
</br>The page should refresh automatically, if not, refresh manually</small>
|
||||
</br><strong>WARNING: Monero might liberate you.</strong>
|
||||
</p>
|
||||
|
||||
<?php
|
||||
/*Benchmarking*/
|
||||
$time = microtime();
|
||||
$time = explode(' ', $time);
|
||||
$time = $time[1] + $time[0];
|
||||
$finish = $time;
|
||||
$total_time = round(($finish - $start), 4);
|
||||
echo '<em>Page generated in '.$total_time.' seconds.</em>';
|
||||
?>
|
||||
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
|
51
physprep.php
Normal file
51
physprep.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
/*********************************************
|
||||
* Description - Neropay payment system
|
||||
*
|
||||
* physorder.php -- Ask the user for extra information
|
||||
* such as shipping details or XMPP contact
|
||||
* Author - Vilyaem
|
||||
* *******************************************/
|
||||
|
||||
if($_SERVER['REQUEST_METHOD'] != 'GET'){
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
}
|
||||
else{
|
||||
$item = isset($_GET['item']) ? $_GET['item'] : '';
|
||||
$item = isset($_GET['wow']) ? $_GET['wow'] : '';
|
||||
$addr = isset($_GET['addr']) ? $_GET['addr'] : '';
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Neropay</title>
|
||||
</head>
|
||||
<style>
|
||||
body{font-family:Courier;};
|
||||
</style>
|
||||
<body>
|
||||
<center>
|
||||
<h1>Neropay</h1>
|
||||
<small>A tiny Monero payment system</small></br>
|
||||
<form action="index.php" method="get">
|
||||
<textarea type="text" id="addr" name="addr" rows=16 cols=64 maxlength=1024 autofocus required placeholder="Please put contact or shipping information here, don't make a fool of yourself."></textarea>
|
||||
<input type="hidden" id="item" name="item" value="<?php echo htmlspecialchars($item) ?>"></textarea>
|
||||
<input type="hidden" id="wow" name="wow" value="<?php echo htmlspecialchars($wow) ?>"></textarea>
|
||||
</br>
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
|
||||
<p>
|
||||
<small>Note: Payments may take a couple minutes to go through.
|
||||
</br>Report irregularities or problems to kenyaz [at] vilyaem.xyz</small>
|
||||
</br><strong>WARNING: Monero might liberate you.</strong>
|
||||
</p>
|
||||
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
25
vendor/autoload.php
vendored
Normal file
25
vendor/autoload.php
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit039ef714d06adc1d2f797455add3d2e5::getLoader();
|
202
vendor/chillerlan/php-qrcode/LICENSE-ASL-2.0
vendored
Normal file
202
vendor/chillerlan/php-qrcode/LICENSE-ASL-2.0
vendored
Normal file
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
21
vendor/chillerlan/php-qrcode/LICENSE-MIT
vendored
Normal file
21
vendor/chillerlan/php-qrcode/LICENSE-MIT
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Smiley <smiley@chillerlan.net>
|
||||
|
||||
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.
|
40
vendor/chillerlan/php-qrcode/NOTICE
vendored
Normal file
40
vendor/chillerlan/php-qrcode/NOTICE
vendored
Normal file
|
@ -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
|
168
vendor/chillerlan/php-qrcode/README.md
vendored
Normal file
168
vendor/chillerlan/php-qrcode/README.md
vendored
Normal file
|
@ -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. <br>
|
||||
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#<commit_hash>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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 '<img src="'.(new QRCode)->render($data).'" alt="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.
|
||||
|
||||
<p align="center">
|
||||
<img alt="QR codes are awesome!" style="width: auto; height: 530px;" src="https://raw.githubusercontent.com/chillerlan/php-qrcode/v5.0.x/.github/images/example.svg">
|
||||
</p>
|
||||
|
||||
|
||||
### 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*<br>
|
||||
https://www.qrcode.com/en/faq.html#patentH2Title
|
79
vendor/chillerlan/php-qrcode/composer.json
vendored
Normal file
79
vendor/chillerlan/php-qrcode/composer.json
vendored
Normal file
|
@ -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
|
||||
}
|
||||
}
|
180
vendor/chillerlan/php-qrcode/src/Common/BitBuffer.php
vendored
Normal file
180
vendor/chillerlan/php-qrcode/src/Common/BitBuffer.php
vendored
Normal file
|
@ -0,0 +1,180 @@
|
|||
<?php
|
||||
/**
|
||||
* Class BitBuffer
|
||||
*
|
||||
* @created 25.11.2015
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
125
vendor/chillerlan/php-qrcode/src/Common/ECICharset.php
vendored
Normal file
125
vendor/chillerlan/php-qrcode/src/Common/ECICharset.php
vendored
Normal file
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
/**
|
||||
* Class ECICharset
|
||||
*
|
||||
* @created 21.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
223
vendor/chillerlan/php-qrcode/src/Common/EccLevel.php
vendored
Normal file
223
vendor/chillerlan/php-qrcode/src/Common/EccLevel.php
vendored
Normal file
|
@ -0,0 +1,223 @@
|
|||
<?php
|
||||
/**
|
||||
* Class EccLevel
|
||||
*
|
||||
* @created 19.11.2020
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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()];
|
||||
}
|
||||
|
||||
}
|
97
vendor/chillerlan/php-qrcode/src/Common/GDLuminanceSource.php
vendored
Normal file
97
vendor/chillerlan/php-qrcode/src/Common/GDLuminanceSource.php
vendored
Normal file
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
/**
|
||||
* Class GDLuminanceSource
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author Ashot Khanamiryan
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
154
vendor/chillerlan/php-qrcode/src/Common/GF256.php
vendored
Normal file
154
vendor/chillerlan/php-qrcode/src/Common/GF256.php
vendored
Normal file
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
/**
|
||||
* Class GF256
|
||||
*
|
||||
* @created 16.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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)];
|
||||
}
|
||||
|
||||
}
|
263
vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php
vendored
Normal file
263
vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php
vendored
Normal file
|
@ -0,0 +1,263 @@
|
|||
<?php
|
||||
/**
|
||||
* Class GenericGFPoly
|
||||
*
|
||||
* @created 16.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
78
vendor/chillerlan/php-qrcode/src/Common/IMagickLuminanceSource.php
vendored
Normal file
78
vendor/chillerlan/php-qrcode/src/Common/IMagickLuminanceSource.php
vendored
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
/**
|
||||
* Class IMagickLuminanceSource
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author Ashot Khanamiryan
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
104
vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceAbstract.php
vendored
Normal file
104
vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceAbstract.php
vendored
Normal file
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
/**
|
||||
* Class LuminanceSourceAbstract
|
||||
*
|
||||
* @created 24.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Ashot Khanamiryan
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
61
vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceInterface.php
vendored
Normal file
61
vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceInterface.php
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
/**
|
||||
* Interface LuminanceSourceInterface
|
||||
*
|
||||
* @created 18.11.2021
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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;
|
||||
|
||||
}
|
329
vendor/chillerlan/php-qrcode/src/Common/MaskPattern.php
vendored
Normal file
329
vendor/chillerlan/php-qrcode/src/Common/MaskPattern.php
vendored
Normal file
|
@ -0,0 +1,329 @@
|
|||
<?php
|
||||
/**
|
||||
* Class MaskPattern
|
||||
*
|
||||
* @created 19.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
96
vendor/chillerlan/php-qrcode/src/Common/Mode.php
vendored
Normal file
96
vendor/chillerlan/php-qrcode/src/Common/Mode.php
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
/**
|
||||
* Class Mode
|
||||
*
|
||||
* @created 19.11.2020
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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));
|
||||
}
|
||||
|
||||
}
|
287
vendor/chillerlan/php-qrcode/src/Common/Version.php
vendored
Normal file
287
vendor/chillerlan/php-qrcode/src/Common/Version.php
vendored
Normal file
|
@ -0,0 +1,287 @@
|
|||
<?php
|
||||
/**
|
||||
* Class Version
|
||||
*
|
||||
* @created 19.11.2020
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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];
|
||||
}
|
||||
|
||||
}
|
137
vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php
vendored
Normal file
137
vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php
vendored
Normal file
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
/**
|
||||
* Class AlphaNum
|
||||
*
|
||||
* @created 25.11.2015
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
85
vendor/chillerlan/php-qrcode/src/Data/Byte.php
vendored
Normal file
85
vendor/chillerlan/php-qrcode/src/Data/Byte.php
vendored
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
/**
|
||||
* Class Byte
|
||||
*
|
||||
* @created 25.11.2015
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
155
vendor/chillerlan/php-qrcode/src/Data/ECI.php
vendored
Normal file
155
vendor/chillerlan/php-qrcode/src/Data/ECI.php
vendored
Normal file
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
/**
|
||||
* Class ECI
|
||||
*
|
||||
* @created 20.11.2020
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
205
vendor/chillerlan/php-qrcode/src/Data/Hanzi.php
vendored
Normal file
205
vendor/chillerlan/php-qrcode/src/Data/Hanzi.php
vendored
Normal file
|
@ -0,0 +1,205 @@
|
|||
<?php
|
||||
/**
|
||||
* Class Hanzi
|
||||
*
|
||||
* @created 19.11.2020
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
191
vendor/chillerlan/php-qrcode/src/Data/Kanji.php
vendored
Normal file
191
vendor/chillerlan/php-qrcode/src/Data/Kanji.php
vendored
Normal file
|
@ -0,0 +1,191 @@
|
|||
<?php
|
||||
/**
|
||||
* Class Kanji
|
||||
*
|
||||
* @created 25.11.2015
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
182
vendor/chillerlan/php-qrcode/src/Data/Number.php
vendored
Normal file
182
vendor/chillerlan/php-qrcode/src/Data/Number.php
vendored
Normal file
|
@ -0,0 +1,182 @@
|
|||
<?php
|
||||
/**
|
||||
* Class Number
|
||||
*
|
||||
* @created 26.11.2015
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
20
vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php
vendored
Normal file
20
vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRCodeDataException
|
||||
*
|
||||
* @created 09.12.2015
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2015 Smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Data;
|
||||
|
||||
use chillerlan\QRCode\QRCodeException;
|
||||
|
||||
/**
|
||||
* An exception container
|
||||
*/
|
||||
final class QRCodeDataException extends QRCodeException{
|
||||
|
||||
}
|
263
vendor/chillerlan/php-qrcode/src/Data/QRData.php
vendored
Normal file
263
vendor/chillerlan/php-qrcode/src/Data/QRData.php
vendored
Normal file
|
@ -0,0 +1,263 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRData
|
||||
*
|
||||
* @created 25.11.2015
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
61
vendor/chillerlan/php-qrcode/src/Data/QRDataModeAbstract.php
vendored
Normal file
61
vendor/chillerlan/php-qrcode/src/Data/QRDataModeAbstract.php
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRDataModeAbstract
|
||||
*
|
||||
* @created 19.11.2020
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
63
vendor/chillerlan/php-qrcode/src/Data/QRDataModeInterface.php
vendored
Normal file
63
vendor/chillerlan/php-qrcode/src/Data/QRDataModeInterface.php
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
/**
|
||||
* Interface QRDataModeInterface
|
||||
*
|
||||
* @created 01.12.2015
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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;
|
||||
|
||||
}
|
812
vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php
vendored
Executable file
812
vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php
vendored
Executable file
|
@ -0,0 +1,812 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRMatrix
|
||||
*
|
||||
* @created 15.11.2017
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
127
vendor/chillerlan/php-qrcode/src/Data/ReedSolomonEncoder.php
vendored
Normal file
127
vendor/chillerlan/php-qrcode/src/Data/ReedSolomonEncoder.php
vendored
Normal file
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
/**
|
||||
* Class ReedSolomonEncoder
|
||||
*
|
||||
* @created 07.01.2021
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
361
vendor/chillerlan/php-qrcode/src/Decoder/Binarizer.php
vendored
Normal file
361
vendor/chillerlan/php-qrcode/src/Decoder/Binarizer.php
vendored
Normal file
|
@ -0,0 +1,361 @@
|
|||
<?php
|
||||
/**
|
||||
* Class Binarizer
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
430
vendor/chillerlan/php-qrcode/src/Decoder/BitMatrix.php
vendored
Normal file
430
vendor/chillerlan/php-qrcode/src/Decoder/BitMatrix.php
vendored
Normal file
|
@ -0,0 +1,430 @@
|
|||
<?php
|
||||
/**
|
||||
* Class BitMatrix
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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');
|
||||
}
|
||||
|
||||
}
|
173
vendor/chillerlan/php-qrcode/src/Decoder/Decoder.php
vendored
Normal file
173
vendor/chillerlan/php-qrcode/src/Decoder/Decoder.php
vendored
Normal file
|
@ -0,0 +1,173 @@
|
|||
<?php
|
||||
/**
|
||||
* Class Decoder
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
99
vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php
vendored
Normal file
99
vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php
vendored
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
/**
|
||||
* Class DecoderResult
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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)
|
||||
;
|
||||
}
|
||||
|
||||
}
|
20
vendor/chillerlan/php-qrcode/src/Decoder/QRCodeDecoderException.php
vendored
Normal file
20
vendor/chillerlan/php-qrcode/src/Decoder/QRCodeDecoderException.php
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRCodeDecoderException
|
||||
*
|
||||
* @created 01.12.2021
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Decoder;
|
||||
|
||||
use chillerlan\QRCode\QRCodeException;
|
||||
|
||||
/**
|
||||
* An exception container
|
||||
*/
|
||||
final class QRCodeDecoderException extends QRCodeException{
|
||||
|
||||
}
|
313
vendor/chillerlan/php-qrcode/src/Decoder/ReedSolomonDecoder.php
vendored
Normal file
313
vendor/chillerlan/php-qrcode/src/Decoder/ReedSolomonDecoder.php
vendored
Normal file
|
@ -0,0 +1,313 @@
|
|||
<?php
|
||||
/**
|
||||
* Class ReedSolomonDecoder
|
||||
*
|
||||
* @created 24.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
34
vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php
vendored
Normal file
34
vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
/**
|
||||
* Class AlignmentPattern
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
283
vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php
vendored
Normal file
283
vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php
vendored
Normal file
|
@ -0,0 +1,283 @@
|
|||
<?php
|
||||
/**
|
||||
* Class AlignmentPatternFinder
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
350
vendor/chillerlan/php-qrcode/src/Detector/Detector.php
vendored
Normal file
350
vendor/chillerlan/php-qrcode/src/Detector/Detector.php
vendored
Normal file
|
@ -0,0 +1,350 @@
|
|||
<?php
|
||||
/**
|
||||
* Class Detector
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
92
vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php
vendored
Normal file
92
vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
/**
|
||||
* Class FinderPattern
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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));
|
||||
}
|
||||
|
||||
}
|
770
vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php
vendored
Normal file
770
vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php
vendored
Normal file
|
@ -0,0 +1,770 @@
|
|||
<?php
|
||||
/**
|
||||
* Class FinderPatternFinder
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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)));
|
||||
}
|
||||
|
||||
}
|
181
vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php
vendored
Normal file
181
vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php
vendored
Normal file
|
@ -0,0 +1,181 @@
|
|||
<?php
|
||||
/**
|
||||
* Class GridSampler
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
182
vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php
vendored
Normal file
182
vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php
vendored
Normal file
|
@ -0,0 +1,182 @@
|
|||
<?php
|
||||
/**
|
||||
* Class PerspectiveTransform
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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, []];
|
||||
}
|
||||
|
||||
}
|
20
vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php
vendored
Normal file
20
vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRCodeDetectorException
|
||||
*
|
||||
* @created 01.12.2021
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Detector;
|
||||
|
||||
use chillerlan\QRCode\QRCodeException;
|
||||
|
||||
/**
|
||||
* An exception container
|
||||
*/
|
||||
final class QRCodeDetectorException extends QRCodeException{
|
||||
|
||||
}
|
73
vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php
vendored
Normal file
73
vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
/**
|
||||
* Class ResultPoint
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
20
vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php
vendored
Normal file
20
vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRCodeOutputException
|
||||
*
|
||||
* @created 09.12.2015
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2015 Smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Output;
|
||||
|
||||
use chillerlan\QRCode\QRCodeException;
|
||||
|
||||
/**
|
||||
* An exception container
|
||||
*/
|
||||
final class QRCodeOutputException extends QRCodeException{
|
||||
|
||||
}
|
173
vendor/chillerlan/php-qrcode/src/Output/QREps.php
vendored
Normal file
173
vendor/chillerlan/php-qrcode/src/Output/QREps.php
vendored
Normal file
|
@ -0,0 +1,173 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QREps
|
||||
*
|
||||
* @created 09.05.2022
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
177
vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php
vendored
Normal file
177
vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php
vendored
Normal file
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRFpdf
|
||||
*
|
||||
* @created 03.06.2020
|
||||
* @author Maximilian Kresse
|
||||
* @license MIT
|
||||
*
|
||||
* @see https://github.com/chillerlan/php-qrcode/pull/49
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Output;
|
||||
|
||||
use chillerlan\QRCode\Data\QRMatrix;
|
||||
use chillerlan\Settings\SettingsContainerInterface;
|
||||
use FPDF;
|
||||
|
||||
use function array_values, class_exists, count, intval, is_array, is_numeric, max, min;
|
||||
|
||||
/**
|
||||
* QRFpdf output module (requires fpdf)
|
||||
*
|
||||
* @see https://github.com/Setasign/FPDF
|
||||
* @see http://www.fpdf.org/
|
||||
*/
|
||||
class QRFpdf extends QROutputAbstract{
|
||||
|
||||
public const MIME_TYPE = 'application/pdf';
|
||||
|
||||
protected FPDF $fpdf;
|
||||
protected ?array $prevColor = null;
|
||||
|
||||
/**
|
||||
* QRFpdf constructor.
|
||||
*
|
||||
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
|
||||
*/
|
||||
public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
|
||||
|
||||
if(!class_exists(FPDF::class)){
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new QRCodeOutputException(
|
||||
'The QRFpdf output requires FPDF (https://github.com/Setasign/FPDF)'.
|
||||
' as dependency but the class "\\FPDF" couldn\'t be found.'
|
||||
);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
parent::__construct($options, $matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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):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');
|
||||
}
|
||||
|
||||
}
|
400
vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php
vendored
Normal file
400
vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php
vendored
Normal file
|
@ -0,0 +1,400 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRGdImage
|
||||
*
|
||||
* @created 05.12.2015
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
33
vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php
vendored
Normal file
33
vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRGdImageBMP
|
||||
*
|
||||
* @created 25.10.2023
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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));
|
||||
}
|
||||
|
||||
}
|
33
vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php
vendored
Normal file
33
vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRGdImageGIF
|
||||
*
|
||||
* @created 25.10.2023
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
40
vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php
vendored
Normal file
40
vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRGdImageJPEG
|
||||
*
|
||||
* @created 25.10.2023
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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)));
|
||||
}
|
||||
|
||||
}
|
33
vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php
vendored
Normal file
33
vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRGdImagePNG
|
||||
*
|
||||
* @created 25.10.2023
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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)));
|
||||
}
|
||||
|
||||
}
|
33
vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php
vendored
Normal file
33
vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRGdImageWEBP
|
||||
*
|
||||
* @created 25.10.2023
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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)));
|
||||
}
|
||||
|
||||
}
|
19
vendor/chillerlan/php-qrcode/src/Output/QRImage.php
vendored
Normal file
19
vendor/chillerlan/php-qrcode/src/Output/QRImage.php
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRImage
|
||||
*
|
||||
* @created 14.12.2021
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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{
|
||||
|
||||
}
|
235
vendor/chillerlan/php-qrcode/src/Output/QRImagick.php
vendored
Normal file
235
vendor/chillerlan/php-qrcode/src/Output/QRImagick.php
vendored
Normal file
|
@ -0,0 +1,235 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRImagick
|
||||
*
|
||||
* @created 04.07.2018
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
94
vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php
vendored
Normal file
94
vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php
vendored
Normal file
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRMarkup
|
||||
*
|
||||
* @created 17.12.2016
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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;
|
||||
|
||||
}
|
51
vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php
vendored
Normal file
51
vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRMarkupHTML
|
||||
*
|
||||
* @created 06.06.2022
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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 = '<span style="background: %s;"></span>';
|
||||
$modules = array_map(fn(int $M_TYPE):string => sprintf($element, $this->getModuleValue($M_TYPE)), $row);
|
||||
|
||||
$rows[] = sprintf('<div>%s</div>%s', implode('', $modules), $this->eol);
|
||||
}
|
||||
|
||||
$html = sprintf('<div class="%1$s">%3$s%2$s</div>%3$s', $cssClass, implode('', $rows), $this->eol);
|
||||
|
||||
// wrap the snippet into a body when saving to file
|
||||
if($saveToFile){
|
||||
$html = sprintf(
|
||||
'<!DOCTYPE html><html lang="none">%2$s<head>%2$s<meta charset="UTF-8">%2$s'.
|
||||
'<title>QR Code</title></head>%2$s<body>%1$s</body>%2$s</html>',
|
||||
$html,
|
||||
$this->eol
|
||||
);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
}
|
200
vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php
vendored
Normal file
200
vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php
vendored
Normal file
|
@ -0,0 +1,200 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRMarkupSVG
|
||||
*
|
||||
* @created 06.06.2022
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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('<defs>%1$s%2$s</defs>%2$s', $this->options->svgDefs, $this->eol);
|
||||
}
|
||||
|
||||
$svg .= $this->paths();
|
||||
|
||||
// close svg
|
||||
$svg .= sprintf('%1$s</svg>%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 <svg> header with the given options parsed
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg
|
||||
*/
|
||||
protected function header():string{
|
||||
|
||||
$header = sprintf(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" class="qr-svg %1$s" viewBox="%2$s" preserveAspectRatio="%3$s">%4$s',
|
||||
$this->options->cssClass,
|
||||
$this->getViewBox(),
|
||||
$this->options->svgPreserveAspectRatio,
|
||||
$this->eol
|
||||
);
|
||||
|
||||
if($this->options->svgAddXmlHeader){
|
||||
$header = sprintf('<?xml version="1.0" encoding="UTF-8"?>%s%s', $this->eol, $header);
|
||||
}
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns one or more SVG <path> 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 <path> 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(
|
||||
'<path class="%s" fill="%s" d="%s"/>',
|
||||
$this->getCssClass($M_TYPE),
|
||||
$this->getModuleValue($M_TYPE),
|
||||
$path
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf('<path class="%s" d="%s"/>', $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";
|
||||
}
|
||||
|
||||
}
|
261
vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php
vendored
Normal file
261
vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php
vendored
Normal file
|
@ -0,0 +1,261 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QROutputAbstract
|
||||
*
|
||||
* @created 09.12.2015
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
226
vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php
vendored
Normal file
226
vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php
vendored
Normal file
|
@ -0,0 +1,226 @@
|
|||
<?php
|
||||
/**
|
||||
* Interface QROutputInterface,
|
||||
*
|
||||
* @created 02.12.2015
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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 <no replacement>
|
||||
* @see https://github.com/chillerlan/php-qrcode/issues/223
|
||||
*/
|
||||
public const MARKUP_HTML = 'html';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @deprecated 5.0.0 <no replacement>
|
||||
* @see https://github.com/chillerlan/php-qrcode/issues/223
|
||||
*/
|
||||
public const MARKUP_SVG = 'svg';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @deprecated 5.0.0 <no replacement>
|
||||
* @see https://github.com/chillerlan/php-qrcode/issues/223
|
||||
*/
|
||||
public const GDIMAGE_BMP = 'bmp';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @deprecated 5.0.0 <no replacement>
|
||||
* @see https://github.com/chillerlan/php-qrcode/issues/223
|
||||
*/
|
||||
public const GDIMAGE_GIF = 'gif';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @deprecated 5.0.0 <no replacement>
|
||||
* @see https://github.com/chillerlan/php-qrcode/issues/223
|
||||
*/
|
||||
public const GDIMAGE_JPG = 'jpg';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @deprecated 5.0.0 <no replacement>
|
||||
* @see https://github.com/chillerlan/php-qrcode/issues/223
|
||||
*/
|
||||
public const GDIMAGE_PNG = 'png';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @deprecated 5.0.0 <no replacement>
|
||||
* @see https://github.com/chillerlan/php-qrcode/issues/223
|
||||
*/
|
||||
public const GDIMAGE_WEBP = 'webp';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @deprecated 5.0.0 <no replacement>
|
||||
* @see https://github.com/chillerlan/php-qrcode/issues/223
|
||||
*/
|
||||
public const STRING_JSON = 'json';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @deprecated 5.0.0 <no replacement>
|
||||
* @see https://github.com/chillerlan/php-qrcode/issues/223
|
||||
*/
|
||||
public const STRING_TEXT = 'text';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @deprecated 5.0.0 <no replacement>
|
||||
* @see https://github.com/chillerlan/php-qrcode/issues/223
|
||||
*/
|
||||
public const IMAGICK = 'imagick';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @deprecated 5.0.0 <no replacement>
|
||||
* @see https://github.com/chillerlan/php-qrcode/issues/223
|
||||
*/
|
||||
public const FPDF = 'fpdf';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @deprecated 5.0.0 <no replacement>
|
||||
* @see https://github.com/chillerlan/php-qrcode/issues/223
|
||||
*/
|
||||
public const EPS = 'eps';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @deprecated 5.0.0 <no replacement>
|
||||
* @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 <no replacement>
|
||||
* @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);
|
||||
|
||||
}
|
111
vendor/chillerlan/php-qrcode/src/Output/QRString.php
vendored
Normal file
111
vendor/chillerlan/php-qrcode/src/Output/QRString.php
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRString
|
||||
*
|
||||
* @created 05.12.2015
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
67
vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php
vendored
Normal file
67
vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRStringJSON
|
||||
*
|
||||
* @created 25.10.2023
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
76
vendor/chillerlan/php-qrcode/src/Output/QRStringText.php
vendored
Normal file
76
vendor/chillerlan/php-qrcode/src/Output/QRStringText.php
vendored
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRStringText
|
||||
*
|
||||
* @created 25.10.2023
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
488
vendor/chillerlan/php-qrcode/src/QRCode.php
vendored
Executable file
488
vendor/chillerlan/php-qrcode/src/QRCode.php
vendored
Executable file
|
@ -0,0 +1,488 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRCode
|
||||
*
|
||||
* @created 26.11.2015
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
20
vendor/chillerlan/php-qrcode/src/QRCodeException.php
vendored
Normal file
20
vendor/chillerlan/php-qrcode/src/QRCodeException.php
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QRCodeException
|
||||
*
|
||||
* @created 27.11.2015
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2015 Smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* An exception container
|
||||
*/
|
||||
class QRCodeException extends Exception{
|
||||
|
||||
}
|
20
vendor/chillerlan/php-qrcode/src/QROptions.php
vendored
Normal file
20
vendor/chillerlan/php-qrcode/src/QROptions.php
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
/**
|
||||
* Class QROptions
|
||||
*
|
||||
* @created 08.12.2015
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2015 Smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode;
|
||||
|
||||
use chillerlan\Settings\SettingsContainerAbstract;
|
||||
|
||||
/**
|
||||
* The QRCode settings container
|
||||
*/
|
||||
class QROptions extends SettingsContainerAbstract{
|
||||
use QROptionsTrait;
|
||||
}
|
729
vendor/chillerlan/php-qrcode/src/QROptionsTrait.php
vendored
Normal file
729
vendor/chillerlan/php-qrcode/src/QROptionsTrait.php
vendored
Normal file
|
@ -0,0 +1,729 @@
|
|||
<?php
|
||||
/**
|
||||
* Trait QROptionsTrait
|
||||
*
|
||||
* Note: the docblocks in this file are optimized for readability in PhpStorm ond on readthedocs.io
|
||||
*
|
||||
* @created 10.03.2018
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @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
|
||||
*
|
||||
* `<?xml version="1.0" encoding="UTF-8"?>`
|
||||
*/
|
||||
protected bool $svgAddXmlHeader = true;
|
||||
|
||||
/**
|
||||
* Anything in the SVG `<defs>` tag
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs
|
||||
*/
|
||||
protected string $svgDefs = '';
|
||||
|
||||
/**
|
||||
* Sets the value for the "preserveAspectRatio" on the `<svg>` 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 `<path>` 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;
|
||||
}
|
||||
|
||||
}
|
21
vendor/chillerlan/php-settings-container/LICENSE
vendored
Normal file
21
vendor/chillerlan/php-settings-container/LICENSE
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Smiley <smiley@chillerlan.net>
|
||||
|
||||
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.
|
167
vendor/chillerlan/php-settings-container/README.md
vendored
Normal file
167
vendor/chillerlan/php-settings-container/README.md
vendored
Normal file
|
@ -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.
|
52
vendor/chillerlan/php-settings-container/composer.json
vendored
Normal file
52
vendor/chillerlan/php-settings-container/composer.json
vendored
Normal file
|
@ -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
|
||||
}
|
||||
}
|
4
vendor/chillerlan/php-settings-container/rules-magic-access.neon
vendored
Normal file
4
vendor/chillerlan/php-settings-container/rules-magic-access.neon
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
parameters:
|
||||
ignoreErrors:
|
||||
# yes, these are magic
|
||||
- message: "#^Access to an undefined property chillerlan\\\\Settings\\\\SettingsContainerInterface\\:\\:\\$[\\w]+\\.$#"
|
252
vendor/chillerlan/php-settings-container/src/SettingsContainerAbstract.php
vendored
Normal file
252
vendor/chillerlan/php-settings-container/src/SettingsContainerAbstract.php
vendored
Normal file
|
@ -0,0 +1,252 @@
|
|||
<?php
|
||||
/**
|
||||
* Class SettingsContainerAbstract
|
||||
*
|
||||
* @created 28.08.2018
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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<string, mixed> $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<string, mixed> $data */
|
||||
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
return $this->fromIterable($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
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<string, mixed> $data
|
||||
*/
|
||||
public function __unserialize(array $data):void{
|
||||
|
||||
foreach($data as $key => $value){
|
||||
$this->{$key} = $value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
86
vendor/chillerlan/php-settings-container/src/SettingsContainerInterface.php
vendored
Normal file
86
vendor/chillerlan/php-settings-container/src/SettingsContainerInterface.php
vendored
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
/**
|
||||
* Interface SettingsContainerInterface
|
||||
*
|
||||
* @created 28.08.2018
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @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<string, mixed>
|
||||
*/
|
||||
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<string, mixed> $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;
|
||||
|
||||
}
|
579
vendor/composer/ClassLoader.php
vendored
Normal file
579
vendor/composer/ClassLoader.php
vendored
Normal file
|
@ -0,0 +1,579 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* 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 <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @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<string, array<string, int>>
|
||||
*/
|
||||
private $prefixLengthsPsr4 = array();
|
||||
/**
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
private $prefixDirsPsr4 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
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<string, array<string, list<string>>>
|
||||
*/
|
||||
private $prefixesPsr0 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
/** @var bool */
|
||||
private $useIncludePath = false;
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private $classMap = array();
|
||||
|
||||
/** @var bool */
|
||||
private $classMapAuthoritative = false;
|
||||
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private $missingClasses = array();
|
||||
|
||||
/** @var string|null */
|
||||
private $apcuPrefix;
|
||||
|
||||
/**
|
||||
* @var array<string, self>
|
||||
*/
|
||||
private static $registeredLoaders = array();
|
||||
|
||||
/**
|
||||
* @param string|null $vendorDir
|
||||
*/
|
||||
public function __construct($vendorDir = null)
|
||||
{
|
||||
$this->vendorDir = $vendorDir;
|
||||
self::initializeIncludeClosure();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string> Array of classname => path
|
||||
*/
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $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>|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>|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>|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>|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<string, self>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
359
vendor/composer/InstalledVersions.php
vendored
Normal file
359
vendor/composer/InstalledVersions.php
vendored
Normal file
|
@ -0,0 +1,359 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* 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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||
*/
|
||||
private static $installed;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
private static $canGetVendors;
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
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<string>
|
||||
*/
|
||||
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<string>
|
||||
*/
|
||||
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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
|
||||
*/
|
||||
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<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
|
||||
*/
|
||||
public static function reload($data)
|
||||
{
|
||||
self::$installed = $data;
|
||||
self::$installedByVendor = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require __DIR__ . '/installed.php';
|
||||
self::$installed = $required;
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
|
||||
if (self::$installed !== array()) {
|
||||
$installed[] = self::$installed;
|
||||
}
|
||||
|
||||
return $installed;
|
||||
}
|
||||
}
|
21
vendor/composer/LICENSE
vendored
Normal file
21
vendor/composer/LICENSE
vendored
Normal file
|
@ -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.
|
||||
|
10
vendor/composer/autoload_classmap.php
vendored
Normal file
10
vendor/composer/autoload_classmap.php
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
);
|
9
vendor/composer/autoload_namespaces.php
vendored
Normal file
9
vendor/composer/autoload_namespaces.php
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
12
vendor/composer/autoload_psr4.php
vendored
Normal file
12
vendor/composer/autoload_psr4.php
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'chillerlan\\Settings\\' => array($vendorDir . '/chillerlan/php-settings-container/src'),
|
||||
'chillerlan\\QRCode\\' => array($vendorDir . '/chillerlan/php-qrcode/src'),
|
||||
'Norgul\\Xmpp\\' => array($vendorDir . '/norgul/xmpp-php/src'),
|
||||
);
|
38
vendor/composer/autoload_real.php
vendored
Normal file
38
vendor/composer/autoload_real.php
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit039ef714d06adc1d2f797455add3d2e5
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Composer\Autoload\ClassLoader
|
||||
*/
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
require __DIR__ . '/platform_check.php';
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit039ef714d06adc1d2f797455add3d2e5', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit039ef714d06adc1d2f797455add3d2e5', 'loadClassLoader'));
|
||||
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit039ef714d06adc1d2f797455add3d2e5::getInitializer($loader));
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
49
vendor/composer/autoload_static.php
vendored
Normal file
49
vendor/composer/autoload_static.php
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInit039ef714d06adc1d2f797455add3d2e5
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'c' =>
|
||||
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);
|
||||
}
|
||||
}
|
272
vendor/composer/installed.json
vendored
Normal file
272
vendor/composer/installed.json
vendored
Normal file
|
@ -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": []
|
||||
}
|
59
vendor/composer/installed.php
vendored
Normal file
59
vendor/composer/installed.php
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php return array(
|
||||
'root' => 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,
|
||||
),
|
||||
),
|
||||
);
|
26
vendor/composer/platform_check.php
vendored
Normal file
26
vendor/composer/platform_check.php
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
// platform_check.php @generated by Composer
|
||||
|
||||
$issues = array();
|
||||
|
||||
if (!(PHP_VERSION_ID >= 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
|
||||
);
|
||||
}
|
21
vendor/monero-integrations/monerophp/LICENSE
vendored
Normal file
21
vendor/monero-integrations/monerophp/LICENSE
vendored
Normal file
|
@ -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.
|
45
vendor/monero-integrations/monerophp/README.md
vendored
Normal file
45
vendor/monero-integrations/monerophp/README.md
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
[](https://www.php-fig.org/psr/psr-12/) [](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
|
||||

|
||||
|
||||
## 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.
|
66
vendor/monero-integrations/monerophp/composer.json
vendored
Normal file
66
vendor/monero-integrations/monerophp/composer.json
vendored
Normal file
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
1286
vendor/monero-integrations/monerophp/composer.lock
generated
vendored
Normal file
1286
vendor/monero-integrations/monerophp/composer.lock
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
166
vendor/monero-integrations/monerophp/docs/README.md
vendored
Normal file
166
vendor/monero-integrations/monerophp/docs/README.md
vendored
Normal file
|
@ -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)
|
42
vendor/monero-integrations/monerophp/docs/base58.md
vendored
Normal file
42
vendor/monero-integrations/monerophp/docs/base58.md
vendored
Normal file
|
@ -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 <String>` A hexadecimal (Base16) string to convert to Base58
|
||||
|
||||
Return: `<String>`
|
||||
|
||||
`"479cG5opa54beQWSyqNoWw5tna9sHUNmMTtiFqLPaUhDevpJ2YLwXAggSx5ePdeFrYF8cdbmVRSmp1Kn3t4Y9kFu7rZ7pFw"`
|
||||
|
||||
#### `decode`
|
||||
|
||||
Decode a Base58 string to hexadecimal (Base16)
|
||||
|
||||
Parameters:
|
||||
|
||||
- `$hex <String>` A Base58 string to convert to hexadecimal (Base16)
|
||||
|
||||
Return: `<String>`
|
||||
|
||||
`"0137F8F06C971B168745F562AA107B4D172F336271BC0F9D3B510C14D3460DFB27D8CEBE561E73AC1E11833D5EA40200EB3C82E9C66ACAF1AB1A6BB53C40537C0B7A22160B0E"`
|
||||
|
||||
### Credits
|
||||
|
||||
Written by the [Monero Integrations team](https://github.com/monero-integrations/monerophp/graphs/contributors) (<support@monerointegrations.com>)
|
||||
|
||||
Using work from:
|
||||
- bigreddmachine [MoneroPy] (https://github.com/bigreddmachine)
|
||||
- Paul Shapiro [mymonero-core-js] (https://github.com/paulshapiro)
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue