This commit is contained in:
tsf 2025-09-04 21:09:42 +08:00
parent 6942e1101c
commit ae00cfea5e
88 changed files with 7731 additions and 3 deletions

View File

@ -25,7 +25,7 @@
"alibabacloud/client": "^1.5",
"picqer/php-barcode-generator": "^2.4",
"endroid/qr-code": "^4.3",
"bacon/bacon-qr-code": "^2.0"
"chillerlan/php-qrcode": "^3.4"
},
"autoload": {
"psr-4": {

View File

@ -0,0 +1 @@
ko_fi: codemasher

View File

@ -0,0 +1,77 @@
# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
# https://github.com/sebastianbergmann/phpunit/blob/master/.github/workflows/ci.yml
on:
- pull_request
- push
name: "Continuous Integration"
jobs:
static-code-analysis:
name: "Static Code Analysis"
runs-on: ubuntu-latest
env:
PHAN_ALLOW_XDEBUG: 0
PHAN_DISABLE_XDEBUG_WARN: 1
steps:
- name: "Checkout"
uses: actions/checkout@v2
- name: "Install PHP"
uses: shivammathur/setup-php@v2
with:
php-version: "7.4"
coverage: none
tools: pecl
extensions: ast, gd, imagick, json, mbstring
- name: "Update dependencies with composer"
run: composer update --no-interaction --no-ansi --no-progress --no-suggest
- name: "Run phan"
run: php vendor/bin/phan
tests:
name: "Unit Tests"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
# - windows-latest
php-version:
- "7.2"
- "7.3"
- "7.4"
- "8.0"
steps:
- name: "Checkout"
uses: actions/checkout@v2
- name: "Install PHP with extensions"
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
coverage: pcov
tools: pecl
extensions: gd, imagick, json, mbstring
- name: "Install dependencies with composer"
run: composer update --no-ansi --no-interaction --no-progress --no-suggest
- name: "Run tests with phpunit"
run: php vendor/phpunit/phpunit/phpunit --configuration=phpunit.xml
- name: "Send code coverage report to Codecov.io"
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}

View File

@ -0,0 +1,5 @@
.build/*
.idea/*
vendor/*
composer.lock
*.phpunit.result.cache

View File

@ -0,0 +1,5 @@
filter:
excluded_paths:
- examples/*
- tests/*
- vendor/*

View File

@ -0,0 +1,24 @@
branches:
only:
- main
- v3.2.x
addons:
apt:
packages:
- imagemagick
language: php
matrix:
include:
- php: 7.2
- php: 7.3
- php: 7.4
before_install:
- pecl channel-update pecl.php.net
- printf "\n" | pecl install imagick
install: travis_retry composer install --no-interaction --prefer-source
script: vendor/bin/phpunit --configuration phpunit.xml --coverage-clover clover.xml
after_script: bash <(curl -s https://codecov.io/bash)

21
vendor/chillerlan/php-qrcode/LICENSE vendored Normal file
View 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.

392
vendor/chillerlan/php-qrcode/README.md vendored Normal file
View File

@ -0,0 +1,392 @@
# chillerlan/php-qrcode
A PHP7.2+ QR Code library based on the [implementation](https://github.com/kazuhikoarase/qrcode-generator) by [Kazuhiko Arase](https://github.com/kazuhikoarase),
namespaced, cleaned up, improved and other stuff.
[![Packagist version][packagist-badge]][packagist]
[![License][license-badge]][license]
[![Travis CI][travis-badge]][travis]
[![CodeCov][coverage-badge]][coverage]
[![Scrunitizer CI][scrutinizer-badge]][scrutinizer]
[![Packagist downloads][downloads-badge]][downloads]
[![PayPal donate][donate-badge]][donate]
[![Continuous Integration][gh-action-badge]][gh-action]
[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-qrcode.svg?style=flat-square
[packagist]: https://packagist.org/packages/chillerlan/php-qrcode
[license-badge]: https://img.shields.io/github/license/chillerlan/php-qrcode.svg?style=flat-square
[license]: https://github.com/chillerlan/php-qrcode/blob/main/LICENSE
[travis-badge]: https://img.shields.io/travis/chillerlan/php-qrcode.svg?style=flat-square
[travis]: https://travis-ci.org/chillerlan/php-qrcode
[coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-qrcode.svg?style=flat-square
[coverage]: https://codecov.io/github/chillerlan/php-qrcode
[scrutinizer-badge]: https://img.shields.io/scrutinizer/g/chillerlan/php-qrcode.svg?style=flat-square
[scrutinizer]: https://scrutinizer-ci.com/g/chillerlan/php-qrcode
[downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-qrcode.svg?style=flat-square
[downloads]: https://packagist.org/packages/chillerlan/php-qrcode/stats
[donate-badge]: https://img.shields.io/badge/donate-paypal-ff33aa.svg?style=flat-square
[donate]: https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4
[gh-action-badge]: https://github.com/chillerlan/php-qrcode/workflows/Continuous%20Integration/badge.svg
[gh-action]: https://github.com/chillerlan/php-qrcode/actions
## Documentation
### Requirements
- PHP 7.2+
- `ext-mbstring`
- optional:
- `ext-json`, `ext-gd`
- `ext-imagick` with [ImageMagick](https://imagemagick.org) installed
- [`setasign/fpdf`](https://github.com/setasign/fpdf) for the PDF output module
### Installation
**requires [composer](https://getcomposer.org)**
via terminal: `composer require chillerlan/php-qrcode`
*composer.json* (note: replace `dev-master` with a [version boundary](https://getcomposer.org/doc/articles/versions.md), e.g. `^3.2`)
```json
{
"require": {
"php": "^7.2",
"chillerlan/php-qrcode": "^3.4"
}
}
```
### Usage
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" />';
```
<p align="center">
<img alt="QR codes are awesome!" src="https://raw.githubusercontent.com/chillerlan/php-qrcode/main/examples/example_image.png">
<img alt="QR codes are awesome!" src="https://raw.githubusercontent.com/chillerlan/php-qrcode/main/examples/example_svg.png">
</p>
Wait, what was that? Please again, slower!
### Advanced usage
Ok, step by step. First you'll need a `QRCode` instance, which can be optionally invoked with a `QROptions` (or a [`SettingsContainerInterface`](https://github.com/chillerlan/php-settings-container/blob/master/src/SettingsContainerInterface.php), respectively) object as the only parameter.
```php
$options = new QROptions([
'version' => 5,
'outputType' => QRCode::OUTPUT_MARKUP_SVG,
'eccLevel' => QRCode::ECC_L,
]);
// invoke a fresh QRCode instance
$qrcode = new QRCode($options);
// and dump the output
$qrcode->render($data);
// ...with additional cache file
$qrcode->render($data, '/path/to/file.svg');
```
In case you just want the raw QR code matrix, call `QRCode::getMatrix()` - this method is also called internally from `QRCode::render()`. See also [Custom output modules](#custom-qroutputinterface).
```php
$matrix = $qrcode->getMatrix($data);
foreach($matrix->matrix() as $y => $row){
foreach($row as $x => $module){
// get a module's value
$value = $module;
$value = $matrix->get($x, $y);
// boolean check a module
if($matrix->check($x, $y)){ // if($module >> 8 > 0)
// do stuff, the module is dark
}
else{
// do other stuff, the module is light
}
}
}
```
Have a look [in this folder](https://github.com/chillerlan/php-qrcode/tree/master/examples) for some more usage examples.
#### Custom module values
Previous versions of `QRCode` held only boolean matrix values that only allowed to determine whether a module was dark or not. Now you can distinguish between different parts of the matrix, namely the several required patterns from the QR Code specification, and use them in different ways.
The dark value is the module (light) value shifted by 8 bits to the left: `$value = $M_TYPE << ($bool ? 8 : 0);`, where `$M_TYPE` is one of the `QRMatrix::M_*` constants.
You can check the value for a type explicitly like...
```php
// for true (dark)
$value >> 8 === $M_TYPE;
//for false (light)
$value === $M_TYPE;
```
...or you can perform a loose check, ignoring the module value
```php
// for true
$value >> 8 > 0;
// for false
$value >> 8 === 0
```
See also `QRMatrix::set()`, `QRMatrix::check()` and [`QRMatrix` constants](#qrmatrix-constants).
To map the values and properly render the modules for the given `QROutputInterface`, it's necessary to overwrite the default values:
```php
$options = new QROptions;
// for HTML, SVG and ImageMagick
$options->moduleValues = [
// finder
1536 => '#A71111', // dark (true)
6 => '#FFBFBF', // light (false)
// alignment
2560 => '#A70364',
10 => '#FFC9C9',
// timing
3072 => '#98005D',
12 => '#FFB8E9',
// format
3584 => '#003804',
14 => '#00FB12',
// version
4096 => '#650098',
16 => '#E0B8FF',
// data
1024 => '#4A6000',
4 => '#ECF9BE',
// darkmodule
512 => '#080063',
// separator
8 => '#AFBFBF',
// quietzone
18 => '#FFFFFF',
];
// for the image output types
$options->moduleValues = [
512 => [0, 0, 0],
// ...
];
// for string/text output
$options->moduleValues = [
512 => '#',
// ...
];
```
#### Custom `QROutputInterface`
Instead of bloating your code you can simply create your own output interface by extending `QROutputAbstract`. Have a look at the [built-in output modules](https://github.com/chillerlan/php-qrcode/tree/master/src/Output).
```php
class MyCustomOutput extends QROutputAbstract{
// inherited from QROutputAbstract
protected $matrix; // QRMatrix
protected $moduleCount; // modules QRMatrix::size()
protected $options; // MyCustomOptions or QROptions
protected $scale; // scale factor from options
protected $length; // length of the matrix ($moduleCount * $scale)
// ...check/set default module values (abstract method, called by the constructor)
protected function setModuleValues():void{
// $this->moduleValues = ...
}
// QROutputInterface::dump()
public function dump(string $file = null):string{
$output = '';
for($row = 0; $row < $this->moduleCount; $row++){
for($col = 0; $col < $this->moduleCount; $col++){
$output .= (int)$this->matrix->check($col, $row);
}
}
return $output;
}
}
```
In case you need additional settings for your output module, just extend `QROptions`...
```
class MyCustomOptions extends QROptions{
protected $myParam = 'defaultValue';
// ...
}
```
...or use the [`SettingsContainerInterface`](https://github.com/chillerlan/php-settings-container/blob/master/src/SettingsContainerInterface.php), which is the more flexible approach.
```php
trait MyCustomOptionsTrait{
protected $myParam = 'defaultValue';
// ...
}
```
set the options:
```php
$myOptions = [
'version' => 5,
'eccLevel' => QRCode::ECC_L,
'outputType' => QRCode::OUTPUT_CUSTOM,
'outputInterface' => MyCustomOutput::class,
// your custom settings
'myParam' => 'whatever value',
];
// extends QROptions
$myCustomOptions = new MyCustomOptions($myOptions);
// using the SettingsContainerInterface
$myCustomOptions = new class($myOptions) extends SettingsContainerAbstract{
use QROptionsTrait, MyCustomOptionsTrait;
};
```
You can then call `QRCode` with the custom modules...
```php
(new QRCode($myCustomOptions))->render($data);
```
...or invoke the `QROutputInterface` manually.
```php
$qrOutputInterface = new MyCustomOutput($myCustomOptions, (new QRCode($myCustomOptions))->getMatrix($data));
//dump the output, which is equivalent to QRCode::render()
$qrOutputInterface->dump();
```
### API
#### `QRCode` methods
method | return | description
------ | ------ | -----------
`__construct(QROptions $options = null)` | - | see [`SettingsContainerInterface`](https://github.com/chillerlan/php-settings-container/blob/master/src/SettingsContainerInterface.php)
`render(string $data, string $file = null)` | mixed, `QROutputInterface::dump()` | renders a QR Code for the given `$data` and `QROptions`, saves `$file` optional
`getMatrix(string $data)` | `QRMatrix` | returns a `QRMatrix` object for the given `$data` and current `QROptions`
`initDataInterface(string $data)` | `QRDataInterface` | returns a fresh `QRDataInterface` for the given `$data`
`isNumber(string $string)` | bool | checks if a string qualifies for `Number`
`isAlphaNum(string $string)` | bool | checks if a string qualifies for `AlphaNum`
`isKanji(string $string)` | bool | checks if a string qualifies for `Kanji`
#### `QRCode` constants
name | description
---- | -----------
`VERSION_AUTO` | `QROptions::$version`
`MASK_PATTERN_AUTO` | `QROptions::$maskPattern`
`OUTPUT_MARKUP_SVG`, `OUTPUT_MARKUP_HTML` | `QROptions::$outputType` markup
`OUTPUT_IMAGE_PNG`, `OUTPUT_IMAGE_JPG`, `OUTPUT_IMAGE_GIF` | `QROptions::$outputType` image
`OUTPUT_STRING_JSON`, `OUTPUT_STRING_TEXT` | `QROptions::$outputType` string
`OUTPUT_IMAGICK` | `QROptions::$outputType` ImageMagick
`OUTPUT_FPDF` | `QROptions::$outputType` PDF, using [FPDF](https://github.com/setasign/fpdf)
`OUTPUT_CUSTOM` | `QROptions::$outputType`, requires `QROptions::$outputInterface`
`ECC_L`, `ECC_M`, `ECC_Q`, `ECC_H`, | ECC-Level: 7%, 15%, 25%, 30% in `QROptions::$eccLevel`
`DATA_NUMBER`, `DATA_ALPHANUM`, `DATA_BYTE`, `DATA_KANJI` | `QRDataInterface::$datamode`
#### `QROptions` properties
property | type | default | allowed | description
-------- | ---- | ------- | ------- | -----------
`$version` | int | `QRCode::VERSION_AUTO` | 1...40 | the [QR Code version number](http://www.qrcode.com/en/about/version.html)
`$versionMin` | int | 1 | 1...40 | Minimum QR version (if `$version = QRCode::VERSION_AUTO`)
`$versionMax` | int | 40 | 1...40 | Maximum QR version (if `$version = QRCode::VERSION_AUTO`)
`$eccLevel` | int | `QRCode::ECC_L` | `QRCode::ECC_X` | Error correct level, where X = L (7%), M (15%), Q (25%), H (30%)
`$maskPattern` | int | `QRCode::MASK_PATTERN_AUTO` | 0...7 | Mask Pattern to use
`$addQuietzone` | bool | `true` | - | Add a "quiet zone" (margin) according to the QR code spec
`$quietzoneSize` | int | 4 | clamped to 0 ... `$matrixSize / 2` | Size of the quiet zone
`$dataMode` | string | `null` | `Number`, `AlphaNum`, `Kanji`, `Byte` | allows overriding the data type detection
`$outputType` | string | `QRCode::OUTPUT_IMAGE_PNG` | `QRCode::OUTPUT_*` | built-in output type
`$outputInterface` | string | `null` | * | FQCN of the custom `QROutputInterface` if `QROptions::$outputType` is set to `QRCode::OUTPUT_CUSTOM`
`$cachefile` | string | `null` | * | optional cache file path
`$eol` | string | `PHP_EOL` | * | newline string (HTML, SVG, TEXT)
`$scale` | int | 5 | * | size of a QR code pixel (SVG, IMAGE_*), HTML -> via CSS
`$cssClass` | string | `null` | * | a common css class
`$svgOpacity` | float | 1.0 | 0...1 |
`$svgDefs` | string | * | * | anything between [`<defs>`](https://developer.mozilla.org/docs/Web/SVG/Element/defs)
`$svgViewBoxSize` | int | `null` | * | a positive integer which defines width/height of the [viewBox attribute](https://css-tricks.com/scale-svg/#article-header-id-3)
`$textDark` | string | '🔴' | * | string substitute for dark
`$textLight` | string | '⭕' | * | string substitute for light
`$markupDark` | string | '#000' | * | markup substitute for dark (CSS value)
`$markupLight` | string | '#fff' | * | markup substitute for light (CSS value)
`$imageBase64` | bool | `true` | - | whether to return the image data as base64 or raw like from `file_get_contents()`
`$imageTransparent` | bool | `true` | - | toggle transparency (no jpeg support)
`$imageTransparencyBG` | array | `[255, 255, 255]` | `[R, G, B]` | the RGB values for the transparent color, see [`imagecolortransparent()`](http://php.net/manual/function.imagecolortransparent.php)
`$pngCompression` | int | -1 | -1 ... 9 | `imagepng()` compression level, -1 = auto
`$jpegQuality` | int | 85 | 0 - 100 | `imagejpeg()` quality
`$imagickFormat` | string | 'png' | * | ImageMagick output type, see `Imagick::setType()`
`$imagickBG` | string | `null` | * | ImageMagick background color, see `ImagickPixel::__construct()`
`$moduleValues` | array | `null` | * | Module values map, see [Custom output modules](#custom-qroutputinterface) and `QROutputInterface::DEFAULT_MODULE_VALUES`
#### `QRMatrix` methods
method | return | description
------ | ------ | -----------
`__construct(int $version, int $eclevel)` | - | -
`matrix()` | array | the internal matrix representation as a 2 dimensional array
`version()` | int | the current QR Code version
`eccLevel()` | int | current ECC level
`maskPattern()` | int | the used mask pattern
`size()` | int | the absoulute size of the matrix, including quiet zone (if set). `$version * 4 + 17 + 2 * $quietzone`
`get(int $x, int $y)` | int | returns the value of the module
`set(int $x, int $y, bool $value, int $M_TYPE)` | `QRMatrix` | sets the `$M_TYPE` value for the module
`check(int $x, int $y)` | bool | checks whether a module is true (dark) or false (light)
`setLogoSpace(int $width, int $height, int $startX = null, int $startY = null)` | `QRMatrix` | creates a logo space in the matrix
#### `QRMatrix` constants
name | light (false) | dark (true) | description
---- | ------------- | ----------- | -----------
`M_NULL` | 0 | - | module not set (should never appear. if so, there's an error)
`M_DARKMODULE` | - | 512 | once per matrix at `$xy = [8, 4 * $version + 9]`
`M_DATA` | 4 | 1024 | the actual encoded data
`M_FINDER` | 6 | 1536 | the 7x7 finder patterns
`M_SEPARATOR` | 8 | - | separator lines around the finder patterns
`M_ALIGNMENT` | 10 | 2560 | the 5x5 alignment patterns
`M_TIMING` | 12 | 3072 | the timing pattern lines
`M_FORMAT` | 14 | 3584 | format information pattern
`M_VERSION` | 16 | 4096 | version information pattern
`M_QUIETZONE` | 18 | - | margin around the QR Code
`M_LOGO` | 20 | - | space for a logo image (not used yet)
`M_TEST` | 255 | 65280 | test value
### Notes
The QR encoder, especially the subroutines for mask pattern testing, can cause high CPU load on increased matrix size.
You can avoid a part of this load by choosing a fast output module, like `OUTPUT_IMAGE_*` and setting the mask pattern manually (which may result in unreadable QR Codes).
Oh hey and don't forget to sanitize any user input!
### Disclaimer!
I don't take responsibility for molten CPUs, misled applications, failed log-ins etc.. Use at your own risk!
#### Trademark Notice
The word "QR Code" is registered trademark of *DENSO WAVE INCORPORATED*<br>
http://www.denso-wave.com/qrcode/faqpatent-e.html
### Framework Integration
- Drupal [Google Authenticator Login `ga_login`](https://www.drupal.org/project/ga_login)
- WordPress [`wp-two-factor-auth`](https://github.com/sjinks/wp-two-factor-auth)
- WordPress [Simple 2FA `simple-2fa`](https://wordpress.org/plugins/simple-2fa/)
- WoltLab Suite [two-step-verification](http://pluginstore.woltlab.com/file/3007-two-step-verification/)
- [Cachet](https://github.com/CachetHQ/Cachet)
- [Appwrite](https://github.com/appwrite/appwrite)
- other uses: [dependents](https://github.com/chillerlan/php-qrcode/network/dependents) / [packages](https://github.com/chillerlan/php-qrcode/network/dependents?dependent_type=PACKAGE)
Hi, please check out my other projects that are way cooler than qrcodes!
- [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-httpinterface](https://github.com/chillerlan/php-httpinterface) - a PSR-7/15/17/18 implemetation
- [php-database](https://github.com/chillerlan/php-database) - a database client & querybuilder for MySQL, Postgres, SQLite, MSSQL, Firebird

View File

@ -0,0 +1,52 @@
{
"name": "chillerlan/php-qrcode",
"description": "A QR code generator. PHP 7.2+",
"homepage": "https://github.com/chillerlan/php-qrcode",
"license": "MIT",
"minimum-stability": "stable",
"type": "library",
"keywords": [
"QR code", "qrcode", "qr", "qrcode-generator", "phpqrcode"
],
"authors": [
{
"name": "Kazuhiko Arase",
"homepage": "https://github.com/kazuhikoarase"
},
{
"name": "Smiley",
"email": "smiley@chillerlan.net",
"homepage": "https://github.com/codemasher"
},
{
"name": "Contributors",
"homepage":"https://github.com/chillerlan/php-qrcode/graphs/contributors"
}
],
"require": {
"php": "^7.2 || ^8.0",
"ext-mbstring": "*",
"chillerlan/php-settings-container": "^1.2.2"
},
"require-dev": {
"phpunit/phpunit": "^8.5",
"phan/phan": "^3.2.2",
"setasign/fpdf": "^1.8.2"
},
"suggest": {
"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
"setasign/fpdf": "Required to use the QR FPDF output."
},
"autoload": {
"psr-4": {
"chillerlan\\QRCode\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"chillerlan\\QRCodePublic\\": "public/",
"chillerlan\\QRCodeTest\\": "tests/",
"chillerlan\\QRCodeExamples\\": "examples/"
}
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* Class MyCustomOutput
*
* @filesource MyCustomOutput.php
* @created 24.12.2017
* @package chillerlan\QRCodeExamples
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeExamples;
use chillerlan\QRCode\Output\QROutputAbstract;
class MyCustomOutput extends QROutputAbstract{
protected function setModuleValues():void{
// TODO: Implement setModuleValues() method.
}
public function dump(string $file = null){
$output = '';
for($row = 0; $row < $this->moduleCount; $row++){
for($col = 0; $col < $this->moduleCount; $col++){
$output .= (int)$this->matrix->check($col, $row);
}
}
return $output;
}
}

View File

@ -0,0 +1,81 @@
<?php
/**
* Class QRImageWithLogo
*
* @filesource QRImageWithLogo.php
* @created 18.11.2020
* @package chillerlan\QRCodeExamples
* @author smiley <smiley@chillerlan.net>
* @copyright 2020 smiley
* @license MIT
*
* @noinspection PhpComposerExtensionStubsInspection
*/
namespace chillerlan\QRCodeExamples;
use chillerlan\QRCode\Output\{QRCodeOutputException, QRImage};
use function imagecopyresampled, imagecreatefrompng, imagesx, imagesy, is_file, is_readable;
/**
* @property \chillerlan\QRCodeExamples\LogoOptions $options
*/
class QRImageWithLogo extends QRImage{
/**
* @param string|null $file
* @param string|null $logo
*
* @return string
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
*/
public function dump(string $file = null, string $logo = null):string{
// set returnResource to true to skip further processing for now
$this->options->returnResource = true;
// of course you could accept other formats too (such as resource or Imagick)
// i'm not checking for the file type either for simplicity reasons (assuming PNG)
if(!is_file($logo) || !is_readable($logo)){
throw new QRCodeOutputException('invalid logo');
}
$this->matrix->setLogoSpace(
$this->options->logoSpaceWidth,
$this->options->logoSpaceHeight
// not utilizing the position here
);
// there's no need to save the result of dump() into $this->image here
parent::dump($file);
$im = imagecreatefrompng($logo);
// get logo image size
$w = imagesx($im);
$h = imagesy($im);
// set new logo size, leave a border of 1 module (no proportional resize/centering)
$lw = ($this->options->logoSpaceWidth - 2) * $this->options->scale;
$lh = ($this->options->logoSpaceHeight - 2) * $this->options->scale;
// get the qrcode size
$ql = $this->matrix->size() * $this->options->scale;
// scale the logo and copy it over. done!
imagecopyresampled($this->image, $im, ($ql - $lw) / 2, ($ql - $lh) / 2, 0, 0, $lw, $lh, $w, $h);
$imageData = $this->dumpImage();
if($file !== null){
$this->saveToFile($imageData, $file);
}
if($this->options->imageBase64){
$imageData = 'data:image/'.$this->options->outputType.';base64,'.base64_encode($imageData);
}
return $imageData;
}
}

View File

@ -0,0 +1,104 @@
<?php
/**
* Class QRImageWithText
*
* example for additional text
*
* @link https://github.com/chillerlan/php-qrcode/issues/35
*
* @filesource QRImageWithText.php
* @created 22.06.2019
* @package chillerlan\QRCodeExamples
* @author smiley <smiley@chillerlan.net>
* @copyright 2019 smiley
* @license MIT
*
* @noinspection PhpComposerExtensionStubsInspection
*/
namespace chillerlan\QRCodeExamples;
use chillerlan\QRCode\Output\QRImage;
use function base64_encode, imagechar, imagecolorallocate, imagecolortransparent, imagecopymerge, imagecreatetruecolor,
imagedestroy, imagefilledrectangle, imagefontwidth, in_array, round, str_split, strlen;
class QRImageWithText extends QRImage{
/**
* @param string|null $file
* @param string|null $text
*
* @return string
*/
public function dump(string $file = null, string $text = null):string{
$this->image = imagecreatetruecolor($this->length, $this->length);
$background = imagecolorallocate($this->image, ...$this->options->imageTransparencyBG);
if((bool)$this->options->imageTransparent && in_array($this->options->outputType, $this::TRANSPARENCY_TYPES, true)){
imagecolortransparent($this->image, $background);
}
imagefilledrectangle($this->image, 0, 0, $this->length, $this->length, $background);
foreach($this->matrix->matrix() as $y => $row){
foreach($row as $x => $M_TYPE){
$this->setPixel($x, $y, $this->moduleValues[$M_TYPE]);
}
}
// render text output if a string is given
if($text !== null){
$this->addText($text);
}
$imageData = $this->dumpImage($file);
if((bool)$this->options->imageBase64){
$imageData = 'data:image/'.$this->options->outputType.';base64,'.base64_encode($imageData);
}
return $imageData;
}
/**
* @param string $text
*/
protected function addText(string $text):void{
// save the qrcode image
$qrcode = $this->image;
// options things
$textSize = 3; // see imagefontheight() and imagefontwidth()
$textBG = [200, 200, 200];
$textColor = [50, 50, 50];
$bgWidth = $this->length;
$bgHeight = $bgWidth + 20; // 20px extra space
// create a new image with additional space
$this->image = imagecreatetruecolor($bgWidth, $bgHeight);
$background = imagecolorallocate($this->image, ...$textBG);
// allow transparency
if((bool)$this->options->imageTransparent && in_array($this->options->outputType, $this::TRANSPARENCY_TYPES, true)){
imagecolortransparent($this->image, $background);
}
// fill the background
imagefilledrectangle($this->image, 0, 0, $bgWidth, $bgHeight, $background);
// copy over the qrcode
imagecopymerge($this->image, $qrcode, 0, 0, 0, 0, $this->length, $this->length, 100);
imagedestroy($qrcode);
$fontColor = imagecolorallocate($this->image, ...$textColor);
$w = imagefontwidth($textSize);
$x = round(($bgWidth - strlen($text) * $w) / 2);
// loop through the string and draw the letters
foreach(str_split($text) as $i => $chr){
imagechar($this->image, $textSize, $i * $w + $x, $this->length, $chr, $fontColor);
}
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
*
* @filesource custom_output.php
* @created 24.12.2017
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeExamples;
use chillerlan\QRCode\{QRCode, QROptions};
require_once __DIR__.'/../vendor/autoload.php';
$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s';
// invoke the QROutputInterface manually
$options = new QROptions([
'version' => 5,
'eccLevel' => QRCode::ECC_L,
]);
$qrOutputInterface = new MyCustomOutput($options, (new QRCode($options))->getMatrix($data));
var_dump($qrOutputInterface->dump());
// or just
$options = new QROptions([
'version' => 5,
'eccLevel' => QRCode::ECC_L,
'outputType' => QRCode::OUTPUT_CUSTOM,
'outputInterface' => MyCustomOutput::class,
]);
var_dump((new QRCode($options))->render($data));

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,47 @@
<?php
namespace chillerlan\QRCodeExamples;
use chillerlan\QRCode\{QRCode, QROptions};
require_once __DIR__ . '/../vendor/autoload.php';
$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s';
$options = new QROptions([
'version' => 7,
'outputType' => QRCode::OUTPUT_FPDF,
'eccLevel' => QRCode::ECC_L,
'scale' => 5,
'imageBase64' => false,
'moduleValues' => [
// finder
1536 => [0, 63, 255], // dark (true)
6 => [255, 255, 255], // light (false), white is the transparency color and is enabled by default
// alignment
2560 => [255, 0, 255],
10 => [255, 255, 255],
// timing
3072 => [255, 0, 0],
12 => [255, 255, 255],
// format
3584 => [67, 191, 84],
14 => [255, 255, 255],
// version
4096 => [62, 174, 190],
16 => [255, 255, 255],
// data
1024 => [0, 0, 0],
4 => [255, 255, 255],
// darkmodule
512 => [0, 0, 0],
// separator
8 => [255, 255, 255],
// quietzone
18 => [255, 255, 255],
],
]);
\header('Content-type: application/pdf');
echo (new QRCode($options))->render($data);

View File

@ -0,0 +1,102 @@
<?php
/**
*
* @filesource html.php
* @created 21.12.2017
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeExamples;
use chillerlan\QRCode\{QRCode, QROptions};
require_once '../vendor/autoload.php';
header('Content-Type: text/html; charset=utf-8');
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>QRCode test</title>
<style>
body{
margin: 5em;
padding: 0;
}
div.qrcode{
margin: 0;
padding: 0;
}
/* rows */
div.qrcode > div {
margin: 0;
padding: 0;
height: 10px;
}
/* modules */
div.qrcode > div > span {
display: inline-block;
width: 10px;
height: 10px;
}
div.qrcode > div > span {
background-color: #ccc;
}
</style>
</head>
<body>
<div class="qrcode">
<?php
$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s';
$options = new QROptions([
'version' => 5,
'outputType' => QRCode::OUTPUT_MARKUP_HTML,
'eccLevel' => QRCode::ECC_L,
'moduleValues' => [
// finder
1536 => '#A71111', // dark (true)
6 => '#FFBFBF', // light (false)
// alignment
2560 => '#A70364',
10 => '#FFC9C9',
// timing
3072 => '#98005D',
12 => '#FFB8E9',
// format
3584 => '#003804',
14 => '#00FB12',
// version
4096 => '#650098',
16 => '#E0B8FF',
// data
1024 => '#4A6000',
4 => '#ECF9BE',
// darkmodule
512 => '#080063',
// separator
8 => '#AFBFBF',
// quietzone
18 => '#FFFFFF',
],
]);
echo (new QRCode($options))->render($data);
?>
</div>
</body>
</html>

View File

@ -0,0 +1,60 @@
<?php
/**
*
* @filesource image.php
* @created 24.12.2017
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeExamples;
use chillerlan\QRCode\{QRCode, QROptions};
require_once __DIR__.'/../vendor/autoload.php';
$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s';
$options = new QROptions([
'version' => 7,
'outputType' => QRCode::OUTPUT_IMAGE_PNG,
'eccLevel' => QRCode::ECC_L,
'scale' => 5,
'imageBase64' => false,
'moduleValues' => [
// finder
1536 => [0, 63, 255], // dark (true)
6 => [255, 255, 255], // light (false), white is the transparency color and is enabled by default
// alignment
2560 => [255, 0, 255],
10 => [255, 255, 255],
// timing
3072 => [255, 0, 0],
12 => [255, 255, 255],
// format
3584 => [67, 191, 84],
14 => [255, 255, 255],
// version
4096 => [62, 174, 190],
16 => [255, 255, 255],
// data
1024 => [0, 0, 0],
4 => [255, 255, 255],
// darkmodule
512 => [0, 0, 0],
// separator
8 => [255, 255, 255],
// quietzone
18 => [255, 255, 255],
],
]);
header('Content-type: image/png');
echo (new QRCode($options))->render($data);

View File

@ -0,0 +1,45 @@
<?php
/**
*
* @filesource imageWithLogo.php
* @created 18.11.2020
* @author smiley <smiley@chillerlan.net>
* @copyright 2020 smiley
* @license MIT
*/
namespace chillerlan\QRCodeExamples;
use chillerlan\QRCode\{QRCode, QROptions};
require_once __DIR__.'/../vendor/autoload.php';
$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s';
/**
* @property int $logoSpaceWidth
* @property int $logoSpaceHeight
*
* @noinspection PhpIllegalPsrClassPathInspection
*/
class LogoOptions extends QROptions{
// size in QR modules, multiply with QROptions::$scale for pixel size
protected $logoSpaceWidth;
protected $logoSpaceHeight;
}
$options = new LogoOptions;
$options->version = 7;
$options->eccLevel = QRCode::ECC_H;
$options->imageBase64 = false;
$options->logoSpaceWidth = 13;
$options->logoSpaceHeight = 13;
$options->scale = 5;
$options->imageTransparent = false;
header('Content-type: image/png');
$qrOutputInterface = new QRImageWithLogo($options, (new QRCode($options))->getMatrix($data));
// dump the output, with an additional logo
echo $qrOutputInterface->dump(null, __DIR__.'/octocat.png');

View File

@ -0,0 +1,33 @@
<?php
/**
* example for additional text
* @link https://github.com/chillerlan/php-qrcode/issues/35
*
* @filesource imageWithText.php
* @created 22.06.2019
* @author Smiley <smiley@chillerlan.net>
* @copyright 2019 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeExamples;
use chillerlan\QRCode\{QRCode, QROptions};
require_once __DIR__.'/../vendor/autoload.php';
$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s';
$options = new QROptions([
'version' => 7,
'outputType' => QRCode::OUTPUT_IMAGE_PNG,
'scale' => 3,
'imageBase64' => false,
]);
header('Content-type: image/png');
$qrOutputInterface = new QRImageWithText($options, (new QRCode($options))->getMatrix($data));
// dump the output, with additional text
echo $qrOutputInterface->dump(null, 'example text');

View File

@ -0,0 +1,59 @@
<?php
/**
*
* @filesource image.php
* @created 24.12.2017
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeExamples;
use chillerlan\QRCode\{QRCode, QROptions};
require_once __DIR__.'/../vendor/autoload.php';
$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s';
$options = new QROptions([
'version' => 7,
'outputType' => QRCode::OUTPUT_IMAGICK,
'eccLevel' => QRCode::ECC_L,
'scale' => 5,
'moduleValues' => [
// finder
1536 => '#A71111', // dark (true)
6 => '#FFBFBF', // light (false)
// alignment
2560 => '#A70364',
10 => '#FFC9C9',
// timing
3072 => '#98005D',
12 => '#FFB8E9',
// format
3584 => '#003804',
14 => '#00FB12',
// version
4096 => '#650098',
16 => '#E0B8FF',
// data
1024 => '#4A6000',
4 => '#ECF9BE',
// darkmodule
512 => '#080063',
// separator
8 => '#DDDDDD',
// quietzone
18 => '#DDDDDD',
],
]);
header('Content-type: image/png');
echo (new QRCode($options))->render($data);

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,77 @@
<?php
/**
*
* @filesource svg.php
* @created 21.12.2017
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeExamples;
use chillerlan\QRCode\{QRCode, QROptions};
require_once __DIR__.'/../vendor/autoload.php';
$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s';
$gzip = true;
$options = new QROptions([
'version' => 7,
'outputType' => QRCode::OUTPUT_MARKUP_SVG,
'eccLevel' => QRCode::ECC_L,
'svgViewBoxSize' => 530,
'addQuietzone' => true,
'cssClass' => 'my-css-class',
'svgOpacity' => 1.0,
'svgDefs' => '
<linearGradient id="g2">
<stop offset="0%" stop-color="#39F" />
<stop offset="100%" stop-color="#F3F" />
</linearGradient>
<linearGradient id="g1">
<stop offset="0%" stop-color="#F3F" />
<stop offset="100%" stop-color="#39F" />
</linearGradient>
<style>rect{shape-rendering:crispEdges}</style>',
'moduleValues' => [
// finder
1536 => 'url(#g1)', // dark (true)
6 => '#fff', // light (false)
// alignment
2560 => 'url(#g1)',
10 => '#fff',
// timing
3072 => 'url(#g1)',
12 => '#fff',
// format
3584 => 'url(#g1)',
14 => '#fff',
// version
4096 => 'url(#g1)',
16 => '#fff',
// data
1024 => 'url(#g2)',
4 => '#fff',
// darkmodule
512 => 'url(#g1)',
// separator
8 => '#fff',
// quietzone
18 => '#fff',
],
]);
$qrcode = (new QRCode($options))->render($data);
header('Content-type: image/svg+xml');
if($gzip === true){
header('Vary: Accept-Encoding');
header('Content-Encoding: gzip');
$qrcode = gzencode($qrcode ,9);
}
echo $qrcode;

View File

@ -0,0 +1,68 @@
<?php
/**
*
* @filesource text.php
* @created 21.12.2017
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeExamples;
use chillerlan\QRCode\{QRCode, QROptions};
require_once __DIR__.'/../vendor/autoload.php';
$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s';
$options = new QROptions([
'version' => 5,
'outputType' => QRCode::OUTPUT_STRING_TEXT,
'eccLevel' => QRCode::ECC_L,
]);
// <pre> to view it in a browser
echo '<pre style="font-size: 75%; line-height: 1;">'.(new QRCode($options))->render($data).'</pre>';
// custom values
$options = new QROptions([
'version' => 5,
'outputType' => QRCode::OUTPUT_STRING_TEXT,
'eccLevel' => QRCode::ECC_L,
'moduleValues' => [
// finder
1536 => 'A', // dark (true)
6 => 'a', // light (false)
// alignment
2560 => 'B',
10 => 'b',
// timing
3072 => 'C',
12 => 'c',
// format
3584 => 'D',
14 => 'd',
// version
4096 => 'E',
16 => 'e',
// data
1024 => 'F',
4 => 'f',
// darkmodule
512 => 'G',
// separator
8 => 'h',
// quietzone
18 => 'i',
],
]);
// <pre> to view it in a browser
echo '<pre style="font-size: 75%; line-height: 1;">'.(new QRCode($options))->render($data).'</pre>';

15
vendor/chillerlan/php-qrcode/phpdoc.xml vendored Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<phpdoc>
<parser>
<target>public/docs</target>
</parser>
<transformer>
<target>public/docs</target>
</transformer>
<files>
<directory>src</directory>
</files>
<transformations>
<template name="responsive-twig"/>
</transformations>
</phpdoc>

34
vendor/chillerlan/php-qrcode/phpmd.xml vendored Normal file
View File

@ -0,0 +1,34 @@
<?xml version="1.0"?>
<ruleset name="codemasher/php-qrcode PMD ruleset"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
<description>codemasher/php-qrcode PMD ruleset</description>
<exclude-pattern>*/examples/*</exclude-pattern>
<exclude-pattern>*/tests/*</exclude-pattern>
<rule ref="rulesets/cleancode.xml">
<exclude name="BooleanArgumentFlag"/>
</rule>
<rule ref="rulesets/codesize.xml/CyclomaticComplexity">
<priority>1</priority>
<properties>
<property name="maximum" value="200" />
</properties>
</rule>
<rule ref="rulesets/controversial.xml">
<exclude name="CamelCaseMethodName"/>
<exclude name="CamelCasePropertyName"/>
<exclude name="CamelCaseParameterName"/>
<exclude name="CamelCaseVariableName"/>
</rule>
<rule ref="rulesets/design.xml">
</rule>
<rule ref="rulesets/naming.xml">
<exclude name="LongVariable"/>
<exclude name="ShortVariable"/>
</rule>
<rule ref="rulesets/unusedcode.xml">
<exclude name="UnusedFormalParameter"/>
</rule>
</ruleset>

View File

@ -0,0 +1,23 @@
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheResultFile=".build/phpunit.result.cache"
colors="true"
verbose="true"
>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
<testsuites>
<testsuite name="php-qrcode test suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
<logging>
<log type="coverage-clover" target=".build/coverage/clover.xml"/>
<log type="coverage-xml" target=".build/coverage/coverage-xml"/>
<log type="junit" target=".build/logs/junit.xml"/>
</logging>
</phpunit>

View File

@ -0,0 +1,163 @@
<!DOCTYPE html>
<html lang="en" >
<head >
<meta charset="UTF-8" >
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title >QR Code Generator</title >
<style >
body{ font-size: 20px; line-height: 1.4em; font-family: "Trebuchet MS", sans-serif; color: #000;}
input, textarea, select{font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 75%; line-height: 1.25em; border: 1px solid #aaa; }
input:focus, textarea:focus, select:focus{ border: 1px solid #ccc; }
label{ cursor: pointer; }
#qrcode-settings, div#qrcode-output{ text-align: center; }
div#qrcode-output > div {margin: 0;padding: 0;height: 3px;}
div#qrcode-output > div > span {display: inline-block;width: 3px;height: 3px;}
div#qrcode-output > div > span {background-color: lightgrey;}
</style >
</head >
<body >
<form id="qrcode-settings" >
<label for="inputstring" >Input String</label ><br /><textarea name="inputstring" id="inputstring" cols="80" rows="3" autocomplete="off" spellcheck="false"></textarea ><br />
<label for="version" >Version</label >
<input id="version" name="version" class="options" type="number" min="1" max="40" value="5" placeholder="version" />
<label for="maskpattern" >Mask Pattern</label >
<input id="maskpattern" name="maskpattern" class="options" type="number" min="-1" max="7" value="-1" placeholder="mask pattern" />
<label for="ecc" >ECC</label >
<select class="options" id="ecc" name="ecc" >
<option value="L" selected="selected" >L - 7%</option >
<option value="M" >M - 15%</option >
<option value="Q" >Q - 25%</option >
<option value="H" >H - 30%</option >
</select >
<br />
<label for="quietzone" >Quiet Zone
<input id="quietzone" name="quietzone" class="options" type="checkbox" value="true" />
</label >
<label for="quietzonesize" >size</label >
<input id="quietzonesize" name="quietzonesize" class="options" type="number" min="0" max="100" value="4" placeholder="quiet zone" />
<br />
<label for="output_type" >Output</label >
<select class="options" id="output_type" name="output_type" >
<option value="html" >Markup - HTML</option >
<option value="svg" selected="selected" >Markup - SVG</option >
<option value="png">Image - png</option >
<option value="jpg" >Image - jpg</option >
<option value="gif" >Image - gif</option >
<option value="text" >String - text</option >
<option value="json" >String - json</option >
</select >
<label for="scale" >scale</label >
<input id="scale" name="scale" class="options" type="number" min="1" max="10" value="5" placeholder="scale" />
<div>Finder</div>
<label for="m_finder_light" >
<input type="text" id="m_finder_light" name="m_finder_light" class="jscolor options" value="ffffff" autocomplete="off" spellcheck="false" minlength="6" maxlength="6" />
</label >
<label for="m_finder_dark" >
<input type="text" id="m_finder_dark" name="m_finder_dark" class="jscolor options" value="000000" autocomplete="off" spellcheck="false" minlength="6" maxlength="6" />
</label >
<div>Alignment</div>
<label for="m_alignment_light" >
<input type="text" id="m_alignment_light" name="m_alignment_light" class="jscolor options" value="ffffff" autocomplete="off" spellcheck="false" minlength="6" maxlength="6" />
</label >
<label for="m_alignment_dark" >
<input type="text" id="m_alignment_dark" name="m_alignment_dark" class="jscolor options" value="000000" autocomplete="off" spellcheck="false" minlength="6" maxlength="6" />
</label >
<div>Timing</div>
<label for="m_timing_light" >
<input type="text" id="m_timing_light" name="m_timing_light" class="jscolor options" value="ffffff" autocomplete="off" spellcheck="false" minlength="6" maxlength="6" />
</label >
<label for="m_timing_dark" >
<input type="text" id="m_timing_dark" name="m_timing_dark" class="jscolor options" value="000000" autocomplete="off" spellcheck="false" minlength="6" maxlength="6" />
</label >
<div>Format</div>
<label for="m_format_light" >
<input type="text" id="m_format_light" name="m_format_light" class="jscolor options" value="ffffff" autocomplete="off" spellcheck="false" minlength="6" maxlength="6" />
</label >
<label for="m_format_dark" >
<input type="text" id="m_format_dark" name="m_format_dark" class="jscolor options" value="000000" autocomplete="off" spellcheck="false" minlength="6" maxlength="6" />
</label >
<div>Version</div>
<label for="m_version_light" >
<input type="text" id="m_version_light" name="m_version_light" class="jscolor options" value="ffffff" autocomplete="off" spellcheck="false" minlength="6" maxlength="6" />
</label >
<label for="m_version_dark" >
<input type="text" id="m_version_dark" name="m_version_dark" class="jscolor options" value="000000" autocomplete="off" spellcheck="false" minlength="6" maxlength="6" />
</label >
<div>Data</div>
<label for="m_data_light" >
<input type="text" id="m_data_light" name="m_data_light" class="jscolor options" value="ffffff" autocomplete="off" spellcheck="false" minlength="6" maxlength="6" />
</label >
<label for="m_data_dark" >
<input type="text" id="m_data_dark" name="m_data_dark" class="jscolor options" value="000000" autocomplete="off" spellcheck="false" minlength="6" maxlength="6" />
</label >
<div>Dark Module</div>
<label for="m_darkmodule_light" >
<input disabled="disabled" type="text" id="m_darkmodule_light" class="options" value="" autocomplete="off" spellcheck="false" />
</label >
<label for="m_darkmodule_dark" >
<input type="text" id="m_darkmodule_dark" name="m_darkmodule_dark" class="jscolor options" value="000000" autocomplete="off" spellcheck="false" minlength="6" maxlength="6" />
</label >
<div>Separator</div>
<label for="m_separator_light" >
<input type="text" id="m_separator_light" name="m_separator_light" class="jscolor options" value="ffffff" autocomplete="off" spellcheck="false" minlength="6" maxlength="6" />
</label >
<label for="m_separator_dark" >
<input disabled="disabled" type="text" id="m_separator_dark" class="options" value="" autocomplete="off" spellcheck="false" />
</label >
<div>Quiet Zone</div>
<label for="m_quietzone_light" >
<input type="text" id="m_quietzone_light" name="m_quietzone_light" class="jscolor options" value="ffffff" autocomplete="off" spellcheck="false" minlength="6" maxlength="6" />
</label >
<label for="m_quietzone_dark" >
<input disabled="disabled" type="text" id="m_quietzone_dark" class="options" value="" autocomplete="off" spellcheck="false" />
</label >
<br />
<button type="submit" >generate</button >
</form >
<div id="qrcode-output" ></div >
<div><a href="https://play.google.com/store/apps/details?id=com.google.zxing.client.android" >ZXing Barcode Scanner</a ></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prototype/1.7.3/prototype.js" ></script >
<script src="https://cdnjs.cloudflare.com/ajax/libs/jscolor/2.0.4/jscolor.js" ></script >
<script >
((form, output, url) => {
$(form).observe('submit', ev => {
Event.stop(ev);
new Ajax.Request(url, {
method: 'post',
parameters: ev.target.serialize(true),
onUninitialized: $(output).update(),
onLoading: $(output).update('[portlandia_screaming.gif]'),
onFailure: response => $(output).update(response.responseJSON.error),
onSuccess: response => $(output).update(response.responseJSON.qrcode),
});
});
})('qrcode-settings', 'qrcode-output', './qrcode.php');
</script >
</body >
</html >

View File

@ -0,0 +1,97 @@
<?php
/**
* @filesource qrcode.php
* @created 18.11.2017
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodePublic;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
require_once '../vendor/autoload.php';
try{
$moduleValues = [
// finder
1536 => $_POST['m_finder_dark'],
6 => $_POST['m_finder_light'],
// alignment
2560 => $_POST['m_alignment_dark'],
10 => $_POST['m_alignment_light'],
// timing
3072 => $_POST['m_timing_dark'],
12 => $_POST['m_timing_light'],
// format
3584 => $_POST['m_format_dark'],
14 => $_POST['m_format_light'],
// version
4096 => $_POST['m_version_dark'],
16 => $_POST['m_version_light'],
// data
1024 => $_POST['m_data_dark'],
4 => $_POST['m_data_light'],
// darkmodule
512 => $_POST['m_darkmodule_dark'],
// separator
8 => $_POST['m_separator_light'],
// quietzone
18 => $_POST['m_quietzone_light'],
];
$moduleValues = array_map(function($v){
if(preg_match('/[a-f\d]{6}/i', $v) === 1){
return in_array($_POST['output_type'], ['png', 'jpg', 'gif'])
? array_map('hexdec', str_split($v, 2))
: '#'.$v ;
}
return null;
}, $moduleValues);
$ecc = in_array($_POST['ecc'], ['L', 'M', 'Q', 'H'], true) ? $_POST['ecc'] : 'L';
$qro = new QROptions;
$qro->version = (int)$_POST['version'];
$qro->eccLevel = constant('chillerlan\\QRCode\\QRCode::ECC_'.$ecc);
$qro->maskPattern = (int)$_POST['maskpattern'];
$qro->addQuietzone = isset($_POST['quietzone']);
$qro->quietzoneSize = (int)$_POST['quietzonesize'];
$qro->moduleValues = $moduleValues;
$qro->outputType = $_POST['output_type'];
$qro->scale = (int)$_POST['scale'];
$qro->imageTransparent = false;
$qrcode = (new QRCode($qro))->render($_POST['inputstring']);
if(in_array($_POST['output_type'], ['png', 'jpg', 'gif'])){
$qrcode = '<img src="'.$qrcode.'" />';
}
elseif($_POST['output_type'] === 'text'){
$qrcode = '<pre style="font-size: 75%; line-height: 1;">'.$qrcode.'</pre>';
}
elseif($_POST['output_type'] === 'json'){
$qrcode = '<pre style="font-size: 75%; overflow-x: auto;">'.$qrcode.'</pre>';
}
send_response(['qrcode' => $qrcode]);
}
// Pokémon exception handler
catch(\Exception $e){
header('HTTP/1.1 500 Internal Server Error');
send_response(['error' => $e->getMessage()]);
}
/**
* @param array $response
*/
function send_response(array $response){
header('Content-type: application/json;charset=utf-8;');
echo json_encode($response);
exit;
}

View File

@ -0,0 +1,65 @@
<?php
/**
* Class AlphaNum
*
* @filesource AlphaNum.php
* @created 25.11.2015
* @package chillerlan\QRCode\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\QRCode;
use function array_search, ord, sprintf;
/**
* Alphanumeric mode: 0 to 9, A to Z, space, $ % * + - . / :
*/
class AlphaNum extends QRDataAbstract{
/**
* @inheritdoc
*/
protected $datamode = QRCode::DATA_ALPHANUM;
/**
* @inheritdoc
*/
protected $lengthBits = [9, 11, 13];
/**
* @inheritdoc
*/
protected function write(string $data):void{
for($i = 0; $i + 1 < $this->strlen; $i += 2){
$this->bitBuffer->put($this->getCharCode($data[$i]) * 45 + $this->getCharCode($data[$i + 1]), 11);
}
if($i < $this->strlen){
$this->bitBuffer->put($this->getCharCode($data[$i]), 6);
}
}
/**
* @param string $chr
*
* @return int
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
protected function getCharCode(string $chr):int{
$i = array_search($chr, $this::ALPHANUM_CHAR_MAP);
if($i !== false){
return $i;
}
throw new QRCodeDataException(sprintf('illegal char: "%s" [%d]', $chr, ord($chr)));
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* Class Byte
*
* @filesource Byte.php
* @created 25.11.2015
* @package chillerlan\QRCode\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\QRCode;
use function ord;
/**
* Byte mode, ISO-8859-1 or UTF-8
*/
class Byte extends QRDataAbstract{
/**
* @inheritdoc
*/
protected $datamode = QRCode::DATA_BYTE;
/**
* @inheritdoc
*/
protected $lengthBits = [8, 16, 16];
/**
* @inheritdoc
*/
protected function write(string $data):void{
$i = 0;
while($i < $this->strlen){
$this->bitBuffer->put(ord($data[$i]), 8);
$i++;
}
}
}

View File

@ -0,0 +1,70 @@
<?php
/**
* Class Kanji
*
* @filesource Kanji.php
* @created 25.11.2015
* @package chillerlan\QRCode\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\QRCode;
use function mb_strlen, ord, sprintf, strlen;
/**
* Kanji mode: double-byte characters from the Shift JIS character set
*/
class Kanji extends QRDataAbstract{
/**
* @inheritdoc
*/
protected $datamode = QRCode::DATA_KANJI;
/**
* @inheritdoc
*/
protected $lengthBits = [8, 10, 12];
/**
* @inheritdoc
*/
protected function getLength(string $data):int{
return mb_strlen($data, 'SJIS');
}
/**
* @inheritdoc
*/
protected function write(string $data):void{
$len = strlen($data);
for($i = 0; $i + 1 < $len; $i += 2){
$c = ((0xff & ord($data[$i])) << 8) | (0xff & ord($data[$i + 1]));
if(0x8140 <= $c && $c <= 0x9FFC){
$c -= 0x8140;
}
elseif(0xE040 <= $c && $c <= 0xEBBF){
$c -= 0xC140;
}
else{
throw new QRCodeDataException(sprintf('illegal char at %d [%d]', $i + 1, $c));
}
$this->bitBuffer->put((($c >> 8) & 0xff) * 0xC0 + ($c & 0xff), 13);
}
if($i < $len){
throw new QRCodeDataException(sprintf('illegal char at %d', $i + 1));
}
}
}

View File

@ -0,0 +1,203 @@
<?php
/**
* Class MaskPatternTester
*
* @filesource MaskPatternTester.php
* @created 22.11.2017
* @package chillerlan\QRCode\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Data;
use function abs, call_user_func_array;
/**
* The sole purpose of this class is to receive a QRMatrix object and run the pattern tests on it.
*
* @link http://www.thonky.com/qr-code-tutorial/data-masking
*/
class MaskPatternTester{
/**
* @var \chillerlan\QRCode\Data\QRMatrix
*/
protected $matrix;
/**
* @var int
*/
protected $moduleCount;
/**
* Receives the matrix an sets the module count
*
* @see \chillerlan\QRCode\QROptions::$maskPattern
* @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern
* @see \chillerlan\QRCode\QRCode::getBestMaskPattern()
*
* @param \chillerlan\QRCode\Data\QRMatrix $matrix
*/
public function __construct(QRMatrix $matrix){
$this->matrix = $matrix;
$this->moduleCount = $this->matrix->size();
}
/**
* Returns the penalty for the given mask pattern
*
* @see \chillerlan\QRCode\QROptions::$maskPattern
* @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern
* @see \chillerlan\QRCode\QRCode::getBestMaskPattern()
*
* @return int
*/
public function testPattern():int{
$penalty = 0;
for($level = 1; $level <= 4; $level++){
$penalty += call_user_func_array([$this, 'testLevel'.$level], [$this->matrix->matrix(true)]);
}
return (int)$penalty;
}
/**
* Checks for each group of five or more same-colored modules in a row (or column)
*
* @return int
*/
protected function testLevel1(array $m):int{
$penalty = 0;
foreach($m as $y => $row){
foreach($row as $x => $val){
$count = 0;
for($ry = -1; $ry <= 1; $ry++){
if($y + $ry < 0 || $this->moduleCount <= $y + $ry){
continue;
}
for($rx = -1; $rx <= 1; $rx++){
if(($ry === 0 && $rx === 0) || (($x + $rx) < 0 || $this->moduleCount <= ($x + $rx))){
continue;
}
if($m[$y + $ry][$x + $rx] === $val){
$count++;
}
}
}
if($count > 5){
$penalty += (3 + $count - 5);
}
}
}
return $penalty;
}
/**
* Checks for each 2x2 area of same-colored modules in the matrix
*
* @return int
*/
protected function testLevel2(array $m):int{
$penalty = 0;
foreach($m as $y => $row){
if($y > ($this->moduleCount - 2)){
break;
}
foreach($row as $x => $val){
if($x > ($this->moduleCount - 2)){
break;
}
if(
$val === $m[$y][$x + 1]
&& $val === $m[$y + 1][$x]
&& $val === $m[$y + 1][$x + 1]
){
$penalty++;
}
}
}
return 3 * $penalty;
}
/**
* Checks if there are patterns that look similar to the finder patterns (1:1:3:1:1 ratio)
*
* @return int
*/
protected function testLevel3(array $m):int{
$penalties = 0;
foreach($m as $y => $row){
foreach($row as $x => $val){
if(
($x + 6) < $this->moduleCount
&& $val
&& !$m[$y][$x + 1]
&& $m[$y][$x + 2]
&& $m[$y][$x + 3]
&& $m[$y][$x + 4]
&& !$m[$y][$x + 5]
&& $m[$y][$x + 6]
){
$penalties++;
}
if(
($y + 6) < $this->moduleCount
&& $val
&& !$m[$y + 1][$x]
&& $m[$y + 2][$x]
&& $m[$y + 3][$x]
&& $m[$y + 4][$x]
&& !$m[$y + 5][$x]
&& $m[$y + 6][$x]
){
$penalties++;
}
}
}
return $penalties * 40;
}
/**
* Checks if more than half of the modules are dark or light, with a larger penalty for a larger difference
*
* @return float
*/
protected function testLevel4(array $m):float{
$count = 0;
foreach($m as $y => $row){
foreach($row as $x => $val){
if($val){
$count++;
}
}
}
return (abs(100 * $count / $this->moduleCount / $this->moduleCount - 50) / 5) * 10;
}
}

View File

@ -0,0 +1,82 @@
<?php
/**
* Class Number
*
* @filesource Number.php
* @created 26.11.2015
* @package QRCode
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\QRCode;
use function ord, sprintf, substr;
/**
* Numeric mode: decimal digits 0 through 9
*/
class Number extends QRDataAbstract{
/**
* @inheritdoc
*/
protected $datamode = QRCode::DATA_NUMBER;
/**
* @inheritdoc
*/
protected $lengthBits = [10, 12, 14];
/**
* @inheritdoc
*/
protected function write(string $data):void{
$i = 0;
while($i + 2 < $this->strlen){
$this->bitBuffer->put($this->parseInt(substr($data, $i, 3)), 10);
$i += 3;
}
if($i < $this->strlen){
if($this->strlen - $i === 1){
$this->bitBuffer->put($this->parseInt(substr($data, $i, $i + 1)), 4);
}
elseif($this->strlen - $i === 2){
$this->bitBuffer->put($this->parseInt(substr($data, $i, $i + 2)), 7);
}
}
}
/**
* @param string $string
*
* @return int
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
protected function parseInt(string $string):int{
$num = 0;
$len = strlen($string);
for($i = 0; $i < $len; $i++){
$c = ord($string[$i]);
if(!in_array($string[$i], $this::NUMBER_CHAR_MAP, true)){
throw new QRCodeDataException(sprintf('illegal char: "%s" [%d]', $string[$i], $c));
}
$c = $c - 48; // ord('0')
$num = $num * 10 + $c;
}
return $num;
}
}

View File

@ -0,0 +1,17 @@
<?php
/**
* Class QRCodeDataException
*
* @filesource QRCodeDataException.php
* @created 09.12.2015
* @package chillerlan\QRCode\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\QRCodeException;
class QRCodeDataException extends QRCodeException{}

View File

@ -0,0 +1,351 @@
<?php
/**
* Class QRDataAbstract
*
* @filesource QRDataAbstract.php
* @created 25.11.2015
* @package chillerlan\QRCode\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\{QRCode, QRCodeException};
use chillerlan\QRCode\Helpers\{BitBuffer, Polynomial};
use chillerlan\Settings\SettingsContainerInterface;
use function array_fill, array_merge, count, max, mb_convert_encoding, mb_detect_encoding, range, sprintf, strlen;
/**
* Processes the binary data and maps it on a matrix which is then being returned
*/
abstract class QRDataAbstract implements QRDataInterface{
/**
* the string byte count
*
* @var int
*/
protected $strlen;
/**
* the current data mode: Num, Alphanum, Kanji, Byte
*
* @var int
*/
protected $datamode;
/**
* mode length bits for the version breakpoints 1-9, 10-26 and 27-40
*
* @var array
*/
protected $lengthBits = [0, 0, 0];
/**
* current QR Code version
*
* @var int
*/
protected $version;
/**
* the raw data that's being passed to QRMatrix::mapData()
*
* @var array
*/
protected $matrixdata;
/**
* ECC temp data
*
* @var array
*/
protected $ecdata;
/**
* ECC temp data
*
* @var array
*/
protected $dcdata;
/**
* @var \chillerlan\QRCode\QROptions
*/
protected $options;
/**
* @var \chillerlan\QRCode\Helpers\BitBuffer
*/
protected $bitBuffer;
/**
* QRDataInterface constructor.
*
* @param \chillerlan\Settings\SettingsContainerInterface $options
* @param string|null $data
*/
public function __construct(SettingsContainerInterface $options, string $data = null){
$this->options = $options;
if($data !== null){
$this->setData($data);
}
}
/**
* @inheritDoc
*/
public function setData(string $data):QRDataInterface{
if($this->datamode === QRCode::DATA_KANJI){
$data = mb_convert_encoding($data, 'SJIS', mb_detect_encoding($data));
}
$this->strlen = $this->getLength($data);
$this->version = $this->options->version === QRCode::VERSION_AUTO
? $this->getMinimumVersion()
: $this->options->version;
$this->matrixdata = $this
->writeBitBuffer($data)
->maskECC()
;
return $this;
}
/**
* @inheritDoc
*/
public function initMatrix(int $maskPattern, bool $test = null):QRMatrix{
return (new QRMatrix($this->version, $this->options->eccLevel))
->setFinderPattern()
->setSeparators()
->setAlignmentPattern()
->setTimingPattern()
->setVersionNumber($test)
->setFormatInfo($maskPattern, $test)
->setDarkModule()
->mapData($this->matrixdata, $maskPattern)
;
}
/**
* returns the length bits for the version breakpoints 1-9, 10-26 and 27-40
*
* @return int
* @throws \chillerlan\QRCode\Data\QRCodeDataException
* @codeCoverageIgnore
*/
protected function getLengthBits():int{
foreach([9, 26, 40] as $key => $breakpoint){
if($this->version <= $breakpoint){
return $this->lengthBits[$key];
}
}
throw new QRCodeDataException(sprintf('invalid version number: %d', $this->version));
}
/**
* returns the byte count of the $data string
*
* @param string $data
*
* @return int
*/
protected function getLength(string $data):int{
return strlen($data);
}
/**
* returns the minimum version number for the given string
*
* @return int
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
protected function getMinimumVersion():int{
$maxlength = 0;
// guess the version number within the given range
foreach(range($this->options->versionMin, $this->options->versionMax) as $version){
$maxlength = $this::MAX_LENGTH[$version][QRCode::DATA_MODES[$this->datamode]][QRCode::ECC_MODES[$this->options->eccLevel]];
if($this->strlen <= $maxlength){
return $version;
}
}
throw new QRCodeDataException(sprintf('data exceeds %d characters', $maxlength));
}
/**
* writes the actual data string to the BitBuffer
*
* @see \chillerlan\QRCode\Data\QRDataAbstract::writeBitBuffer()
*
* @param string $data
*
* @return void
*/
abstract protected function write(string $data):void;
/**
* creates a BitBuffer and writes the string data to it
*
* @param string $data
*
* @return \chillerlan\QRCode\Data\QRDataAbstract
* @throws \chillerlan\QRCode\QRCodeException
*/
protected function writeBitBuffer(string $data):QRDataInterface{
$this->bitBuffer = new BitBuffer;
$MAX_BITS = $this::MAX_BITS[$this->version][QRCode::ECC_MODES[$this->options->eccLevel]];
$this->bitBuffer
->clear()
->put($this->datamode, 4)
->put($this->strlen, $this->getLengthBits())
;
$this->write($data);
// there was an error writing the BitBuffer data, which is... unlikely.
if($this->bitBuffer->length > $MAX_BITS){
throw new QRCodeException(sprintf('code length overflow. (%d > %d bit)', $this->bitBuffer->length, $MAX_BITS)); // @codeCoverageIgnore
}
// end code.
if($this->bitBuffer->length + 4 <= $MAX_BITS){
$this->bitBuffer->put(0, 4);
}
// padding
while($this->bitBuffer->length % 8 !== 0){
$this->bitBuffer->putBit(false);
}
// padding
while(true){
if($this->bitBuffer->length >= $MAX_BITS){
break;
}
$this->bitBuffer->put(0xEC, 8);
if($this->bitBuffer->length >= $MAX_BITS){
break;
}
$this->bitBuffer->put(0x11, 8);
}
return $this;
}
/**
* ECC masking
*
* @link http://www.thonky.com/qr-code-tutorial/error-correction-coding
*
* @return array
*/
protected function maskECC():array{
[$l1, $l2, $b1, $b2] = $this::RSBLOCKS[$this->version][QRCode::ECC_MODES[$this->options->eccLevel]];
$rsBlocks = array_fill(0, $l1, [$b1, $b2]);
$rsCount = $l1 + $l2;
$this->ecdata = array_fill(0, $rsCount, null);
$this->dcdata = $this->ecdata;
if($l2 > 0){
$rsBlocks = array_merge($rsBlocks, array_fill(0, $l2, [$b1 + 1, $b2 + 1]));
}
$totalCodeCount = 0;
$maxDcCount = 0;
$maxEcCount = 0;
$offset = 0;
foreach($rsBlocks as $key => $block){
[$rsBlockTotal, $dcCount] = $block;
$ecCount = $rsBlockTotal - $dcCount;
$maxDcCount = max($maxDcCount, $dcCount);
$maxEcCount = max($maxEcCount, $ecCount);
$this->dcdata[$key] = array_fill(0, $dcCount, null);
foreach($this->dcdata[$key] as $a => $_z){
$this->dcdata[$key][$a] = 0xff & $this->bitBuffer->buffer[$a + $offset];
}
[$num, $add] = $this->poly($key, $ecCount);
foreach($this->ecdata[$key] as $c => $_z){
$modIndex = $c + $add;
$this->ecdata[$key][$c] = $modIndex >= 0 ? $num[$modIndex] : 0;
}
$offset += $dcCount;
$totalCodeCount += $rsBlockTotal;
}
$data = array_fill(0, $totalCodeCount, null);
$index = 0;
$mask = function($arr, $count) use (&$data, &$index, $rsCount){
for($x = 0; $x < $count; $x++){
for($y = 0; $y < $rsCount; $y++){
if($x < count($arr[$y])){
$data[$index] = $arr[$y][$x];
$index++;
}
}
}
};
$mask($this->dcdata, $maxDcCount);
$mask($this->ecdata, $maxEcCount);
return $data;
}
/**
* @param int $key
* @param int $count
*
* @return int[]
*/
protected function poly(int $key, int $count):array{
$rsPoly = new Polynomial;
$modPoly = new Polynomial;
for($i = 0; $i < $count; $i++){
$modPoly->setNum([1, $modPoly->gexp($i)]);
$rsPoly->multiply($modPoly->getNum());
}
$rsPolyCount = count($rsPoly->getNum());
$modPoly
->setNum($this->dcdata[$key], $rsPolyCount - 1)
->mod($rsPoly->getNum())
;
$this->ecdata[$key] = array_fill(0, $rsPolyCount - 1, null);
$num = $modPoly->getNum();
return [
$num,
count($num) - count($this->ecdata[$key]),
];
}
}

View File

@ -0,0 +1,187 @@
<?php
/**
* Interface QRDataInterface
*
* @filesource QRDataInterface.php
* @created 01.12.2015
* @package chillerlan\QRCode\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Data;
/**
*
*/
interface QRDataInterface{
const NUMBER_CHAR_MAP = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
const ALPHANUM_CHAR_MAP = [
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
'W', 'X', 'Y', 'Z', ' ', '$', '%', '*',
'+', '-', '.', '/', ':',
];
/**
* @link http://www.qrcode.com/en/about/version.html
*/
const MAX_LENGTH =[
// v => [NUMERIC => [L, M, Q, H ], ALPHANUM => [L, M, Q, H], BINARY => [L, M, Q, H ], KANJI => [L, M, Q, H ]] // modules
1 => [[ 41, 34, 27, 17], [ 25, 20, 16, 10], [ 17, 14, 11, 7], [ 10, 8, 7, 4]], // 21
2 => [[ 77, 63, 48, 34], [ 47, 38, 29, 20], [ 32, 26, 20, 14], [ 20, 16, 12, 8]], // 25
3 => [[ 127, 101, 77, 58], [ 77, 61, 47, 35], [ 53, 42, 32, 24], [ 32, 26, 20, 15]], // 29
4 => [[ 187, 149, 111, 82], [ 114, 90, 67, 50], [ 78, 62, 46, 34], [ 48, 38, 28, 21]], // 33
5 => [[ 255, 202, 144, 106], [ 154, 122, 87, 64], [ 106, 84, 60, 44], [ 65, 52, 37, 27]], // 37
6 => [[ 322, 255, 178, 139], [ 195, 154, 108, 84], [ 134, 106, 74, 58], [ 82, 65, 45, 36]], // 41
7 => [[ 370, 293, 207, 154], [ 224, 178, 125, 93], [ 154, 122, 86, 64], [ 95, 75, 53, 39]], // 45
8 => [[ 461, 365, 259, 202], [ 279, 221, 157, 122], [ 192, 152, 108, 84], [ 118, 93, 66, 52]], // 49
9 => [[ 552, 432, 312, 235], [ 335, 262, 189, 143], [ 230, 180, 130, 98], [ 141, 111, 80, 60]], // 53
10 => [[ 652, 513, 364, 288], [ 395, 311, 221, 174], [ 271, 213, 151, 119], [ 167, 131, 93, 74]], // 57
11 => [[ 772, 604, 427, 331], [ 468, 366, 259, 200], [ 321, 251, 177, 137], [ 198, 155, 109, 85]], // 61
12 => [[ 883, 691, 489, 374], [ 535, 419, 296, 227], [ 367, 287, 203, 155], [ 226, 177, 125, 96]], // 65
13 => [[1022, 796, 580, 427], [ 619, 483, 352, 259], [ 425, 331, 241, 177], [ 262, 204, 149, 109]], // 69 NICE!
14 => [[1101, 871, 621, 468], [ 667, 528, 376, 283], [ 458, 362, 258, 194], [ 282, 223, 159, 120]], // 73
15 => [[1250, 991, 703, 530], [ 758, 600, 426, 321], [ 520, 412, 292, 220], [ 320, 254, 180, 136]], // 77
16 => [[1408, 1082, 775, 602], [ 854, 656, 470, 365], [ 586, 450, 322, 250], [ 361, 277, 198, 154]], // 81
17 => [[1548, 1212, 876, 674], [ 938, 734, 531, 408], [ 644, 504, 364, 280], [ 397, 310, 224, 173]], // 85
18 => [[1725, 1346, 948, 746], [1046, 816, 574, 452], [ 718, 560, 394, 310], [ 442, 345, 243, 191]], // 89
19 => [[1903, 1500, 1063, 813], [1153, 909, 644, 493], [ 792, 624, 442, 338], [ 488, 384, 272, 208]], // 93
20 => [[2061, 1600, 1159, 919], [1249, 970, 702, 557], [ 858, 666, 482, 382], [ 528, 410, 297, 235]], // 97
21 => [[2232, 1708, 1224, 969], [1352, 1035, 742, 587], [ 929, 711, 509, 403], [ 572, 438, 314, 248]], // 101
22 => [[2409, 1872, 1358, 1056], [1460, 1134, 823, 640], [1003, 779, 565, 439], [ 618, 480, 348, 270]], // 105
23 => [[2620, 2059, 1468, 1108], [1588, 1248, 890, 672], [1091, 857, 611, 461], [ 672, 528, 376, 284]], // 109
24 => [[2812, 2188, 1588, 1228], [1704, 1326, 963, 744], [1171, 911, 661, 511], [ 721, 561, 407, 315]], // 113
25 => [[3057, 2395, 1718, 1286], [1853, 1451, 1041, 779], [1273, 997, 715, 535], [ 784, 614, 440, 330]], // 117
26 => [[3283, 2544, 1804, 1425], [1990, 1542, 1094, 864], [1367, 1059, 751, 593], [ 842, 652, 462, 365]], // 121
27 => [[3517, 2701, 1933, 1501], [2132, 1637, 1172, 910], [1465, 1125, 805, 625], [ 902, 692, 496, 385]], // 125
28 => [[3669, 2857, 2085, 1581], [2223, 1732, 1263, 958], [1528, 1190, 868, 658], [ 940, 732, 534, 405]], // 129
29 => [[3909, 3035, 2181, 1677], [2369, 1839, 1322, 1016], [1628, 1264, 908, 698], [1002, 778, 559, 430]], // 133
30 => [[4158, 3289, 2358, 1782], [2520, 1994, 1429, 1080], [1732, 1370, 982, 742], [1066, 843, 604, 457]], // 137
31 => [[4417, 3486, 2473, 1897], [2677, 2113, 1499, 1150], [1840, 1452, 1030, 790], [1132, 894, 634, 486]], // 141
32 => [[4686, 3693, 2670, 2022], [2840, 2238, 1618, 1226], [1952, 1538, 1112, 842], [1201, 947, 684, 518]], // 145
33 => [[4965, 3909, 2805, 2157], [3009, 2369, 1700, 1307], [2068, 1628, 1168, 898], [1273, 1002, 719, 553]], // 149
34 => [[5253, 4134, 2949, 2301], [3183, 2506, 1787, 1394], [2188, 1722, 1228, 958], [1347, 1060, 756, 590]], // 153
35 => [[5529, 4343, 3081, 2361], [3351, 2632, 1867, 1431], [2303, 1809, 1283, 983], [1417, 1113, 790, 605]], // 157
36 => [[5836, 4588, 3244, 2524], [3537, 2780, 1966, 1530], [2431, 1911, 1351, 1051], [1496, 1176, 832, 647]], // 161
37 => [[6153, 4775, 3417, 2625], [3729, 2894, 2071, 1591], [2563, 1989, 1423, 1093], [1577, 1224, 876, 673]], // 165
38 => [[6479, 5039, 3599, 2735], [3927, 3054, 2181, 1658], [2699, 2099, 1499, 1139], [1661, 1292, 923, 701]], // 169
39 => [[6743, 5313, 3791, 2927], [4087, 3220, 2298, 1774], [2809, 2213, 1579, 1219], [1729, 1362, 972, 750]], // 173
40 => [[7089, 5596, 3993, 3057], [4296, 3391, 2420, 1852], [2953, 2331, 1663, 1273], [1817, 1435, 1024, 784]], // 177
];
const MAX_BITS = [
// version => [L, M, Q, H ]
1 => [ 152, 128, 104, 72],
2 => [ 272, 224, 176, 128],
3 => [ 440, 352, 272, 208],
4 => [ 640, 512, 384, 288],
5 => [ 864, 688, 496, 368],
6 => [ 1088, 864, 608, 480],
7 => [ 1248, 992, 704, 528],
8 => [ 1552, 1232, 880, 688],
9 => [ 1856, 1456, 1056, 800],
10 => [ 2192, 1728, 1232, 976],
11 => [ 2592, 2032, 1440, 1120],
12 => [ 2960, 2320, 1648, 1264],
13 => [ 3424, 2672, 1952, 1440],
14 => [ 3688, 2920, 2088, 1576],
15 => [ 4184, 3320, 2360, 1784],
16 => [ 4712, 3624, 2600, 2024],
17 => [ 5176, 4056, 2936, 2264],
18 => [ 5768, 4504, 3176, 2504],
19 => [ 6360, 5016, 3560, 2728],
20 => [ 6888, 5352, 3880, 3080],
21 => [ 7456, 5712, 4096, 3248],
22 => [ 8048, 6256, 4544, 3536],
23 => [ 8752, 6880, 4912, 3712],
24 => [ 9392, 7312, 5312, 4112],
25 => [10208, 8000, 5744, 4304],
26 => [10960, 8496, 6032, 4768],
27 => [11744, 9024, 6464, 5024],
28 => [12248, 9544, 6968, 5288],
29 => [13048, 10136, 7288, 5608],
30 => [13880, 10984, 7880, 5960],
31 => [14744, 11640, 8264, 6344],
32 => [15640, 12328, 8920, 6760],
33 => [16568, 13048, 9368, 7208],
34 => [17528, 13800, 9848, 7688],
35 => [18448, 14496, 10288, 7888],
36 => [19472, 15312, 10832, 8432],
37 => [20528, 15936, 11408, 8768],
38 => [21616, 16816, 12016, 9136],
39 => [22496, 17728, 12656, 9776],
40 => [23648, 18672, 13328, 10208],
];
/**
* @link http://www.thonky.com/qr-code-tutorial/error-correction-table
*/
const RSBLOCKS = [
1 => [[ 1, 0, 26, 19], [ 1, 0, 26, 16], [ 1, 0, 26, 13], [ 1, 0, 26, 9]],
2 => [[ 1, 0, 44, 34], [ 1, 0, 44, 28], [ 1, 0, 44, 22], [ 1, 0, 44, 16]],
3 => [[ 1, 0, 70, 55], [ 1, 0, 70, 44], [ 2, 0, 35, 17], [ 2, 0, 35, 13]],
4 => [[ 1, 0, 100, 80], [ 2, 0, 50, 32], [ 2, 0, 50, 24], [ 4, 0, 25, 9]],
5 => [[ 1, 0, 134, 108], [ 2, 0, 67, 43], [ 2, 2, 33, 15], [ 2, 2, 33, 11]],
6 => [[ 2, 0, 86, 68], [ 4, 0, 43, 27], [ 4, 0, 43, 19], [ 4, 0, 43, 15]],
7 => [[ 2, 0, 98, 78], [ 4, 0, 49, 31], [ 2, 4, 32, 14], [ 4, 1, 39, 13]],
8 => [[ 2, 0, 121, 97], [ 2, 2, 60, 38], [ 4, 2, 40, 18], [ 4, 2, 40, 14]],
9 => [[ 2, 0, 146, 116], [ 3, 2, 58, 36], [ 4, 4, 36, 16], [ 4, 4, 36, 12]],
10 => [[ 2, 2, 86, 68], [ 4, 1, 69, 43], [ 6, 2, 43, 19], [ 6, 2, 43, 15]],
11 => [[ 4, 0, 101, 81], [ 1, 4, 80, 50], [ 4, 4, 50, 22], [ 3, 8, 36, 12]],
12 => [[ 2, 2, 116, 92], [ 6, 2, 58, 36], [ 4, 6, 46, 20], [ 7, 4, 42, 14]],
13 => [[ 4, 0, 133, 107], [ 8, 1, 59, 37], [ 8, 4, 44, 20], [12, 4, 33, 11]],
14 => [[ 3, 1, 145, 115], [ 4, 5, 64, 40], [11, 5, 36, 16], [11, 5, 36, 12]],
15 => [[ 5, 1, 109, 87], [ 5, 5, 65, 41], [ 5, 7, 54, 24], [11, 7, 36, 12]],
16 => [[ 5, 1, 122, 98], [ 7, 3, 73, 45], [15, 2, 43, 19], [ 3, 13, 45, 15]],
17 => [[ 1, 5, 135, 107], [10, 1, 74, 46], [ 1, 15, 50, 22], [ 2, 17, 42, 14]],
18 => [[ 5, 1, 150, 120], [ 9, 4, 69, 43], [17, 1, 50, 22], [ 2, 19, 42, 14]],
19 => [[ 3, 4, 141, 113], [ 3, 11, 70, 44], [17, 4, 47, 21], [ 9, 16, 39, 13]],
20 => [[ 3, 5, 135, 107], [ 3, 13, 67, 41], [15, 5, 54, 24], [15, 10, 43, 15]],
21 => [[ 4, 4, 144, 116], [17, 0, 68, 42], [17, 6, 50, 22], [19, 6, 46, 16]],
22 => [[ 2, 7, 139, 111], [17, 0, 74, 46], [ 7, 16, 54, 24], [34, 0, 37, 13]],
23 => [[ 4, 5, 151, 121], [ 4, 14, 75, 47], [11, 14, 54, 24], [16, 14, 45, 15]],
24 => [[ 6, 4, 147, 117], [ 6, 14, 73, 45], [11, 16, 54, 24], [30, 2, 46, 16]],
25 => [[ 8, 4, 132, 106], [ 8, 13, 75, 47], [ 7, 22, 54, 24], [22, 13, 45, 15]],
26 => [[10, 2, 142, 114], [19, 4, 74, 46], [28, 6, 50, 22], [33, 4, 46, 16]],
27 => [[ 8, 4, 152, 122], [22, 3, 73, 45], [ 8, 26, 53, 23], [12, 28, 45, 15]],
28 => [[ 3, 10, 147, 117], [ 3, 23, 73, 45], [ 4, 31, 54, 24], [11, 31, 45, 15]],
29 => [[ 7, 7, 146, 116], [21, 7, 73, 45], [ 1, 37, 53, 23], [19, 26, 45, 15]],
30 => [[ 5, 10, 145, 115], [19, 10, 75, 47], [15, 25, 54, 24], [23, 25, 45, 15]],
31 => [[13, 3, 145, 115], [ 2, 29, 74, 46], [42, 1, 54, 24], [23, 28, 45, 15]],
32 => [[17, 0, 145, 115], [10, 23, 74, 46], [10, 35, 54, 24], [19, 35, 45, 15]],
33 => [[17, 1, 145, 115], [14, 21, 74, 46], [29, 19, 54, 24], [11, 46, 45, 15]],
34 => [[13, 6, 145, 115], [14, 23, 74, 46], [44, 7, 54, 24], [59, 1, 46, 16]],
35 => [[12, 7, 151, 121], [12, 26, 75, 47], [39, 14, 54, 24], [22, 41, 45, 15]],
36 => [[ 6, 14, 151, 121], [ 6, 34, 75, 47], [46, 10, 54, 24], [ 2, 64, 45, 15]],
37 => [[17, 4, 152, 122], [29, 14, 74, 46], [49, 10, 54, 24], [24, 46, 45, 15]],
38 => [[ 4, 18, 152, 122], [13, 32, 74, 46], [48, 14, 54, 24], [42, 32, 45, 15]],
39 => [[20, 4, 147, 117], [40, 7, 75, 47], [43, 22, 54, 24], [10, 67, 45, 15]],
40 => [[19, 6, 148, 118], [18, 31, 75, 47], [34, 34, 54, 24], [20, 61, 45, 15]],
];
/**
* Sets the data string (internally called by the constructor)
*
* @param string $data
*
* @return \chillerlan\QRCode\Data\QRDataInterface
*/
public function setData(string $data):QRDataInterface;
/**
* returns a fresh matrix object with the data written for the given $maskPattern
*
* @param int $maskPattern
* @param bool|null $test
*
* @return \chillerlan\QRCode\Data\QRMatrix
*/
public function initMatrix(int $maskPattern, bool $test = null):QRMatrix;
}

View File

@ -0,0 +1,733 @@
<?php
/**
* Class QRMatrix
*
* @filesource QRMatrix.php
* @created 15.11.2017
* @package chillerlan\QRCode\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\QRCode;
use Closure;
use function array_fill, array_key_exists, array_push, array_unshift, count, floor, in_array, max, min, range;
/**
* @link http://www.thonky.com/qr-code-tutorial/format-version-information
*/
class QRMatrix{
public const M_NULL = 0x00;
public const M_DARKMODULE = 0x02;
public const M_DATA = 0x04;
public const M_FINDER = 0x06;
public const M_SEPARATOR = 0x08;
public const M_ALIGNMENT = 0x0a;
public const M_TIMING = 0x0c;
public const M_FORMAT = 0x0e;
public const M_VERSION = 0x10;
public const M_QUIETZONE = 0x12;
public const M_LOGO = 0x14;
public const M_FINDER_DOT = 0x16;
public const M_TEST = 0xff;
/**
* @link http://www.thonky.com/qr-code-tutorial/alignment-pattern-locations
*
* version -> pattern
*/
protected const alignmentPattern = [
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],
];
/**
* @link http://www.thonky.com/qr-code-tutorial/format-version-tables
*
* no version pattern for QR Codes < 7
*/
protected const versionPattern = [
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,
];
// ECC level -> mask pattern
protected const formatPattern = [
[ // 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,
],
];
/**
* @var int
*/
protected $version;
/**
* @var int
*/
protected $eclevel;
/**
* @var int
*/
protected $maskPattern = QRCode::MASK_PATTERN_AUTO;
/**
* @var int
*/
protected $moduleCount;
/**
* @var mixed[]
*/
protected $matrix;
/**
* QRMatrix constructor.
*
* @param int $version
* @param int $eclevel
*
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
public function __construct(int $version, int $eclevel){
if(!in_array($version, range(1, 40), true)){
throw new QRCodeDataException('invalid QR Code version');
}
if(!array_key_exists($eclevel, QRCode::ECC_MODES)){
throw new QRCodeDataException('invalid ecc level');
}
$this->version = $version;
$this->eclevel = $eclevel;
$this->moduleCount = $this->version * 4 + 17;
$this->matrix = array_fill(0, $this->moduleCount, array_fill(0, $this->moduleCount, $this::M_NULL));
}
/**
* Returns the data matrix, returns a pure boolean representation if $boolean is set to true
*
* @return int[][]|bool[][]
*/
public function matrix(bool $boolean = false):array{
if(!$boolean){
return $this->matrix;
}
$matrix = [];
foreach($this->matrix as $y => $row){
$matrix[$y] = [];
foreach($row as $x => $val){
$matrix[$y][$x] = ($val >> 8) > 0;
}
}
return $matrix;
}
/**
* @return int
*/
public function version():int{
return $this->version;
}
/**
* @return int
*/
public function eccLevel():int{
return $this->eclevel;
}
/**
* @return int
*/
public function maskPattern():int{
return $this->maskPattern;
}
/**
* Returns the absoulute size of the matrix, including quiet zone (after setting it).
*
* size = version * 4 + 17 [ + 2 * quietzone size]
*
* @return int
*/
public function size():int{
return $this->moduleCount;
}
/**
* Returns the value of the module at position [$x, $y]
*
* @param int $x
* @param int $y
*
* @return int
*/
public function get(int $x, int $y):int{
return $this->matrix[$y][$x];
}
/**
* Sets the $M_TYPE value for the module at position [$x, $y]
*
* true => $M_TYPE << 8
* false => $M_TYPE
*
* @param int $x
* @param int $y
* @param int $M_TYPE
* @param bool $value
*
* @return \chillerlan\QRCode\Data\QRMatrix
*/
public function set(int $x, int $y, bool $value, int $M_TYPE):QRMatrix{
$this->matrix[$y][$x] = $M_TYPE << ($value ? 8 : 0);
return $this;
}
/**
* Checks whether a module is true (dark) or false (light)
*
* true => $value >> 8 === $M_TYPE
* $value >> 8 > 0
*
* false => $value === $M_TYPE
* $value >> 8 === 0
*
* @param int $x
* @param int $y
*
* @return bool
*/
public function check(int $x, int $y):bool{
return $this->matrix[$y][$x] >> 8 > 0;
}
/**
* Sets the "dark module", that is always on the same position 1x1px away from the bottom left finder
*
* @return \chillerlan\QRCode\Data\QRMatrix
*/
public function setDarkModule():QRMatrix{
$this->set(8, 4 * $this->version + 9, true, $this::M_DARKMODULE);
return $this;
}
/**
* Draws the 7x7 finder patterns in the corners top left/right and bottom left
*
* @return \chillerlan\QRCode\Data\QRMatrix
*/
public function setFinderPattern():QRMatrix{
$pos = [
[0, 0], // top left
[$this->moduleCount - 7, 0], // bottom left
[0, $this->moduleCount - 7], // top right
];
foreach($pos as $c){
for($y = 0; $y < 7; $y++){
for($x = 0; $x < 7; $x++){
// outer (dark) 7*7 square
if($x === 0 || $x === 6 || $y === 0 || $y === 6){
$this->set($c[0] + $y, $c[1] + $x, true, $this::M_FINDER);
}
// inner (light) 5*5 square
elseif($x === 1 || $x === 5 || $y === 1 || $y === 5){
$this->set($c[0] + $y, $c[1] + $x, false, $this::M_FINDER);
}
// 3*3 dot
else{
$this->set($c[0] + $y, $c[1] + $x, true, $this::M_FINDER_DOT);
}
}
}
}
return $this;
}
/**
* Draws the separator lines around the finder patterns
*
* @return \chillerlan\QRCode\Data\QRMatrix
*/
public function setSeparators():QRMatrix{
$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
*
* @return \chillerlan\QRCode\Data\QRMatrix
*/
public function setAlignmentPattern():QRMatrix{
foreach($this::alignmentPattern[$this->version] as $y){
foreach($this::alignmentPattern[$this->version] as $x){
// skip existing patterns
if($this->matrix[$y][$x] !== $this::M_NULL){
continue;
}
for($ry = -2; $ry <= 2; $ry++){
for($rx = -2; $rx <= 2; $rx++){
$v = ($ry === 0 && $rx === 0) || $ry === 2 || $ry === -2 || $rx === 2 || $rx === -2;
$this->set($x + $rx, $y + $ry, $v, $this::M_ALIGNMENT);
}
}
}
}
return $this;
}
/**
* Draws the timing pattern (h/v checkered line between the finder patterns)
*
* @return \chillerlan\QRCode\Data\QRMatrix
*/
public function setTimingPattern():QRMatrix{
foreach(range(8, $this->moduleCount - 8 - 1) as $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
*
* @param bool|null $test
*
* @return \chillerlan\QRCode\Data\QRMatrix
*/
public function setVersionNumber(bool $test = null):QRMatrix{
$bits = $this::versionPattern[$this->version] ?? false;
if($bits !== false){
for($i = 0; $i < 18; $i++){
$a = (int)floor($i / 3);
$b = $i % 3 + $this->moduleCount - 8 - 3;
$v = !$test && (($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
*
* @param int $maskPattern
* @param bool|null $test
*
* @return \chillerlan\QRCode\Data\QRMatrix
*/
public function setFormatInfo(int $maskPattern, bool $test = null):QRMatrix{
$bits = $this::formatPattern[QRCode::ECC_MODES[$this->eclevel]][$maskPattern] ?? 0;
for($i = 0; $i < 15; $i++){
$v = !$test && (($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);
}
}
$this->set(8, $this->moduleCount - 8, !$test, $this::M_FORMAT);
return $this;
}
/**
* Draws the "quiet zone" of $size around the matrix
*
* @param int|null $size
*
* @return \chillerlan\QRCode\Data\QRMatrix
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
public function setQuietZone(int $size = null):QRMatrix{
if($this->matrix[$this->moduleCount - 1][$this->moduleCount - 1] === $this::M_NULL){
throw new QRCodeDataException('use only after writing data');
}
$size = $size !== null
? max(0, min($size, floor($this->moduleCount / 2)))
: 4;
for($y = 0; $y < $this->moduleCount; $y++){
for($i = 0; $i < $size; $i++){
array_unshift($this->matrix[$y], $this::M_QUIETZONE);
array_push($this->matrix[$y], $this::M_QUIETZONE);
}
}
$this->moduleCount += ($size * 2);
$r = array_fill(0, $this->moduleCount, $this::M_QUIETZONE);
for($i = 0; $i < $size; $i++){
array_unshift($this->matrix, $r);
array_push($this->matrix, $r);
}
return $this;
}
/**
* Clears a space of $width * $height in order to add a logo or text.
*
* Additionally, the logo space can be positioned within the QR Code - respecting the main functional patterns -
* 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.
*
* 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
*
* @param int $width
* @param int $height
* @param int|null $startX
* @param int|null $startY
*
* @return \chillerlan\QRCode\Data\QRMatrix
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
public function setLogoSpace(int $width, int $height, int $startX = null, int $startY = null):QRMatrix{
// for logos we operate in ECC H (30%) only
if($this->eclevel !== 0b10){
throw new QRCodeDataException('ECC level "H" required to add logo space');
}
// 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++;
}
// $this->moduleCount includes the quiet zone (if created), we need the QR size here
$length = $this->version * 4 + 17;
// throw if the logo space exceeds the maximum error correction capacity
if($width * $height > floor($length * $length * 0.2)){
throw new QRCodeDataException('logo space exceeds the maximum error correction capacity');
}
// quiet zone size
$qz = ($this->moduleCount - $length) / 2;
// skip quiet zone and the first 9 rows/columns (finder-, mode-, version- and timing patterns)
$start = $qz + 9;
// skip quiet zone
$end = $this->moduleCount - $qz;
// determine start coordinates
$startX = ($startX !== null ? $startX : ($length - $width) / 2) + $qz;
$startY = ($startY !== null ? $startY : ($length - $height) / 2) + $qz;
// clear the space
foreach($this->matrix as $y => $row){
foreach($row as $x => $val){
// out of bounds, skip
if($x < $start || $y < $start ||$x >= $end || $y >= $end){
continue;
}
// a match
if($x >= $startX && $x < ($startX + $width) && $y >= $startY && $y < ($startY + $height)){
$this->set($x, $y, false, $this::M_LOGO);
}
}
}
return $this;
}
/**
* Maps the binary $data array from QRDataInterface::maskECC() on the matrix, using $maskPattern
*
* @see \chillerlan\QRCode\Data\QRDataAbstract::maskECC()
*
* @param int[] $data
* @param int $maskPattern
*
* @return \chillerlan\QRCode\Data\QRMatrix
*/
public function mapData(array $data, int $maskPattern):QRMatrix{
$this->maskPattern = $maskPattern;
$byteCount = count($data);
$size = $this->moduleCount - 1;
$mask = $this->getMask($this->maskPattern);
for($i = $size, $y = $size, $inc = -1, $byteIndex = 0, $bitIndex = 7; $i > 0; $i -= 2){
if($i === 6){
$i--;
}
while(true){
for($c = 0; $c < 2; $c++){
$x = $i - $c;
if($this->matrix[$y][$x] === $this::M_NULL){
$v = false;
if($byteIndex < $byteCount){
$v = (($data[$byteIndex] >> $bitIndex) & 1) === 1;
}
if($mask($x, $y) === 0){
$v = !$v;
}
$this->matrix[$y][$x] = $this::M_DATA << ($v ? 8 : 0);
$bitIndex--;
if($bitIndex === -1){
$byteIndex++;
$bitIndex = 7;
}
}
}
$y += $inc;
if($y < 0 || $this->moduleCount <= $y){
$y -= $inc;
$inc = -$inc;
break;
}
}
}
return $this;
}
/**
* ISO/IEC 18004:2000 Section 8.8.1
*
* Note that some versions of the QR code standard have had errors in the section about mask patterns.
* The information below has been corrected. (https://www.thonky.com/qr-code-tutorial/mask-patterns)
*
* @see \chillerlan\QRCode\QRMatrix::mapData()
*
* @internal
*
* @param int $maskPattern
*
* @return \Closure
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
protected function getMask(int $maskPattern):Closure{
if((0b111 & $maskPattern) !== $maskPattern){
throw new QRCodeDataException('invalid mask pattern'); // @codeCoverageIgnore
}
return [
0b000 => function($x, $y):int{ return ($x + $y) % 2; },
0b001 => function($x, $y):int{ return $y % 2; },
0b010 => function($x, $y):int{ return $x % 3; },
0b011 => function($x, $y):int{ return ($x + $y) % 3; },
0b100 => function($x, $y):int{ return ((int)($y / 2) + (int)($x / 3)) % 2; },
0b101 => function($x, $y):int{ return (($x * $y) % 2) + (($x * $y) % 3); },
0b110 => function($x, $y):int{ return ((($x * $y) % 2) + (($x * $y) % 3)) % 2; },
0b111 => function($x, $y):int{ return ((($x * $y) % 3) + (($x + $y) % 2)) % 2; },
][$maskPattern];
}
}

View File

@ -0,0 +1,75 @@
<?php
/**
* Class BitBuffer
*
* @filesource BitBuffer.php
* @created 25.11.2015
* @package chillerlan\QRCode\Helpers
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Helpers;
use function count, floor;
class BitBuffer{
/**
* @var int[]
*/
public $buffer = [];
/**
* @var int
*/
public $length = 0;
/**
* @return \chillerlan\QRCode\Helpers\BitBuffer
*/
public function clear():BitBuffer{
$this->buffer = [];
$this->length = 0;
return $this;
}
/**
* @param int $num
* @param int $length
*
* @return \chillerlan\QRCode\Helpers\BitBuffer
*/
public function put(int $num, int $length):BitBuffer{
for($i = 0; $i < $length; $i++){
$this->putBit((($num >> ($length - $i - 1)) & 1) === 1);
}
return $this;
}
/**
* @param bool $bit
*
* @return \chillerlan\QRCode\Helpers\BitBuffer
*/
public function putBit(bool $bit):BitBuffer{
$bufIndex = floor($this->length / 8);
if(count($this->buffer) <= $bufIndex){
$this->buffer[] = 0;
}
if($bit === true){
$this->buffer[(int)$bufIndex] |= (0x80 >> ($this->length % 8));
}
$this->length++;
return $this;
}
}

View File

@ -0,0 +1,184 @@
<?php
/**
* Class Polynomial
*
* @filesource Polynomial.php
* @created 25.11.2015
* @package chillerlan\QRCode\Helpers
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Helpers;
use chillerlan\QRCode\QRCodeException;
use function array_fill, count, sprintf;
/**
* @link http://www.thonky.com/qr-code-tutorial/error-correction-coding
*/
class Polynomial{
/**
* @link http://www.thonky.com/qr-code-tutorial/log-antilog-table
*/
protected const table = [
[ 1, 0], [ 2, 0], [ 4, 1], [ 8, 25], [ 16, 2], [ 32, 50], [ 64, 26], [128, 198],
[ 29, 3], [ 58, 223], [116, 51], [232, 238], [205, 27], [135, 104], [ 19, 199], [ 38, 75],
[ 76, 4], [152, 100], [ 45, 224], [ 90, 14], [180, 52], [117, 141], [234, 239], [201, 129],
[143, 28], [ 3, 193], [ 6, 105], [ 12, 248], [ 24, 200], [ 48, 8], [ 96, 76], [192, 113],
[157, 5], [ 39, 138], [ 78, 101], [156, 47], [ 37, 225], [ 74, 36], [148, 15], [ 53, 33],
[106, 53], [212, 147], [181, 142], [119, 218], [238, 240], [193, 18], [159, 130], [ 35, 69],
[ 70, 29], [140, 181], [ 5, 194], [ 10, 125], [ 20, 106], [ 40, 39], [ 80, 249], [160, 185],
[ 93, 201], [186, 154], [105, 9], [210, 120], [185, 77], [111, 228], [222, 114], [161, 166],
[ 95, 6], [190, 191], [ 97, 139], [194, 98], [153, 102], [ 47, 221], [ 94, 48], [188, 253],
[101, 226], [202, 152], [137, 37], [ 15, 179], [ 30, 16], [ 60, 145], [120, 34], [240, 136],
[253, 54], [231, 208], [211, 148], [187, 206], [107, 143], [214, 150], [177, 219], [127, 189],
[254, 241], [225, 210], [223, 19], [163, 92], [ 91, 131], [182, 56], [113, 70], [226, 64],
[217, 30], [175, 66], [ 67, 182], [134, 163], [ 17, 195], [ 34, 72], [ 68, 126], [136, 110],
[ 13, 107], [ 26, 58], [ 52, 40], [104, 84], [208, 250], [189, 133], [103, 186], [206, 61],
[129, 202], [ 31, 94], [ 62, 155], [124, 159], [248, 10], [237, 21], [199, 121], [147, 43],
[ 59, 78], [118, 212], [236, 229], [197, 172], [151, 115], [ 51, 243], [102, 167], [204, 87],
[133, 7], [ 23, 112], [ 46, 192], [ 92, 247], [184, 140], [109, 128], [218, 99], [169, 13],
[ 79, 103], [158, 74], [ 33, 222], [ 66, 237], [132, 49], [ 21, 197], [ 42, 254], [ 84, 24],
[168, 227], [ 77, 165], [154, 153], [ 41, 119], [ 82, 38], [164, 184], [ 85, 180], [170, 124],
[ 73, 17], [146, 68], [ 57, 146], [114, 217], [228, 35], [213, 32], [183, 137], [115, 46],
[230, 55], [209, 63], [191, 209], [ 99, 91], [198, 149], [145, 188], [ 63, 207], [126, 205],
[252, 144], [229, 135], [215, 151], [179, 178], [123, 220], [246, 252], [241, 190], [255, 97],
[227, 242], [219, 86], [171, 211], [ 75, 171], [150, 20], [ 49, 42], [ 98, 93], [196, 158],
[149, 132], [ 55, 60], [110, 57], [220, 83], [165, 71], [ 87, 109], [174, 65], [ 65, 162],
[130, 31], [ 25, 45], [ 50, 67], [100, 216], [200, 183], [141, 123], [ 7, 164], [ 14, 118],
[ 28, 196], [ 56, 23], [112, 73], [224, 236], [221, 127], [167, 12], [ 83, 111], [166, 246],
[ 81, 108], [162, 161], [ 89, 59], [178, 82], [121, 41], [242, 157], [249, 85], [239, 170],
[195, 251], [155, 96], [ 43, 134], [ 86, 177], [172, 187], [ 69, 204], [138, 62], [ 9, 90],
[ 18, 203], [ 36, 89], [ 72, 95], [144, 176], [ 61, 156], [122, 169], [244, 160], [245, 81],
[247, 11], [243, 245], [251, 22], [235, 235], [203, 122], [139, 117], [ 11, 44], [ 22, 215],
[ 44, 79], [ 88, 174], [176, 213], [125, 233], [250, 230], [233, 231], [207, 173], [131, 232],
[ 27, 116], [ 54, 214], [108, 244], [216, 234], [173, 168], [ 71, 80], [142, 88], [ 1, 175],
];
/**
* @var array
*/
protected $num = [];
/**
* Polynomial constructor.
*
* @param array|null $num
* @param int|null $shift
*/
public function __construct(array $num = null, int $shift = null){
$this->setNum($num ?? [1], $shift);
}
/**
* @return array
*/
public function getNum():array{
return $this->num;
}
/**
* @param array $num
* @param int|null $shift
*
* @return \chillerlan\QRCode\Helpers\Polynomial
*/
public function setNum(array $num, int $shift = null):Polynomial{
$offset = 0;
$numCount = count($num);
while($offset < $numCount && $num[$offset] === 0){
$offset++;
}
$this->num = array_fill(0, $numCount - $offset + ($shift ?? 0), 0);
for($i = 0; $i < $numCount - $offset; $i++){
$this->num[$i] = $num[$i + $offset];
}
return $this;
}
/**
* @param array $e
*
* @return \chillerlan\QRCode\Helpers\Polynomial
*/
public function multiply(array $e):Polynomial{
$n = array_fill(0, count($this->num) + count($e) - 1, 0);
foreach($this->num as $i => $vi){
$vi = $this->glog($vi);
foreach($e as $j => $vj){
$n[$i + $j] ^= $this->gexp($vi + $this->glog($vj));
}
}
$this->setNum($n);
return $this;
}
/**
* @param array $e
*
* @return \chillerlan\QRCode\Helpers\Polynomial
*/
public function mod(array $e):Polynomial{
$n = $this->num;
if(count($n) - count($e) < 0){
return $this;
}
$ratio = $this->glog($n[0]) - $this->glog($e[0]);
foreach($e as $i => $v){
$n[$i] ^= $this->gexp($this->glog($v) + $ratio);
}
$this->setNum($n)->mod($e);
return $this;
}
/**
* @param int $n
*
* @return int
* @throws \chillerlan\QRCode\QRCodeException
*/
public function glog(int $n):int{
if($n < 1){
throw new QRCodeException(sprintf('log(%s)', $n));
}
return Polynomial::table[$n][1];
}
/**
* @param int $n
*
* @return int
*/
public function gexp(int $n):int{
if($n < 0){
$n += 255;
}
elseif($n >= 256){
$n -= 255;
}
return Polynomial::table[$n][0];
}
}

View File

@ -0,0 +1,17 @@
<?php
/**
* Class QRCodeOutputException
*
* @filesource QRCodeOutputException.php
* @created 09.12.2015
* @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\QRCodeException;
class QRCodeOutputException extends QRCodeException{}

View File

@ -0,0 +1,112 @@
<?php
/**
* Class QRFpdf
*
* https://github.com/chillerlan/php-qrcode/pull/49
*
* @filesource QRFpdf.php
* @created 03.06.2020
* @package chillerlan\QRCode\Output
* @author Maximilian Kresse
*
* @license MIT
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\QRCode\QRCodeException;
use chillerlan\Settings\SettingsContainerInterface;
use FPDF;
use function array_values, class_exists, count, is_array;
/**
* QRFpdf output module (requires fpdf)
*
* @see https://github.com/Setasign/FPDF
* @see http://www.fpdf.org/
*/
class QRFpdf extends QROutputAbstract{
public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
if(!class_exists(FPDF::class)){
// @codeCoverageIgnoreStart
throw new QRCodeException(
'The QRFpdf output requires FPDF as dependency but the class "\FPDF" couldn\'t be found.'
);
// @codeCoverageIgnoreEnd
}
parent::__construct($options, $matrix);
}
/**
* @inheritDoc
*/
protected function setModuleValues():void{
foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
$v = $this->options->moduleValues[$M_TYPE] ?? null;
if(!is_array($v) || count($v) < 3){
$this->moduleValues[$M_TYPE] = $defaultValue
? [0, 0, 0]
: [255, 255, 255];
}
else{
$this->moduleValues[$M_TYPE] = array_values($v);
}
}
}
/**
* @inheritDoc
*
* @return string|\FPDF
*/
public function dump(string $file = null){
$file = $file ?? $this->options->cachefile;
$fpdf = new FPDF('P', $this->options->fpdfMeasureUnit, [$this->length, $this->length]);
$fpdf->AddPage();
$prevColor = null;
foreach($this->matrix->matrix() as $y => $row){
foreach($row as $x => $M_TYPE){
/** @var int $M_TYPE */
$color = $this->moduleValues[$M_TYPE];
if($prevColor === null || $prevColor !== $color){
$fpdf->SetFillColor(...$color);
$prevColor = $color;
}
$fpdf->Rect($x * $this->scale, $y * $this->scale, 1 * $this->scale, 1 * $this->scale, 'F');
}
}
if($this->options->returnResource){
return $fpdf;
}
$pdfData = $fpdf->Output('S');
if($file !== null){
$this->saveToFile($pdfData, $file);
}
if($this->options->imageBase64){
$pdfData = sprintf('data:application/pdf;base64,%s', base64_encode($pdfData));
}
return $pdfData;
}
}

View File

@ -0,0 +1,208 @@
<?php
/**
* Class QRImage
*
* @filesource QRImage.php
* @created 05.12.2015
* @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*
* @noinspection PhpComposerExtensionStubsInspection
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\QRCode\{QRCode, QRCodeException};
use chillerlan\Settings\SettingsContainerInterface;
use Exception;
use function array_values, base64_encode, call_user_func, count, imagecolorallocate, imagecolortransparent,
imagecreatetruecolor, imagedestroy, imagefilledrectangle, imagegif, imagejpeg, imagepng, in_array,
is_array, ob_end_clean, ob_get_contents, ob_start, range, sprintf;
/**
* Converts the matrix into GD images, raw or base64 output
* requires ext-gd
* @link http://php.net/manual/book.image.php
*/
class QRImage extends QROutputAbstract{
protected const TRANSPARENCY_TYPES = [
QRCode::OUTPUT_IMAGE_PNG,
QRCode::OUTPUT_IMAGE_GIF,
];
/**
* @var string
*/
protected $defaultMode = QRCode::OUTPUT_IMAGE_PNG;
/**
* @see imagecreatetruecolor()
* @var resource
*/
protected $image;
/**
* @inheritDoc
*
* @throws \chillerlan\QRCode\QRCodeException
*/
public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
if(!extension_loaded('gd')){
throw new QRCodeException('ext-gd not loaded'); // @codeCoverageIgnore
}
parent::__construct($options, $matrix);
}
/**
* @inheritDoc
*/
protected function setModuleValues():void{
foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
$v = $this->options->moduleValues[$M_TYPE] ?? null;
if(!is_array($v) || count($v) < 3){
$this->moduleValues[$M_TYPE] = $defaultValue
? [0, 0, 0]
: [255, 255, 255];
}
else{
$this->moduleValues[$M_TYPE] = array_values($v);
}
}
}
/**
* @inheritDoc
*
* @return string|resource
*/
public function dump(string $file = null){
$this->image = imagecreatetruecolor($this->length, $this->length);
// avoid: Indirect modification of overloaded property $imageTransparencyBG has no effect
// https://stackoverflow.com/a/10455217
$tbg = $this->options->imageTransparencyBG;
$background = imagecolorallocate($this->image, ...$tbg);
if((bool)$this->options->imageTransparent && in_array($this->options->outputType, $this::TRANSPARENCY_TYPES, true)){
imagecolortransparent($this->image, $background);
}
imagefilledrectangle($this->image, 0, 0, $this->length, $this->length, $background);
foreach($this->matrix->matrix() as $y => $row){
foreach($row as $x => $M_TYPE){
$this->setPixel($x, $y, $this->moduleValues[$M_TYPE]);
}
}
if($this->options->returnResource){
return $this->image;
}
$imageData = $this->dumpImage($file);
if($this->options->imageBase64){
$imageData = sprintf('data:image/%s;base64,%s', $this->options->outputType, base64_encode($imageData));
}
return $imageData;
}
/**
* @param int $x
* @param int $y
* @param array $rgb
*
* @return void
*/
protected function setPixel(int $x, int $y, array $rgb):void{
imagefilledrectangle(
$this->image,
$x * $this->scale,
$y * $this->scale,
($x + 1) * $this->scale,
($y + 1) * $this->scale,
imagecolorallocate($this->image, ...$rgb)
);
}
/**
* @param string|null $file
*
* @return string
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
*/
protected function dumpImage(string $file = null):string{
$file = $file ?? $this->options->cachefile;
ob_start();
try{
call_user_func([$this, $this->outputMode ?? $this->defaultMode]);
}
// not going to cover edge cases
// @codeCoverageIgnoreStart
catch(Exception $e){
throw new QRCodeOutputException($e->getMessage());
}
// @codeCoverageIgnoreEnd
$imageData = ob_get_contents();
imagedestroy($this->image);
ob_end_clean();
if($file !== null){
$this->saveToFile($imageData, $file);
}
return $imageData;
}
/**
* @return void
*/
protected function png():void{
imagepng(
$this->image,
null,
in_array($this->options->pngCompression, range(-1, 9), true)
? $this->options->pngCompression
: -1
);
}
/**
* Jiff - like... JitHub!
* @return void
*/
protected function gif():void{
imagegif($this->image);
}
/**
* @return void
*/
protected function jpg():void{
imagejpeg(
$this->image,
null,
in_array($this->options->jpegQuality, range(0, 100), true)
? $this->options->jpegQuality
: 85
);
}
}

View File

@ -0,0 +1,123 @@
<?php
/**
* Class QRImagick
*
* @filesource QRImagick.php
* @created 04.07.2018
* @package chillerlan\QRCode\Output
* @author smiley <smiley@chillerlan.net>
* @copyright 2018 smiley
* @license MIT
*
* @noinspection PhpComposerExtensionStubsInspection
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\QRCode\QRCodeException;
use chillerlan\Settings\SettingsContainerInterface;
use Imagick, ImagickDraw, ImagickPixel;
use function is_string;
/**
* ImageMagick output module
* requires ext-imagick
* @link http://php.net/manual/book.imagick.php
* @link http://phpimagick.com
*/
class QRImagick extends QROutputAbstract{
/**
* @var \Imagick
*/
protected $imagick;
/**
* @inheritDoc
* @throws \chillerlan\QRCode\QRCodeException
*/
public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
if(!extension_loaded('imagick')){
throw new QRCodeException('ext-imagick not loaded'); // @codeCoverageIgnore
}
parent::__construct($options, $matrix);
}
/**
* @inheritDoc
*/
protected function setModuleValues():void{
foreach($this::DEFAULT_MODULE_VALUES as $type => $defaultValue){
$v = $this->options->moduleValues[$type] ?? null;
if(!is_string($v)){
$this->moduleValues[$type] = $defaultValue
? new ImagickPixel($this->options->markupDark)
: new ImagickPixel($this->options->markupLight);
}
else{
$this->moduleValues[$type] = new ImagickPixel($v);
}
}
}
/**
* @inheritDoc
*
* @return string|\Imagick
*/
public function dump(string $file = null){
$file = $file ?? $this->options->cachefile;
$this->imagick = new Imagick;
$this->imagick->newImage(
$this->length,
$this->length,
new ImagickPixel($this->options->imagickBG ?? 'transparent'),
$this->options->imagickFormat
);
$this->drawImage();
if($this->options->returnResource){
return $this->imagick;
}
$imageData = $this->imagick->getImageBlob();
if($file !== null){
$this->saveToFile($imageData, $file);
}
return $imageData;
}
/**
* @return void
*/
protected function drawImage():void{
$draw = new ImagickDraw;
foreach($this->matrix->matrix() as $y => $row){
foreach($row as $x => $M_TYPE){
$draw->setStrokeColor($this->moduleValues[$M_TYPE]);
$draw->setFillColor($this->moduleValues[$M_TYPE]);
$draw->rectangle(
$x * $this->scale,
$y * $this->scale,
($x + 1) * $this->scale,
($y + 1) * $this->scale
);
}
}
$this->imagick->drawImage($draw);
}
}

View File

@ -0,0 +1,151 @@
<?php
/**
* Class QRMarkup
*
* @filesource QRMarkup.php
* @created 17.12.2016
* @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2016 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\QRCode;
use function is_string, sprintf, strip_tags, trim;
/**
* Converts the matrix into markup types: HTML, SVG, ...
*/
class QRMarkup extends QROutputAbstract{
/**
* @var string
*/
protected $defaultMode = QRCode::OUTPUT_MARKUP_SVG;
/**
* @see \sprintf()
*
* @var string
*/
protected $svgHeader = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" class="qr-svg %1$s" style="width: 100%%; height: auto;" viewBox="0 0 %2$d %2$d">';
/**
* @inheritDoc
*/
protected function setModuleValues():void{
foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
$v = $this->options->moduleValues[$M_TYPE] ?? null;
if(!is_string($v)){
$this->moduleValues[$M_TYPE] = $defaultValue
? $this->options->markupDark
: $this->options->markupLight;
}
else{
$this->moduleValues[$M_TYPE] = trim(strip_tags($v), '\'"');
}
}
}
/**
* @return string
*/
protected function html():string{
$html = '<div class="'.$this->options->cssClass.'">'.$this->options->eol;
foreach($this->matrix->matrix() as $row){
$html .= '<div>';
foreach($row as $M_TYPE){
$html .= '<span style="background: '.$this->moduleValues[$M_TYPE].';"></span>';
}
$html .= '</div>'.$this->options->eol;
}
$html .= '</div>'.$this->options->eol;
if($this->options->cachefile){
return '<!DOCTYPE html><head><meta charset="UTF-8"></head><body>'.$this->options->eol.$html.'</body>';
}
return $html;
}
/**
* @link https://github.com/codemasher/php-qrcode/pull/5
*
* @return string
*/
protected function svg():string{
$matrix = $this->matrix->matrix();
$svg = sprintf($this->svgHeader, $this->options->cssClass, $this->options->svgViewBoxSize ?? $this->moduleCount)
.$this->options->eol
.'<defs>'.$this->options->svgDefs.'</defs>'
.$this->options->eol;
foreach($this->moduleValues as $M_TYPE => $value){
$path = '';
foreach($matrix as $y => $row){
//we'll combine active blocks within a single row as a lightweight compression technique
$start = null;
$count = 0;
foreach($row as $x => $module){
if($module === $M_TYPE){
$count++;
if($start === null){
$start = $x;
}
if(isset($row[$x + 1])){
continue;
}
}
if($count > 0){
$len = $count;
$path .= sprintf('M%s %s h%s v1 h-%sZ ', $start, $y, $len, $len);
// reset count
$count = 0;
$start = null;
}
}
}
if(!empty($path)){
$svg .= sprintf('<path class="qr-%s %s" stroke="transparent" fill="%s" fill-opacity="%s" d="%s" />', $M_TYPE, $this->options->cssClass, $value, $this->options->svgOpacity, $path);
}
}
// close svg
$svg .= '</svg>'.$this->options->eol;
// if saving to file, append the correct headers
if($this->options->cachefile){
return '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'.$this->options->eol.$svg;
}
if($this->options->imageBase64){
$svg = sprintf('data:image/svg+xml;base64,%s', base64_encode($svg));
}
return $svg;
}
}

View File

@ -0,0 +1,130 @@
<?php
/**
* Class QROutputAbstract
*
* @filesource QROutputAbstract.php
* @created 09.12.2015
* @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\{Data\QRMatrix, QRCode};
use chillerlan\Settings\SettingsContainerInterface;
use function call_user_func, dirname, file_put_contents, get_called_class, in_array, is_writable, sprintf;
/**
* common output abstract
*/
abstract class QROutputAbstract implements QROutputInterface{
/**
* @var int
*/
protected $moduleCount;
/**
* @param \chillerlan\QRCode\Data\QRMatrix $matrix
*/
protected $matrix;
/**
* @var \chillerlan\QRCode\QROptions
*/
protected $options;
/**
* @var string
*/
protected $outputMode;
/**
* @var string;
*/
protected $defaultMode;
/**
* @var int
*/
protected $scale;
/**
* @var int
*/
protected $length;
/**
* @var array
*/
protected $moduleValues;
/**
* QROutputAbstract constructor.
*
* @param \chillerlan\Settings\SettingsContainerInterface $options
* @param \chillerlan\QRCode\Data\QRMatrix $matrix
*/
public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
$this->options = $options;
$this->matrix = $matrix;
$this->moduleCount = $this->matrix->size();
$this->scale = $this->options->scale;
$this->length = $this->moduleCount * $this->scale;
$class = get_called_class();
if(isset(QRCode::OUTPUT_MODES[$class]) && in_array($this->options->outputType, QRCode::OUTPUT_MODES[$class])){
$this->outputMode = $this->options->outputType;
}
$this->setModuleValues();
}
/**
* Sets the initial module values (clean-up & defaults)
*
* @return void
*/
abstract protected function setModuleValues():void;
/**
* saves the qr data to a file
*
* @see file_put_contents()
* @see \chillerlan\QRCode\QROptions::cachefile
*
* @param string $data
* @param string $file
*
* @return bool
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
*/
protected function saveToFile(string $data, string $file):bool{
if(!is_writable(dirname($file))){
throw new QRCodeOutputException(sprintf('Could not write data to cache file: %s', $file));
}
return (bool)file_put_contents($file, $data);
}
/**
* @inheritDoc
*/
public function dump(string $file = null){
// call the built-in output method
$data = call_user_func([$this, $this->outputMode ?? $this->defaultMode]);
$file = $file ?? $this->options->cachefile;
if($file !== null){
$this->saveToFile($data, $file);
}
return $data;
}
}

View File

@ -0,0 +1,55 @@
<?php
/**
* Interface QROutputInterface,
*
* @filesource QROutputInterface.php
* @created 02.12.2015
* @package chillerlan\QRCode\Output
* @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{
const DEFAULT_MODULE_VALUES = [
// light
QRMatrix::M_DATA => false, // 4
QRMatrix::M_FINDER => false, // 6
QRMatrix::M_SEPARATOR => false, // 8
QRMatrix::M_ALIGNMENT => false, // 10
QRMatrix::M_TIMING => false, // 12
QRMatrix::M_FORMAT => false, // 14
QRMatrix::M_VERSION => false, // 16
QRMatrix::M_QUIETZONE => false, // 18
QRMatrix::M_LOGO => false, // 20
QRMatrix::M_TEST => false, // 255
// dark
QRMatrix::M_DARKMODULE << 8 => true, // 512
QRMatrix::M_DATA << 8 => true, // 1024
QRMatrix::M_FINDER << 8 => true, // 1536
QRMatrix::M_ALIGNMENT << 8 => true, // 2560
QRMatrix::M_TIMING << 8 => true, // 3072
QRMatrix::M_FORMAT << 8 => true, // 3584
QRMatrix::M_VERSION << 8 => true, // 4096
QRMatrix::M_FINDER_DOT << 8 => true, // 5632
QRMatrix::M_TEST << 8 => true, // 65280
];
/**
* generates the output, optionally dumps it to a file, and returns it
*
* @param string|null $file
*
* @return mixed
*/
public function dump(string $file = null);
}

View File

@ -0,0 +1,76 @@
<?php
/**
* Class QRString
*
* @filesource QRString.php
* @created 05.12.2015
* @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\QRCode;
use function implode, is_string, json_encode;
/**
* Converts the matrix data into string types
*/
class QRString extends QROutputAbstract{
/**
* @var string
*/
protected $defaultMode = QRCode::OUTPUT_STRING_TEXT;
/**
* @inheritDoc
*/
protected function setModuleValues():void{
foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
$v = $this->options->moduleValues[$M_TYPE] ?? null;
if(!is_string($v)){
$this->moduleValues[$M_TYPE] = $defaultValue
? $this->options->textDark
: $this->options->textLight;
}
else{
$this->moduleValues[$M_TYPE] = $v;
}
}
}
/**
* @return string
*/
protected function text():string{
$str = [];
foreach($this->matrix->matrix() as $row){
$r = [];
foreach($row as $M_TYPE){
$r[] = $this->moduleValues[$M_TYPE];
}
$str[] = implode('', $r);
}
return implode($this->options->eol, $str);
}
/**
* @return string
*/
protected function json():string{
return json_encode($this->matrix->matrix());
}
}

View File

@ -0,0 +1,310 @@
<?php
/**
* Class QRCode
*
* @filesource QRCode.php
* @created 26.11.2015
* @package chillerlan\QRCode
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode;
use chillerlan\QRCode\Data\{
MaskPatternTester, QRCodeDataException, QRDataInterface, QRMatrix
};
use chillerlan\QRCode\Output\{
QRCodeOutputException, QRFpdf, QRImage, QRImagick, QRMarkup, QROutputInterface, QRString
};
use chillerlan\Settings\SettingsContainerInterface;
use function array_search, call_user_func_array, class_exists, in_array, min, ord, strlen;
/**
* Turns a text string into a Model 2 QR Code
*
* @link https://github.com/kazuhikoarase/qrcode-generator/tree/master/php
* @link http://www.qrcode.com/en/codes/model12.html
* @link http://www.thonky.com/qr-code-tutorial/
*/
class QRCode{
/**
* API constants
*/
public const OUTPUT_MARKUP_HTML = 'html';
public const OUTPUT_MARKUP_SVG = 'svg';
public const OUTPUT_IMAGE_PNG = 'png';
public const OUTPUT_IMAGE_JPG = 'jpg';
public const OUTPUT_IMAGE_GIF = 'gif';
public const OUTPUT_STRING_JSON = 'json';
public const OUTPUT_STRING_TEXT = 'text';
public const OUTPUT_IMAGICK = 'imagick';
public const OUTPUT_FPDF = 'fpdf';
public const OUTPUT_CUSTOM = 'custom';
public const VERSION_AUTO = -1;
public const MASK_PATTERN_AUTO = -1;
public const ECC_L = 0b01; // 7%.
public const ECC_M = 0b00; // 15%.
public const ECC_Q = 0b11; // 25%.
public const ECC_H = 0b10; // 30%.
public const DATA_NUMBER = 0b0001;
public const DATA_ALPHANUM = 0b0010;
public const DATA_BYTE = 0b0100;
public const DATA_KANJI = 0b1000;
public const ECC_MODES = [
self::ECC_L => 0,
self::ECC_M => 1,
self::ECC_Q => 2,
self::ECC_H => 3,
];
public const DATA_MODES = [
self::DATA_NUMBER => 0,
self::DATA_ALPHANUM => 1,
self::DATA_BYTE => 2,
self::DATA_KANJI => 3,
];
public const OUTPUT_MODES = [
QRMarkup::class => [
self::OUTPUT_MARKUP_SVG,
self::OUTPUT_MARKUP_HTML,
],
QRImage::class => [
self::OUTPUT_IMAGE_PNG,
self::OUTPUT_IMAGE_GIF,
self::OUTPUT_IMAGE_JPG,
],
QRString::class => [
self::OUTPUT_STRING_JSON,
self::OUTPUT_STRING_TEXT,
],
QRImagick::class => [
self::OUTPUT_IMAGICK,
],
QRFpdf::class => [
self::OUTPUT_FPDF
]
];
/**
* @var \chillerlan\QRCode\QROptions|\chillerlan\Settings\SettingsContainerInterface
*/
protected $options;
/**
* @var \chillerlan\QRCode\Data\QRDataInterface
*/
protected $dataInterface;
/**
* QRCode constructor.
*
* @param \chillerlan\Settings\SettingsContainerInterface|null $options
*/
public function __construct(SettingsContainerInterface $options = null){
$this->options = $options ?? new QROptions;
}
/**
* Renders a QR Code for the given $data and QROptions
*
* @param string $data
* @param string|null $file
*
* @return mixed
*/
public function render(string $data, string $file = null){
return $this->initOutputInterface($data)->dump($file);
}
/**
* Returns a QRMatrix object for the given $data and current QROptions
*
* @param string $data
*
* @return \chillerlan\QRCode\Data\QRMatrix
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
public function getMatrix(string $data):QRMatrix{
if(empty($data)){
throw new QRCodeDataException('QRCode::getMatrix() No data given.');
}
$this->dataInterface = $this->initDataInterface($data);
$maskPattern = $this->options->maskPattern === $this::MASK_PATTERN_AUTO
? $this->getBestMaskPattern()
: $this->options->maskPattern;
$matrix = $this->dataInterface->initMatrix($maskPattern);
if((bool)$this->options->addQuietzone){
$matrix->setQuietZone($this->options->quietzoneSize);
}
return $matrix;
}
/**
* shoves a QRMatrix through the MaskPatternTester to find the lowest penalty mask pattern
*
* @see \chillerlan\QRCode\Data\MaskPatternTester
*
* @return int
*/
protected function getBestMaskPattern():int{
$penalties = [];
for($pattern = 0; $pattern < 8; $pattern++){
$tester = new MaskPatternTester($this->dataInterface->initMatrix($pattern, true));
$penalties[$pattern] = $tester->testPattern();
}
return array_search(min($penalties), $penalties, true);
}
/**
* returns a fresh QRDataInterface for the given $data
*
* @param string $data
*
* @return \chillerlan\QRCode\Data\QRDataInterface
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
public function initDataInterface(string $data):QRDataInterface{
$dataModes = ['Number', 'AlphaNum', 'Kanji', 'Byte'];
$dataNamespace = __NAMESPACE__.'\\Data\\';
// allow forcing the data mode
// see https://github.com/chillerlan/php-qrcode/issues/39
if(in_array($this->options->dataMode, $dataModes, true)){
$dataInterface = $dataNamespace.$this->options->dataMode;
return new $dataInterface($this->options, $data);
}
foreach($dataModes as $mode){
$dataInterface = $dataNamespace.$mode;
if(call_user_func_array([$this, 'is'.$mode], [$data]) && class_exists($dataInterface)){
return new $dataInterface($this->options, $data);
}
}
throw new QRCodeDataException('invalid data type'); // @codeCoverageIgnore
}
/**
* returns a fresh (built-in) QROutputInterface
*
* @param string $data
*
* @return \chillerlan\QRCode\Output\QROutputInterface
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
*/
protected function initOutputInterface(string $data):QROutputInterface{
if($this->options->outputType === $this::OUTPUT_CUSTOM && class_exists($this->options->outputInterface)){
return new $this->options->outputInterface($this->options, $this->getMatrix($data));
}
foreach($this::OUTPUT_MODES as $outputInterface => $modes){
if(in_array($this->options->outputType, $modes, true) && class_exists($outputInterface)){
return new $outputInterface($this->options, $this->getMatrix($data));
}
}
throw new QRCodeOutputException('invalid output type');
}
/**
* checks if a string qualifies as numeric
*
* @param string $string
*
* @return bool
*/
public function isNumber(string $string):bool{
return $this->checkString($string, QRDataInterface::NUMBER_CHAR_MAP);
}
/**
* checks if a string qualifies as alphanumeric
*
* @param string $string
*
* @return bool
*/
public function isAlphaNum(string $string):bool{
return $this->checkString($string, QRDataInterface::ALPHANUM_CHAR_MAP);
}
/**
* checks is a given $string matches the characters of a given $charmap, returns false on the first invalid occurence.
*
* @param string $string
* @param array $charmap
*
* @return bool
*/
protected function checkString(string $string, array $charmap):bool{
$len = strlen($string);
for($i = 0; $i < $len; $i++){
if(!in_array($string[$i], $charmap, true)){
return false;
}
}
return true;
}
/**
* checks if a string qualifies as Kanji
*
* @param string $string
*
* @return bool
*/
public function isKanji(string $string):bool{
$i = 0;
$len = strlen($string);
while($i + 1 < $len){
$c = ((0xff & ord($string[$i])) << 8) | (0xff & ord($string[$i + 1]));
if(!($c >= 0x8140 && $c <= 0x9FFC) && !($c >= 0xE040 && $c <= 0xEBBF)){
return false;
}
$i += 2;
}
return $i >= $len;
}
/**
* a dummy
*
* @param $data
*
* @return bool
*/
protected function isByte(string $data):bool{
return !empty($data);
}
}

View File

@ -0,0 +1,15 @@
<?php
/**
* Class QRCodeException
*
* @filesource QRCodeException.php
* @created 27.11.2015
* @package chillerlan\QRCode
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode;
class QRCodeException extends \Exception{}

View File

@ -0,0 +1,61 @@
<?php
/**
* Class QROptions
*
* @filesource QROptions.php
* @created 08.12.2015
* @package chillerlan\QRCode
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode;
use chillerlan\Settings\SettingsContainerAbstract;
/**
* @property int $version
* @property int $versionMin
* @property int $versionMax
* @property int $eccLevel
* @property int $maskPattern
* @property bool $addQuietzone
* @property bool $quietzoneSize
*
* @property string $dataMode
* @property string $outputType
* @property string $outputInterface
* @property string $cachefile
*
* @property string $eol
* @property int $scale
*
* @property string $cssClass
* @property string $svgOpacity
* @property string $svgDefs
* @property int $svgViewBoxSize
*
* @property string $textDark
* @property string $textLight
*
* @property string $markupDark
* @property string $markupLight
*
* @property bool $returnResource
* @property bool $imageBase64
* @property bool $imageTransparent
* @property array $imageTransparencyBG
* @property int $pngCompression
* @property int $jpegQuality
*
* @property string $imagickFormat
* @property string $imagickBG
*
* @property string $fpdfMeasureUnit
*
* @property array $moduleValues
*/
class QROptions extends SettingsContainerAbstract{
use QROptionsTrait;
}

View File

@ -0,0 +1,408 @@
<?php
/**
* Trait QROptionsTrait
*
* @filesource QROptionsTrait.php
* @created 10.03.2018
* @package chillerlan\QRCode
* @author smiley <smiley@chillerlan.net>
* @copyright 2018 smiley
* @license MIT
*/
namespace chillerlan\QRCode;
use function array_values, count, in_array, is_array, is_numeric, max, min, sprintf, strtolower;
trait QROptionsTrait{
/**
* QR Code version number
*
* [1 ... 40] or QRCode::VERSION_AUTO
*
* @var int
*/
protected $version = QRCode::VERSION_AUTO;
/**
* Minimum QR version (if $version = QRCode::VERSION_AUTO)
*
* @var int
*/
protected $versionMin = 1;
/**
* Maximum QR version
*
* @var int
*/
protected $versionMax = 40;
/**
* Error correct level
*
* QRCode::ECC_X where X is
* L => 7%
* M => 15%
* Q => 25%
* H => 30%
*
* @var int
*/
protected $eccLevel = QRCode::ECC_L;
/**
* Mask Pattern to use
*
* [0...7] or QRCode::MASK_PATTERN_AUTO
*
* @var int
*/
protected $maskPattern = QRCode::MASK_PATTERN_AUTO;
/**
* Add a "quiet zone" (margin) according to the QR code spec
*
* @var bool
*/
protected $addQuietzone = true;
/**
* Size of the quiet zone
*
* internally clamped to [0 ... $moduleCount / 2], defaults to 4 modules
*
* @var int
*/
protected $quietzoneSize = 4;
/**
* Use this to circumvent the data mode detection and force the usage of the given mode.
* valid modes are: Number, AlphaNum, Kanji, Byte
*
* @see https://github.com/chillerlan/php-qrcode/issues/39
*
* @var string|null
*/
protected $dataMode = null;
/**
* QRCode::OUTPUT_MARKUP_XXXX where XXXX = HTML, SVG
* QRCode::OUTPUT_IMAGE_XXX where XXX = PNG, GIF, JPG
* QRCode::OUTPUT_STRING_XXXX where XXXX = TEXT, JSON
* QRCode::OUTPUT_CUSTOM
*
* @var string
*/
protected $outputType = QRCode::OUTPUT_IMAGE_PNG;
/**
* the FQCN of the custom QROutputInterface if $outputType is set to QRCode::OUTPUT_CUSTOM
*
* @var string|null
*/
protected $outputInterface = null;
/**
* /path/to/cache.file
*
* @var string|null
*/
protected $cachefile = null;
/**
* newline string [HTML, SVG, TEXT]
*
* @var string
*/
protected $eol = PHP_EOL;
/**
* size of a QR code pixel [SVG, IMAGE_*]
* HTML -> via CSS
*
* @var int
*/
protected $scale = 5;
/**
* a common css class
*
* @var string
*/
protected $cssClass = '';
/**
* SVG opacity
*
* @var float
*/
protected $svgOpacity = 1.0;
/**
* anything between <defs>
*
* @see https://developer.mozilla.org/docs/Web/SVG/Element/defs
*
* @var string
*/
protected $svgDefs = '<style>rect{shape-rendering:crispEdges}</style>';
/**
* SVG viewBox size. a single integer number which defines width/height of the viewBox attribute.
*
* viewBox="0 0 x x"
*
* @see https://css-tricks.com/scale-svg/#article-header-id-3
*
* @var int|null
*/
protected $svgViewBoxSize = null;
/**
* string substitute for dark
*
* @var string
*/
protected $textDark = '🔴';
/**
* string substitute for light
*
* @var string
*/
protected $textLight = '⭕';
/**
* markup substitute for dark (CSS value)
*
* @var string
*/
protected $markupDark = '#000';
/**
* markup substitute for light (CSS value)
*
* @var string
*/
protected $markupLight = '#fff';
/**
* Return the image resource instead of a render if applicable.
* This option overrides other output options, such as $cachefile and $imageBase64.
*
* Supported by the following modules:
*
* - QRImage: resource
* - QRImagick: Imagick
* - QRFpdf: FPDF
*
* @see \chillerlan\QRCode\Output\QROutputInterface::dump()
*
* @var bool
*/
protected $returnResource = false;
/**
* toggle base64 or raw image data
*
* @var bool
*/
protected $imageBase64 = true;
/**
* toggle transparency, not supported by jpg
*
* @var bool
*/
protected $imageTransparent = true;
/**
* @see imagecolortransparent()
*
* @var array [R, G, B]
*/
protected $imageTransparencyBG = [255, 255, 255];
/**
* @see imagepng()
*
* @var int
*/
protected $pngCompression = -1;
/**
* @see imagejpeg()
*
* @var int
*/
protected $jpegQuality = 85;
/**
* Imagick output format
*
* @see Imagick::setType()
*
* @var string
*/
protected $imagickFormat = 'png';
/**
* Imagick background color (defaults to "transparent")
*
* @see \ImagickPixel::__construct()
*
* @var string|null
*/
protected $imagickBG = null;
/**
* Measurement unit for FPDF output: pt, mm, cm, in (defaults to "pt")
*
* @see \FPDF::__construct()
*/
protected $fpdfMeasureUnit = 'pt';
/**
* Module values map
*
* HTML, IMAGICK: #ABCDEF, cssname, rgb(), rgba()...
* IMAGE: [63, 127, 255] // R, G, B
*
* @var array|null
*/
protected $moduleValues = null;
/**
* clamp min/max version number
*
* @param int $versionMin
* @param int $versionMax
*
* @return void
*/
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
*
* @param int $version
*
* @return void
*/
protected function set_versionMin(int $version):void{
$this->setMinMaxVersion($version, $this->versionMax);
}
/**
* sets the maximum version number
*
* @param int $version
*
* @return void
*/
protected function set_versionMax(int $version):void{
$this->setMinMaxVersion($this->versionMin, $version);
}
/**
* sets the error correction level
*
* @param int $eccLevel
*
* @return void
* @throws \chillerlan\QRCode\QRCodeException
*/
protected function set_eccLevel(int $eccLevel):void{
if(!isset(QRCode::ECC_MODES[$eccLevel])){
throw new QRCodeException(sprintf('Invalid error correct level: %s', $eccLevel));
}
$this->eccLevel = $eccLevel;
}
/**
* sets/clamps the mask pattern
*
* @param int $maskPattern
*
* @return void
*/
protected function set_maskPattern(int $maskPattern):void{
if($maskPattern !== QRCode::MASK_PATTERN_AUTO){
$this->maskPattern = max(0, min(7, $maskPattern));
}
}
/**
* sets the transparency background color
*
* @param mixed $imageTransparencyBG
*
* @return void
* @throws \chillerlan\QRCode\QRCodeException
*/
protected function set_imageTransparencyBG($imageTransparencyBG):void{
// invalid value - set to white as default
if(!is_array($imageTransparencyBG) || count($imageTransparencyBG) < 3){
$this->imageTransparencyBG = [255, 255, 255];
return;
}
foreach($imageTransparencyBG as $k => $v){
if(!is_numeric($v)){
throw new QRCodeException('Invalid RGB value.');
}
// clamp the values
$this->imageTransparencyBG[$k] = max(0, min(255, (int)$v));
}
// use the array values to not run into errors with the spread operator (...$arr)
$this->imageTransparencyBG = array_values($this->imageTransparencyBG);
}
/**
* sets/clamps the version number
*
* @param int $version
*
* @return void
*/
protected function set_version(int $version):void{
if($version !== QRCode::VERSION_AUTO){
$this->version = max(1, min(40, $version));
}
}
/**
* 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?
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Class AlphaNumTest
*
* @filesource AlphaNumTest.php
* @created 24.11.2017
* @package chillerlan\QRCodeTest\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Data;
use chillerlan\QRCode\Data\{AlphaNum, QRCodeDataException};
class AlphaNumTest extends DatainterfaceTestAbstract{
protected $FQCN = AlphaNum::class;
protected $testdata = '0 $%*+-./:';
protected $expected = [
32, 80, 36, 212, 252, 15, 175, 251,
176, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
112, 43, 9, 248, 200, 194, 75, 25,
205, 173, 154, 68, 191, 16, 128,
92, 112, 20, 198, 27
];
public function testGetCharCodeException(){
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('illegal char: "#" [35]');
$this->dataInterface->setData('#');
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* Class ByteTest
*
* @filesource ByteTest.php
* @created 24.11.2017
* @package chillerlan\QRCodeTest\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Data;
use chillerlan\QRCode\Data\Byte;
class ByteTest extends DatainterfaceTestAbstract{
protected $FQCN = Byte::class;
protected $testdata = '[¯\_(ツ)_/¯]';
protected $expected = [
64, 245, 188, 42, 245, 197, 242, 142,
56, 56, 66, 149, 242, 252, 42, 245,
208, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
79, 89, 226, 48, 209, 89, 151, 1,
12, 73, 42, 163, 11, 34, 255, 205,
21, 47, 250, 101
];
}

View File

@ -0,0 +1,65 @@
<?php
/**
* Class DatainterfaceTestAbstract
*
* @filesource DatainterfaceTestAbstract.php
* @created 24.11.2017
* @package chillerlan\QRCodeTest\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Data;
use chillerlan\QRCode\QROptions;
use chillerlan\QRCode\Data\{QRCodeDataException, QRDataInterface, QRMatrix};
use chillerlan\QRCodeTest\QRTestAbstract;
abstract class DatainterfaceTestAbstract extends QRTestAbstract{
/**
* @var \chillerlan\QRCode\Data\QRDataAbstract
*/
protected $dataInterface;
protected $testdata;
protected $expected;
protected function setUp():void{
parent::setUp();
$this->dataInterface = $this->reflection->newInstanceArgs([new QROptions(['version' => 4])]);
}
public function testInstance(){
$this->dataInterface = $this->reflection->newInstanceArgs([new QROptions, $this->testdata]);
$this->assertInstanceOf(QRDataInterface::class, $this->dataInterface);
}
public function testSetData(){
$this->dataInterface->setData($this->testdata);
$this->assertSame($this->expected, $this->getProperty('matrixdata')->getValue($this->dataInterface));
}
public function testInitMatrix(){
$m = $this->dataInterface->setData($this->testdata)->initMatrix(0);
$this->assertInstanceOf(QRMatrix::class, $m);
}
public function testGetMinimumVersion(){
$this->assertSame(1, $this->getMethod('getMinimumVersion')->invoke($this->dataInterface));
}
public function testGetMinimumVersionException(){
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('data exceeds');
$this->getProperty('strlen')->setValue($this->dataInterface, 13370);
$this->getMethod('getMinimumVersion')->invoke($this->dataInterface);
}
}

View File

@ -0,0 +1,50 @@
<?php
/**
* Class KanjiTest
*
* @filesource KanjiTest.php
* @created 24.11.2017
* @package chillerlan\QRCodeTest\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Data;
use chillerlan\QRCode\Data\{Kanji, QRCodeDataException};
class KanjiTest extends DatainterfaceTestAbstract{
protected $FQCN = Kanji::class;
protected $testdata = '茗荷茗荷茗荷茗荷茗荷';
protected $expected = [
128, 173, 85, 26, 95, 85, 70, 151,
213, 81, 165, 245, 84, 105, 125, 85,
26, 92, 0, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
195, 11, 221, 91, 141, 220, 163, 46,
165, 37, 163, 176, 79, 0, 64, 68,
96, 113, 54, 191
];
public function testIllegalCharException1(){
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('illegal char at 1 [16191]');
$this->dataInterface->setData('ÃÃ');
}
public function testIllegalCharException2(){
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('illegal char at 1');
$this->dataInterface->setData('Ã');
}
}

View File

@ -0,0 +1,29 @@
<?php
/**
* Class MaskPatternTesterTest
*
* @filesource MaskPatternTesterTest.php
* @created 24.11.2017
* @package chillerlan\QRCodeTest\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Data;
use chillerlan\QRCode\{QROptions, Data\Byte, Data\MaskPatternTester};
use chillerlan\QRCodeTest\QRTestAbstract;
class MaskPatternTesterTest extends QRTestAbstract{
protected $FQCN = MaskPatternTester::class;
// coverage
public function testMaskpattern(){
$matrix = (new Byte(new QROptions(['version' => 10]), 'test'))->initMatrix(3, true);
$this->assertSame(4243, (new MaskPatternTester($matrix))->testPattern());
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Class NumberTest
*
* @filesource NumberTest.php
* @created 24.11.2017
* @package chillerlan\QRCodeTest\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Data;
use chillerlan\QRCode\Data\{Number, QRCodeDataException};
class NumberTest extends DatainterfaceTestAbstract{
protected $FQCN = Number::class;
protected $testdata = '0123456789';
protected $expected = [
16, 40, 12, 86, 106, 105, 0, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236,
201, 141, 102, 116, 238, 162, 239, 230,
222, 37, 79, 192, 42, 109, 188, 72,
89, 63, 168, 151
];
public function testGetCharCodeException(){
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('illegal char: "#" [35]');
$this->dataInterface->setData('#');
}
}

View File

@ -0,0 +1,260 @@
<?php
/**
* Class QRMatrixTest
*
* @filesource QRMatrixTest.php
* @created 17.11.2017
* @package chillerlan\QRCodeTest\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Data;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
use chillerlan\QRCode\Data\{QRCodeDataException, QRMatrix};
use chillerlan\QRCodeTest\QRTestAbstract;
use ReflectionClass;
class QRMatrixTest extends QRTestAbstract{
protected $FQCN = QRMatrix::class;
protected $version = 7;
/**
* @var \chillerlan\QRCode\Data\QRMatrix
*/
protected $matrix;
protected function setUp():void{
parent::setUp();
$this->matrix = $this->reflection->newInstanceArgs([$this->version, QRCode::ECC_L]);
}
public function testInvalidVersionException(){
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('invalid QR Code version');
$this->reflection->newInstanceArgs([42, 0]);
}
public function testInvalidEccException(){
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('invalid ecc level');
$this->reflection->newInstanceArgs([1, 42]);
}
public function testInstance(){
$this->assertInstanceOf($this->FQCN, $this->matrix);
}
public function testSize(){
$this->assertCount($this->matrix->size(), $this->matrix->matrix());
}
public function testVersion(){
$this->assertSame($this->version, $this->matrix->version());
}
public function testECC(){
$this->assertSame(QRCode::ECC_L, $this->matrix->eccLevel());
}
public function testMaskPattern(){
$this->assertSame(-1, $this->matrix->maskPattern());
}
public function testGetSetCheck(){
$this->matrix->set(10, 10, true, QRMatrix::M_TEST);
$this->assertSame(65280, $this->matrix->get(10, 10));
$this->assertTrue($this->matrix->check(10, 10));
$this->matrix->set(20, 20, false, QRMatrix::M_TEST);
$this->assertSame(255, $this->matrix->get(20, 20));
$this->assertFalse($this->matrix->check(20, 20));
}
public function testSetDarkModule(){
$this->matrix->setDarkModule();
$this->assertSame(QRMatrix::M_DARKMODULE << 8, $this->matrix->get(8, $this->matrix->size() - 8));
}
public function testSetFinderPattern(){
$this->matrix->setFinderPattern();
$this->assertSame(QRMatrix::M_FINDER << 8, $this->matrix->get(0, 0));
$this->assertSame(QRMatrix::M_FINDER << 8, $this->matrix->get(0, $this->matrix->size() - 1));
$this->assertSame(QRMatrix::M_FINDER << 8, $this->matrix->get($this->matrix->size() - 1, 0));
}
public function testSetSeparators(){
$this->matrix->setSeparators();
$this->assertSame(QRMatrix::M_SEPARATOR, $this->matrix->get(7, 0));
$this->assertSame(QRMatrix::M_SEPARATOR, $this->matrix->get(0, 7));
$this->assertSame(QRMatrix::M_SEPARATOR, $this->matrix->get(0, $this->matrix->size() - 8));
$this->assertSame(QRMatrix::M_SEPARATOR, $this->matrix->get($this->matrix->size() - 8, 0));
}
public function testSetAlignmentPattern(){
$this->matrix
->setFinderPattern()
->setAlignmentPattern()
;
$alignmentPattern = (new ReflectionClass(QRMatrix::class))->getConstant('alignmentPattern')[$this->version];
foreach($alignmentPattern as $py){
foreach($alignmentPattern as $px){
if($this->matrix->get($px, $py) === QRMatrix::M_FINDER << 8){
$this->assertSame(QRMatrix::M_FINDER << 8, $this->matrix->get($px, $py), 'skipped finder pattern');
continue;
}
$this->assertSame(QRMatrix::M_ALIGNMENT << 8, $this->matrix->get($px, $py));
}
}
}
public function testSetTimingPattern(){
$this->matrix
->setAlignmentPattern()
->setTimingPattern()
;
$size = $this->matrix->size();
for($i = 7; $i < $size - 7; $i++){
if($i % 2 === 0){
$p1 = $this->matrix->get(6, $i);
if($p1 === QRMatrix::M_ALIGNMENT << 8){
$this->assertSame(QRMatrix::M_ALIGNMENT << 8, $p1, 'skipped alignment pattern');
continue;
}
$this->assertSame(QRMatrix::M_TIMING << 8, $p1);
$this->assertSame(QRMatrix::M_TIMING << 8, $this->matrix->get($i, 6));
}
}
}
public function testSetVersionNumber(){
$this->matrix->setVersionNumber(true);
$this->assertSame(QRMatrix::M_VERSION, $this->matrix->get($this->matrix->size() - 9, 0));
$this->assertSame(QRMatrix::M_VERSION, $this->matrix->get($this->matrix->size() - 11, 5));
$this->assertSame(QRMatrix::M_VERSION, $this->matrix->get(0, $this->matrix->size() - 9));
$this->assertSame(QRMatrix::M_VERSION, $this->matrix->get(5, $this->matrix->size() - 11));
}
public function testSetFormatInfo(){
$this->matrix->setFormatInfo(0, true);
$this->assertSame(QRMatrix::M_FORMAT, $this->matrix->get(8, 0));
$this->assertSame(QRMatrix::M_FORMAT, $this->matrix->get(0, 8));
$this->assertSame(QRMatrix::M_FORMAT, $this->matrix->get($this->matrix->size() - 1, 8));
$this->assertSame(QRMatrix::M_FORMAT, $this->matrix->get($this->matrix->size() - 8, 8));
}
public function testSetQuietZone(){
$size = $this->matrix->size();
$q = 5;
$this->matrix->set(0, 0, true, QRMatrix::M_TEST);
$this->matrix->set($size - 1, $size - 1, true, QRMatrix::M_TEST);
$this->matrix->setQuietZone($q);
$this->assertCount($size + 2 * $q, $this->matrix->matrix());
$this->assertCount($size + 2 * $q, $this->matrix->matrix()[$size - 1]);
$size = $this->matrix->size();
$this->assertSame(QRMatrix::M_QUIETZONE, $this->matrix->get(0, 0));
$this->assertSame(QRMatrix::M_QUIETZONE, $this->matrix->get($size - 1, $size - 1));
$this->assertSame(QRMatrix::M_TEST << 8, $this->matrix->get($q, $q));
$this->assertSame(QRMatrix::M_TEST << 8, $this->matrix->get($size - 1 - $q, $size - 1 - $q));
}
public function testSetQuietZoneException(){
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('use only after writing data');
$this->matrix->setQuietZone();
}
public function testSetLogoSpaceOrientation():void{
$o = new QROptions;
$o->version = 10;
$o->eccLevel = QRCode::ECC_H;
$o->addQuietzone = false;
$matrix = (new QRCode($o))->getMatrix('testdata');
// also testing size adjustment to uneven numbers
$matrix->setLogoSpace(20, 14);
// NW corner
$this::assertNotSame(QRMatrix::M_LOGO, $matrix->get(17, 20));
$this::assertSame(QRMatrix::M_LOGO, $matrix->get(18, 21));
// SE corner
$this::assertSame(QRMatrix::M_LOGO, $matrix->get(38, 35));
$this::assertNotSame(QRMatrix::M_LOGO, $matrix->get(39, 36));
}
public function testSetLogoSpacePosition():void{
$o = new QROptions;
$o->version = 10;
$o->eccLevel = QRCode::ECC_H;
$o->addQuietzone = true;
$o->quietzoneSize = 10;
$m = (new QRCode($o))->getMatrix('testdata');
// logo space should not overwrite quiet zone & function patterns
$m->setLogoSpace(21, 21, -10, -10);
$this::assertSame(QRMatrix::M_QUIETZONE, $m->get(9, 9));
$this::assertSame(QRMatrix::M_FINDER << 8, $m->get(10, 10));
$this::assertSame(QRMatrix::M_FINDER << 8, $m->get(16, 16));
$this::assertSame(QRMatrix::M_SEPARATOR, $m->get(17, 17));
$this::assertSame(QRMatrix::M_FORMAT << 8, $m->get(18, 18));
$this::assertSame(QRMatrix::M_LOGO, $m->get(19, 19));
$this::assertSame(QRMatrix::M_LOGO, $m->get(20, 20));
$this::assertNotSame(QRMatrix::M_LOGO, $m->get(21, 21));
// i just realized that setLogoSpace() could be called multiple times
// on the same instance and i'm not going to do anything about it :P
$m->setLogoSpace(21, 21, 45, 45);
$this::assertNotSame(QRMatrix::M_LOGO, $m->get(54, 54));
$this::assertSame(QRMatrix::M_LOGO, $m->get(55, 55));
$this::assertSame(QRMatrix::M_QUIETZONE, $m->get(67, 67));
}
public function testSetLogoSpaceInvalidEccException():void{
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('ECC level "H" required to add logo space');
(new QRCode)->getMatrix('testdata')->setLogoSpace(50, 50);
}
public function testSetLogoSpaceMaxSizeException():void{
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('logo space exceeds the maximum error correction capacity');
$o = new QROptions;
$o->version = 5;
$o->eccLevel = QRCode::ECC_H;
(new QRCode($o))->getMatrix('testdata')->setLogoSpace(50, 50);
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* Class BitBufferTest
*
* @filesource BitBufferTest.php
* @created 08.02.2016
* @package chillerlan\QRCodeTest\Helpers
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Helpers;
use chillerlan\QRCode\{QRCode, Helpers\BitBuffer};
use chillerlan\QRCodeTest\QRTestAbstract;
class BitBufferTest extends QRTestAbstract{
/**
* @var \chillerlan\QRCode\Helpers\BitBuffer
*/
protected $bitBuffer;
protected function setUp():void{
$this->bitBuffer = new BitBuffer;
}
public function bitProvider(){
return [
'number' => [QRCode::DATA_NUMBER, 16],
'alphanum' => [QRCode::DATA_ALPHANUM, 32],
'byte' => [QRCode::DATA_BYTE, 64],
'kanji' => [QRCode::DATA_KANJI, 128],
];
}
/**
* @dataProvider bitProvider
*/
public function testPut($data, $value){
$this->bitBuffer->put($data, 4);
$this->assertSame($value, $this->bitBuffer->buffer[0]);
$this->assertSame(4, $this->bitBuffer->length);
}
public function testClear(){
$this->bitBuffer->clear();
$this->assertSame([], $this->bitBuffer->buffer);
$this->assertSame(0, $this->bitBuffer->length);
}
}

View File

@ -0,0 +1,42 @@
<?php
/**
* Class PolynomialTest
*
* @filesource PolynomialTest.php
* @created 09.02.2016
* @package chillerlan\QRCodeTest\Helpers
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Helpers;
use chillerlan\QRCode\Helpers\Polynomial;
use chillerlan\QRCode\QRCodeException;
use chillerlan\QRCodeTest\QRTestAbstract;
class PolynomialTest extends QRTestAbstract{
/**
* @var \chillerlan\QRCode\Helpers\Polynomial
*/
protected $polynomial;
protected function setUp():void{
$this->polynomial = new Polynomial;
}
public function testGexp(){
$this->assertSame(142, $this->polynomial->gexp(-1));
$this->assertSame(133, $this->polynomial->gexp(128));
$this->assertSame(2, $this->polynomial->gexp(256));
}
public function testGlogException(){
$this->expectException(QRCodeException::class);
$this->expectExceptionMessage('log(0)');
$this->polynomial->glog(0);
}
}

View File

@ -0,0 +1,83 @@
<?php
/**
* Class QRFpdfTest
*
* @filesource QRFpdfTest.php
* @created 03.06.2020
* @package chillerlan\QRCodeTest\Output
* @author smiley <smiley@chillerlan.net>
* @copyright 2020 smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Output;
use FPDF;
use chillerlan\QRCode\Output\{QRFpdf, QROutputInterface};
use chillerlan\QRCode\{QRCode, QROptions};
use function class_exists, substr;
/**
* Tests the QRFpdf output module
*/
class QRFpdfTest extends QROutputTestAbstract{
protected $FQCN = QRFpdf::class;
/**
* @inheritDoc
* @internal
*/
public function setUp():void{
if(!class_exists(FPDF::class)){
$this->markTestSkipped('FPDF not available');
return;
}
parent::setUp();
}
/**
* @inheritDoc
*/
public function testSetModuleValues():void{
$this->options->moduleValues = [
// data
1024 => [0, 0, 0],
4 => [255, 255, 255],
];
$this->outputInterface->dump();
$this::assertTrue(true); // tricking the code coverage
}
/**
* @inheritDoc
*/
public function testRenderImage():void{
$type = QRCode::OUTPUT_FPDF;
$this->options->outputType = $type;
$this->options->imageBase64 = false;
$this->outputInterface->dump($this::cachefile.$type);
// substr() to avoid CreationDate
$expected = substr(file_get_contents($this::cachefile.$type), 0, 2000);
$actual = substr($this->outputInterface->dump(), 0, 2000);
$this::assertSame($expected, $actual);
}
public function testOutputGetResource():void{
$this->options->returnResource = true;
$this->setOutputInterface();
$this::assertInstanceOf(FPDF::class, $this->outputInterface->dump());
}
}

View File

@ -0,0 +1,77 @@
<?php
/**
* Class QRImageTest
*
* @filesource QRImageTest.php
* @created 24.12.2017
* @package chillerlan\QRCodeTest\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Output;
use chillerlan\QRCode\{QRCode, Output\QRImage};
use const PHP_MAJOR_VERSION;
class QRImageTest extends QROutputTestAbstract{
protected $FQCN = QRImage::class;
public function types(){
return [
'png' => [QRCode::OUTPUT_IMAGE_PNG],
'gif' => [QRCode::OUTPUT_IMAGE_GIF],
'jpg' => [QRCode::OUTPUT_IMAGE_JPG],
];
}
/**
* @dataProvider types
* @param $type
*/
public function testImageOutput($type){
$this->options->outputType = $type;
$this->options->imageBase64 = false;
$this->setOutputInterface();
$this->outputInterface->dump($this::cachefile.$type);
$img = $this->outputInterface->dump();
if($type === QRCode::OUTPUT_IMAGE_JPG){ // jpeg encoding may cause different results
$this->markAsRisky();
}
$this->assertSame($img, file_get_contents($this::cachefile.$type));
}
public function testSetModuleValues(){
$this->options->moduleValues = [
// data
1024 => [0, 0, 0],
4 => [255, 255, 255],
];
$this->setOutputInterface()->dump();
$this->assertTrue(true); // tricking the code coverage
}
public function testOutputGetResource():void{
$this->options->returnResource = true;
$this->setOutputInterface();
$data = $this->outputInterface->dump();
if(PHP_MAJOR_VERSION >= 8){
$this::assertInstanceOf('\\GdImage', $data);
}
else{
$this::assertIsResource($data);
}
}
}

View File

@ -0,0 +1,66 @@
<?php
/**
* Class QRImagickTest
*
* @filesource QRImagickTest.php
* @created 04.07.2018
* @package chillerlan\QRCodeTest\Output
* @author smiley <smiley@chillerlan.net>
* @copyright 2018 smiley
* @license MIT
*
* @noinspection PhpComposerExtensionStubsInspection
*/
namespace chillerlan\QRCodeTest\Output;
use Imagick;
use chillerlan\QRCode\{QRCode, Output\QRImagick};
class QRImagickTest extends QROutputTestAbstract{
protected $FQCN = QRImagick::class;
public function setUp():void{
if(!extension_loaded('imagick')){
$this->markTestSkipped('ext-imagick not loaded');
return;
}
parent::setUp();
}
public function testImageOutput(){
$type = QRCode::OUTPUT_IMAGICK;
$this->options->outputType = $type;
$this->setOutputInterface();
$this->outputInterface->dump($this::cachefile.$type);
$img = $this->outputInterface->dump();
$this->assertSame($img, file_get_contents($this::cachefile.$type));
}
public function testSetModuleValues(){
$this->options->moduleValues = [
// data
1024 => '#4A6000',
4 => '#ECF9BE',
];
$this->setOutputInterface()->dump();
$this->assertTrue(true); // tricking the code coverage
}
public function testOutputGetResource():void{
$this->options->returnResource = true;
$this->setOutputInterface();
$this::assertInstanceOf(Imagick::class, $this->outputInterface->dump());
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* Class QRMarkupTest
*
* @filesource QRMarkupTest.php
* @created 24.12.2017
* @package chillerlan\QRCodeTest\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Output;
use chillerlan\QRCode\{QRCode, Output\QRMarkup};
class QRMarkupTest extends QROutputTestAbstract{
protected $FQCN = QRMarkup::class;
public function types(){
return [
'html' => [QRCode::OUTPUT_MARKUP_HTML],
'svg' => [QRCode::OUTPUT_MARKUP_SVG],
];
}
/**
* @dataProvider types
* @param $type
*/
public function testMarkupOutputFile($type){
$this->options->outputType = $type;
$this->options->cachefile = $this::cachefile.$type;
$this->setOutputInterface();
$data = $this->outputInterface->dump();
$this->assertSame($data, file_get_contents($this->options->cachefile));
}
/**
* @dataProvider types
* @param $type
*/
public function testMarkupOutput($type){
$this->options->imageBase64 = false;
$this->options->outputType = $type;
$this->setOutputInterface();
$expected = explode($this->options->eol, file_get_contents($this::cachefile.$type));
// cut off the doctype & head
array_shift($expected);
if($type === QRCode::OUTPUT_MARKUP_HTML){
// cut off the </body> tag
array_pop($expected);
}
$expected = implode($this->options->eol, $expected);
$this->assertSame(trim($expected), trim($this->outputInterface->dump()));
}
public function testSetModuleValues(){
$this->options->imageBase64 = false;
$this->options->moduleValues = [
// data
1024 => '#4A6000',
4 => '#ECF9BE',
];
$this->setOutputInterface();
$data = $this->outputInterface->dump();
$this->assertStringContainsString('#4A6000', $data);
$this->assertStringContainsString('#ECF9BE', $data);
}
}

View File

@ -0,0 +1,71 @@
<?php
/**
* Class QROutputTestAbstract
*
* @filesource QROutputTestAbstract.php
* @created 24.12.2017
* @package chillerlan\QRCodeTest\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Output;
use chillerlan\QRCode\QROptions;
use chillerlan\QRCode\Data\Byte;
use chillerlan\QRCode\Output\{QRCodeOutputException, QROutputInterface};
use chillerlan\QRCodeTest\QRTestAbstract;
use function dirname, file_exists, mkdir;
abstract class QROutputTestAbstract extends QRTestAbstract{
const cachefile = __DIR__.'/../../.build/output_test/test.';
/**
* @var \chillerlan\QRCode\Output\QROutputInterface
*/
protected $outputInterface;
/**
* @var \chillerlan\QRCode\QROptions
*/
protected $options;
/**
* @var \chillerlan\QRCode\Data\QRMatrix
*/
protected $matrix;
protected function setUp():void{
parent::setUp();
$buildDir = dirname($this::cachefile);
if(!file_exists($buildDir)){
mkdir($buildDir, 0777, true);
}
$this->options = new QROptions;
$this->setOutputInterface();
}
protected function setOutputInterface(){
$this->outputInterface = $this->reflection->newInstanceArgs([$this->options, (new Byte($this->options, 'testdata'))->initMatrix(0)]);
return $this->outputInterface;
}
public function testInstance(){
$this->assertInstanceOf(QROutputInterface::class, $this->outputInterface);
}
public function testSaveException(){
$this->expectException(QRCodeOutputException::class);
$this->expectExceptionMessage('Could not write data to cache file: /foo');
$this->options->cachefile = '/foo';
$this->setOutputInterface();
$this->outputInterface->dump();
}
}

View File

@ -0,0 +1,56 @@
<?php
/**
* Class QRStringTest
*
* @filesource QRStringTest.php
* @created 24.12.2017
* @package chillerlan\QRCodeTest\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Output;
use chillerlan\QRCode\{QRCode, Output\QRString};
class QRStringTest extends QROutputTestAbstract{
protected $FQCN = QRString::class;
public function types(){
return [
'json' => [QRCode::OUTPUT_STRING_JSON],
'text' => [QRCode::OUTPUT_STRING_TEXT],
];
}
/**
* @dataProvider types
* @param $type
*/
public function testStringOutput($type){
$this->options->outputType = $type;
$this->options->cachefile = $this::cachefile.$type;
$this->setOutputInterface();
$data = $this->outputInterface->dump();
$this->assertSame($data, file_get_contents($this->options->cachefile));
}
public function testSetModuleValues(){
$this->options->moduleValues = [
// data
1024 => 'A',
4 => 'B',
];
$this->setOutputInterface();
$data = $this->outputInterface->dump();
$this->assertStringContainsString('A', $data);
$this->assertStringContainsString('B', $data);
}
}

View File

@ -0,0 +1,140 @@
<?php
/**
* Class QRCodeTest
*
* @filesource QRCodeTest.php
* @created 17.11.2017
* @package chillerlan\QRCodeTest
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest;
use chillerlan\QRCode\{QROptions, QRCode};
use chillerlan\QRCode\Data\{AlphaNum, Byte, Number, QRCodeDataException};
use chillerlan\QRCode\Output\QRCodeOutputException;
use chillerlan\QRCodeExamples\MyCustomOutput;
use function random_bytes;
class QRCodeTest extends QRTestAbstract{
protected $FQCN = QRCode::class;
/**
* @var \chillerlan\QRCode\QRCode
*/
protected $qrcode;
protected function setUp():void{
parent::setUp();
$this->qrcode = $this->reflection->newInstance();
}
public function testIsNumber(){
$this->assertTrue($this->qrcode->isNumber('0123456789'));
$this->assertFalse($this->qrcode->isNumber('ABC'));
}
public function testIsAlphaNum(){
$this->assertTrue($this->qrcode->isAlphaNum('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:'));
$this->assertFalse($this->qrcode->isAlphaNum('abc'));
}
public function testIsKanji(){
$this->assertTrue($this->qrcode->isKanji('茗荷'));
$this->assertFalse($this->qrcode->isKanji('Ã'));
}
// coverage
public function typeDataProvider(){
return [
'png' => [QRCode::OUTPUT_IMAGE_PNG, 'data:image/png;base64,'],
'gif' => [QRCode::OUTPUT_IMAGE_GIF, 'data:image/gif;base64,'],
'jpg' => [QRCode::OUTPUT_IMAGE_JPG, 'data:image/jpg;base64,'],
'svg' => [QRCode::OUTPUT_MARKUP_SVG, 'data:image/svg+xml;base64,'],
'html' => [QRCode::OUTPUT_MARKUP_HTML, '<div><span style="background:'],
'text' => [QRCode::OUTPUT_STRING_TEXT, '⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕'.PHP_EOL],
'json' => [QRCode::OUTPUT_STRING_JSON, '[[18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18],'],
];
}
/**
* @dataProvider typeDataProvider
* @param $type
*/
public function testRenderImage($type, $expected){
$this->qrcode = $this->reflection->newInstanceArgs([new QROptions(['outputType' => $type])]);
$this->assertStringContainsString($expected, $this->qrcode->render('test'));
}
public function testInitDataInterfaceException(){
$this->expectException(QRCodeOutputException::class);
$this->expectExceptionMessage('invalid output type');
(new QRCode(new QROptions(['outputType' => 'foo'])))->render('test');
}
public function testGetMatrixException(){
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('QRCode::getMatrix() No data given.');
$this->qrcode->getMatrix('');
}
public function testTrim() {
$m1 = $this->qrcode->getMatrix('hello');
$m2 = $this->qrcode->getMatrix('hello '); // added space
$this->assertNotEquals($m1, $m2);
}
public function testImageTransparencyBGDefault(){
$this->qrcode = $this->reflection->newInstanceArgs([new QROptions(['imageTransparencyBG' => 'foo'])]);
$this->assertSame([255,255,255], $this->getProperty('options')->getValue($this->qrcode)->imageTransparencyBG);
}
public function testCustomOutput(){
$options = new QROptions([
'version' => 5,
'eccLevel' => QRCode::ECC_L,
'outputType' => QRCode::OUTPUT_CUSTOM,
'outputInterface' => MyCustomOutput::class,
]);
$expected = '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011111110111010000101111010000011111110000000010000010111000001101011000001010000010000000010111010101101011000001101011010111010000000010111010110100111110010100111010111010000000010111010000001101011000001101010111010000000010000010100111110010100111110010000010000000011111110101010101010101010101011111110000000000000000010010100111110010100000000000000000011001110000101111010000101111001011110000000000000000111010000101111010000111100010000000001011010100111110010100111110011001010000000010000101111101011000001101011110011110000000000011010100011000001101011000101110100000000011001100001001101011000001101010011010000000010110111110000001101011000001100110100000000010000100100010100111110010100001100100000000011111110111101111010000101111010100110000000011010000111010000101111010000111100100000000010101111111111110010100111110011001000000000010110001110101011000001101011110011010000000001001111100011000001101011000101110010000000011000100110001101011000001101010011100000000001000011001000001101011000001100110000000000011101001011010100111110010100001100000000000010111010001101111010000101111010100110000000011100000001010000101111010000111100000000000000001110110111110010100111110011001000000000000011001011101011000001101011110011100000000011111110101011000001101011001111110110000000000000000110001101011000001101000111100000000011111110001000001101011000011010110000000000010000010101010100111110010101000100100000000010111010111101111010000101111111100110000000010111010011010000101111010001101100010000000010111010000111110010100111100101101100000000010000010101101011000001101001100111100000000011111110101011000001101011000110010110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';
$this->assertSame($expected, $this->reflection->newInstanceArgs([$options])->render('test'));
}
public function testDataModeOverride(){
$this->qrcode = $this->reflection->newInstance();
$this->assertInstanceOf(Number::class, $this->qrcode->initDataInterface('123'));
$this->assertInstanceOf(AlphaNum::class, $this->qrcode->initDataInterface('ABC123'));
$this->assertInstanceOf(Byte::class, $this->qrcode->initDataInterface(random_bytes(32)));
$this->qrcode = $this->reflection->newInstanceArgs([new QROptions(['dataMode' => 'Byte'])]);
$this->assertInstanceOf(Byte::class, $this->qrcode->initDataInterface('123'));
$this->assertInstanceOf(Byte::class, $this->qrcode->initDataInterface('ABC123'));
$this->assertInstanceOf(Byte::class, $this->qrcode->initDataInterface(random_bytes(32)));
}
public function testDataModeOverrideError(){
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('illegal char:');
$this->qrcode = $this->reflection->newInstanceArgs([new QROptions(['dataMode' => 'AlphaNum'])]);
$this->qrcode->initDataInterface(random_bytes(32));
}
}

View File

@ -0,0 +1,80 @@
<?php
/**
* Class QROptionsTest
*
* @filesource QROptionsTest.php
* @created 08.11.2018
* @package chillerlan\QRCodeTest
* @author smiley <smiley@chillerlan.net>
* @copyright 2018 smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest;
use chillerlan\QRCode\{QRCode, QRCodeException, QROptions};
use PHPUnit\Framework\TestCase;
class QROptionsTest extends TestCase{
/**
* @var \chillerlan\QRCode\QROptions
*/
protected $options;
public function testVersionClamp(){
$this->assertSame(40, (new QROptions(['version' => 42]))->version);
$this->assertSame(1, (new QROptions(['version' => -42]))->version);
$this->assertSame(21, (new QROptions(['version' => 21]))->version);
$this->assertSame(QRCode::VERSION_AUTO, (new QROptions)->version); // QRCode::VERSION_AUTO = -1, default
}
public function testVersionMinMaxClamp(){
// normal clamp
$o = new QROptions(['versionMin' => 5, 'versionMax' => 10]);
$this->assertSame(5, $o->versionMin);
$this->assertSame(10, $o->versionMax);
// exceeding values
$o = new QROptions(['versionMin' => -42, 'versionMax' => 42]);
$this->assertSame(1, $o->versionMin);
$this->assertSame(40, $o->versionMax);
// min > max
$o = new QROptions(['versionMin' => 10, 'versionMax' => 5]);
$this->assertSame(5, $o->versionMin);
$this->assertSame(10, $o->versionMax);
$o = new QROptions(['versionMin' => 42, 'versionMax' => -42]);
$this->assertSame(1, $o->versionMin);
$this->assertSame(40, $o->versionMax);
}
public function testMaskPatternClamp(){
$this->assertSame(7, (new QROptions(['maskPattern' => 42]))->maskPattern);
$this->assertSame(0, (new QROptions(['maskPattern' => -42]))->maskPattern);
$this->assertSame(QRCode::MASK_PATTERN_AUTO, (new QROptions)->maskPattern); // QRCode::MASK_PATTERN_AUTO = -1, default
}
public function testInvalidEccLevelException(){
$this->expectException(QRCodeException::class);
$this->expectExceptionMessage('Invalid error correct level: 42');
new QROptions(['eccLevel' => 42]);
}
public function testClampRGBValues(){
$o = new QROptions(['imageTransparencyBG' => [-1, 0, 999]]);
$this->assertSame(0, $o->imageTransparencyBG[0]);
$this->assertSame(0, $o->imageTransparencyBG[1]);
$this->assertSame(255, $o->imageTransparencyBG[2]);
}
public function testInvalidRGBValueException(){
$this->expectException(QRCodeException::class);
$this->expectExceptionMessage('Invalid RGB value.');
new QROptions(['imageTransparencyBG' => ['r', 'g', 'b']]);
}
}

View File

@ -0,0 +1,72 @@
<?php
/**
* Class QRTestAbstract
*
* @filesource QRTestAbstract.php
* @created 17.11.2017
* @package chillerlan\QRCodeTest
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest;
use PHPUnit\Framework\TestCase;
use ReflectionClass, ReflectionMethod, ReflectionProperty;
abstract class QRTestAbstract extends TestCase{
/**
* @var \ReflectionClass
*/
protected $reflection;
/**
* @var string
*/
protected $FQCN;
protected function setUp():void{
$this->reflection = new ReflectionClass($this->FQCN);
}
/**
* @param string $method
*
* @return \ReflectionMethod
*/
protected function getMethod(string $method):ReflectionMethod {
$method = $this->reflection->getMethod($method);
$method->setAccessible(true);
return $method;
}
/**
* @param string $property
*
* @return \ReflectionProperty
*/
protected function getProperty(string $property):ReflectionProperty{
$property = $this->reflection->getProperty($property);
$property->setAccessible(true);
return $property;
}
/**
* @param $object
* @param string $property
* @param $value
*
* @return void
*/
protected function setProperty($object, string $property, $value){
$property = $this->getProperty($property);
$property->setAccessible(true);
$property->setValue($object, $value);
}
}

View File

@ -0,0 +1,51 @@
# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
# https://github.com/sebastianbergmann/phpunit/blob/master/.github/workflows/ci.yml
on:
push:
branches:
- v1.2.x
pull_request:
branches:
- v1.2.x
name: "CI"
jobs:
tests:
name: "Unit Tests"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
php-version:
- "7.2"
- "7.3"
- "7.4"
- "8.0"
- "8.1"
- "8.2"
- "8.3"
steps:
- name: "Checkout"
uses: actions/checkout@v3
- name: "Install PHP with extensions"
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
coverage: pcov
extensions: json
- name: "Install dependencies with composer"
uses: ramsey/composer-install@v2
- name: "Run tests with phpunit"
run: php vendor/phpunit/phpunit/phpunit --configuration=phpunit.xml

View File

@ -0,0 +1,4 @@
.idea
.vendor
composer.lock
*.phpunit.result.cache

View 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.

View File

@ -0,0 +1,162 @@
# chillerlan/php-settings-container
A container class for immutable settings objects. Not a DI container. PHP 7.2+
- [`SettingsContainerInterface`](https://github.com/chillerlan/php-settings-container/blob/master/src/SettingsContainerInterface.php) provides immutable properties with magic getter & setter and some fancy
[![version][packagist-badge]][packagist]
[![license][license-badge]][license]
[![Travis][travis-badge]][travis]
[![Coverage][coverage-badge]][coverage]
[![Scrunitizer][scrutinizer-badge]][scrutinizer]
[![Packagist downloads][downloads-badge]][downloads]
[![PayPal donate][donate-badge]][donate]
[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-settings-container.svg?style=flat-square
[packagist]: https://packagist.org/packages/chillerlan/php-settings-container
[license-badge]: https://img.shields.io/github/license/chillerlan/php-settings-container.svg?style=flat-square
[license]: https://github.com/chillerlan/php-settings-container/blob/master/LICENSE
[travis-badge]: https://img.shields.io/travis/chillerlan/php-settings-container.svg?style=flat-square
[travis]: https://travis-ci.org/chillerlan/php-settings-container
[coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-settings-container.svg?style=flat-square
[coverage]: https://codecov.io/github/chillerlan/php-settings-container
[scrutinizer-badge]: https://img.shields.io/scrutinizer/g/chillerlan/php-settings-container.svg?style=flat-square
[scrutinizer]: https://scrutinizer-ci.com/g/chillerlan/php-settings-container
[downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-settings-container.svg?style=flat-square
[downloads]: https://packagist.org/packages/chillerlan/php-settings-container/stats
[donate-badge]: https://img.shields.io/badge/donate-paypal-ff33aa.svg?style=flat-square
[donate]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WLYUNAT9ZTJZ4
## Documentation
### Installation
**requires [composer](https://getcomposer.org)**
*composer.json* (note: replace `dev-master` with a version boundary)
```json
{
"require": {
"php": "^7.2",
"chillerlan/php-settings-container": "^1.0"
}
}
```
### Manual installation
Download the desired version of the package from [master](https://github.com/chillerlan/php-settings-container/archive/master.zip) or
[release](https://github.com/chillerlan/php-settings-container/releases) and extract the contents to your project folder. After that:
- run `composer install` to install the required dependencies and generate `/vendor/autoload.php`.
- if you use a custom autoloader, point the namespace `chillerlan\Settings` to the folder `src` of the package
Profit!
## Usage
The `SettingsContainerInterface` (wrapped in`SettingsContainerAbstract` ) provides plug-in functionality for immutable object properties and adds some fancy, like loading/saving JSON, arrays etc.
It takes an `iterable` as the only constructor argument and calls a method with the trait's name on invocation (`MyTrait::MyTrait()`) for each used trait.
### Simple usage
```php
class MyContainer extends SettingsContainerAbstract{
protected $foo;
protected $bar;
}
```
Typed properties in PHP 7.4+:
```php
class MyContainer extends SettingsContainerAbstract{
protected string $foo;
protected string $bar;
}
```
```php
// use it just like a \stdClass
$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
trait SomeOptions{
protected $foo;
protected $what;
// this method will be called in SettingsContainerAbstract::construct()
// after the properties have been set
protected function SomeOptions(){
// just some constructor stuff...
$this->foo = strtoupper($this->foo);
}
// this method will be called from __set() when property $what is set
protected function set_what(string $value){
$this->what = md5($value);
}
}
trait MoreOptions{
protected $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); // -> md5 hash of "some value"
```
### API
#### [`SettingsContainerAbstract`](https://github.com/chillerlan/php-settings-container/blob/master/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
## 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.

View File

@ -0,0 +1,40 @@
{
"name": "chillerlan/php-settings-container",
"description": "A container class for immutable settings objects. Not a DI container. PHP 7.2+",
"homepage": "https://github.com/chillerlan/php-settings-container",
"license": "MIT",
"type": "library",
"minimum-stability": "stable",
"keywords": [
"php7", "helper", "container", "settings"
],
"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": "^7.2 || ^8.0",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^8.5"
},
"autoload": {
"psr-4": {
"chillerlan\\Settings\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"chillerlan\\SettingsTest\\": "tests/",
"chillerlan\\SettingsExamples\\": "examples/"
}
}
}

View File

@ -0,0 +1,46 @@
<?php
/**
* @filesource advanced.php
* @created 28.08.2018
* @author smiley <smiley@chillerlan.net>
* @copyright 2018 smiley
* @license MIT
*/
namespace chillerlan\SettingsExamples;
use chillerlan\Settings\SettingsContainerAbstract;
require_once __DIR__.'/../vendor/autoload.php';
// from library #1
trait SomeOptions{
protected $foo;
// this method will be called in SettingsContainerAbstract::__construct() after the properties have been set
protected function SomeOptions(){
// just some constructor stuff...
$this->foo = strtoupper($this->foo);
}
}
// from library #2
trait MoreOptions{
protected $bar = 'whatever'; // provide default values
}
$commonOptions = [
// SomeOptions
'foo' => 'whatever',
// MoreOptions
'bar' => 'nothing',
];
// now plug the several library options together to a single object
/** @var \chillerlan\Settings\SettingsContainerInterface $container */
$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

View File

@ -0,0 +1,30 @@
<?php
/**
* @filesource simple.php
* @created 28.08.2018
* @author smiley <smiley@chillerlan.net>
* @copyright 2018 smiley
* @license MIT
*/
namespace chillerlan\SettingsExamples;
use chillerlan\Settings\SettingsContainerAbstract;
require_once __DIR__.'/../vendor/autoload.php';
class MyContainer extends SettingsContainerAbstract{
protected $foo;
protected $bar;
}
/** @var \chillerlan\Settings\SettingsContainerInterface $container */
$container = new MyContainer(['foo' => 'what']);
$container->bar = 'foo';
var_dump($container->toJSON()); // -> {"foo":"what","bar":"foo"}
// non-existing properties will be ignored:
$container->nope = 'what';
var_dump($container->nope); // -> NULL

View File

@ -0,0 +1,35 @@
<?xml version="1.0"?>
<ruleset name="codemasher/php-settings-container PMD ruleset"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
<description>codemasher/php-settings-container PMD ruleset</description>
<exclude-pattern>*/examples/*</exclude-pattern>
<exclude-pattern>*/tests/*</exclude-pattern>
<exclude-pattern>*/vendor/*</exclude-pattern>
<rule ref="rulesets/cleancode.xml">
<exclude name="BooleanArgumentFlag"/>
</rule>
<rule ref="rulesets/codesize.xml/CyclomaticComplexity">
<priority>1</priority>
<properties>
<property name="maximum" value="150" />
</properties>
</rule>
<rule ref="rulesets/controversial.xml">
<exclude name="CamelCaseMethodName"/>
<exclude name="CamelCasePropertyName"/>
<exclude name="CamelCaseParameterName"/>
<exclude name="CamelCaseVariableName"/>
</rule>
<rule ref="rulesets/design.xml">
</rule>
<rule ref="rulesets/naming.xml">
<exclude name="LongVariable"/>
<exclude name="ShortVariable"/>
</rule>
<rule ref="rulesets/unusedcode.xml">
<exclude name="UnusedFormalParameter"/>
</rule>
</ruleset>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
<testsuites>
<testsuite name="php-settings-container test suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@ -0,0 +1,173 @@
<?php
/**
* Class SettingsContainerAbstract
*
* @filesource SettingsContainerAbstract.php
* @created 28.08.2018
* @package chillerlan\Settings
* @author Smiley <smiley@chillerlan.net>
* @copyright 2018 Smiley
* @license MIT
*/
namespace chillerlan\Settings;
use Exception, ReflectionClass, ReflectionProperty;
use function call_user_func, call_user_func_array, get_object_vars, json_decode, json_encode, method_exists, property_exists;
abstract class SettingsContainerAbstract implements SettingsContainerInterface{
/**
* SettingsContainerAbstract constructor.
*
* @param iterable|null $properties
*/
public function __construct(iterable $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.)
*
* @return void
*/
protected function construct():void{
$traits = (new ReflectionClass($this))->getTraits();
foreach($traits as $trait){
$method = $trait->getShortName();
if(method_exists($this, $method)){
call_user_func([$this, $method]);
}
}
}
/**
* @inheritdoc
*/
public function __get(string $property){
if(property_exists($this, $property) && !$this->isPrivate($property)){
if(method_exists($this, 'get_'.$property)){
return call_user_func([$this, 'get_'.$property]);
}
return $this->{$property};
}
return null;
}
/**
* @inheritdoc
*/
public function __set(string $property, $value):void{
if(!property_exists($this, $property) || $this->isPrivate($property)){
return;
}
if(method_exists($this, 'set_'.$property)){
call_user_func_array([$this, 'set_'.$property], [$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
*
* @param string $property
*
* @return bool
*/
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{
return get_object_vars($this);
}
/**
* @inheritdoc
*/
public function fromIterable(iterable $properties):SettingsContainerInterface{
foreach($properties as $key => $value){
$this->__set($key, $value);
}
return $this;
}
/**
* @inheritdoc
*/
public function toJSON(int $jsonOptions = null):string{
return json_encode($this, $jsonOptions ?? 0);
}
/**
* @inheritdoc
*/
public function fromJSON(string $json):SettingsContainerInterface{
$data = json_decode($json, true); // as of PHP 7.3: JSON_THROW_ON_ERROR
if($data === false || $data === null){
throw new Exception('error while decoding JSON');
}
return $this->fromIterable($data);
}
/**
* @inheritdoc
*/
#[\ReturnTypeWillChange]
public function jsonSerialize(){
return $this->toArray();
}
}

View File

@ -0,0 +1,104 @@
<?php
/**
* Interface SettingsContainerInterface
*
* @filesource SettingsContainerInterface.php
* @created 28.08.2018
* @package chillerlan\Settings
* @author Smiley <smiley@chillerlan.net>
* @copyright 2018 Smiley
* @license MIT
*/
namespace chillerlan\Settings;
use JsonSerializable;
/**
* a generic container with magic getter and setter
*/
interface SettingsContainerInterface extends JsonSerializable{
/**
* Retrieve the value of $property
*
* @param string $property
*
* @return mixed
*/
public function __get(string $property);
/**
* Set $property to $value while avoiding private and non-existing properties
*
* @param string $property
* @param mixed $value
*
* @return void
*/
public function __set(string $property, $value):void;
/**
* Checks if $property is set (aka. not null), excluding private properties
*
* @param string $property
*
* @return bool
*/
public function __isset(string $property):bool;
/**
* Unsets $property while avoiding private and non-existing properties
*
* @param string $property
*
* @return void
*/
public function __unset(string $property):void;
/**
* @see SettingsContainerInterface::toJSON()
*
* @return string
*/
public function __toString():string;
/**
* Returns an array representation of the settings object
*
* @return array
*/
public function toArray():array;
/**
* Sets properties from a given iterable
*
* @param iterable $properties
*
* @return \chillerlan\Settings\SettingsContainerInterface
*/
public function fromIterable(iterable $properties):SettingsContainerInterface;
/**
* Returns a JSON representation of the settings object
* @see \json_encode()
*
* @param int|null $jsonOptions
*
* @return string
*/
public function toJSON(int $jsonOptions = null):string;
/**
* Sets properties from a given JSON string
*
* @param string $json
*
* @return \chillerlan\Settings\SettingsContainerInterface
*
* @throws \Exception
* @throws \JsonException
*/
public function fromJSON(string $json):SettingsContainerInterface;
}

View File

@ -0,0 +1,105 @@
<?php
/**
* Class ContainerTraitTest
*
* @filesource ContainerTraitTest.php
* @created 28.08.2018
* @package chillerlan\SettingsTest
* @author Smiley <smiley@chillerlan.net>
* @copyright 2018 Smiley
* @license MIT
*/
namespace chillerlan\SettingsTest;
use PHPUnit\Framework\TestCase;
use Exception, TypeError;
class ContainerTraitTest extends TestCase{
public function testConstruct(){
$container = new TestContainer([
'test1' => 'test1',
'test2' => 'test2',
'test3' => 'test3',
'test4' => 'test4',
]);
$this->assertSame('test1', $container->test1);
$this->assertSame('test2', $container->test2);
$this->assertNull($container->test3);
$this->assertSame('test4', $container->test4);
$this->assertSame('success', $container->testConstruct);
}
public function testGet(){
$container = new TestContainer;
$this->assertSame('foo', $container->test1);
$this->assertNull($container->test2);
$this->assertNull($container->test3);
$this->assertNull($container->test4);
$this->assertNull($container->foo);
// isset test
$this->assertTrue(isset($container->test1));
$this->assertFalse(isset($container->test2));
$this->assertFalse(isset($container->test3));
$this->assertFalse(isset($container->test4));
$this->assertFalse(isset($container->foo));
// custom getter
$container->test6 = 'foo';
$this->assertSame(sha1('foo'), $container->test6);
// nullable/isset test
$container->test6 = null;
$this->assertFalse(isset($container->test6));
$this->assertSame('null', $container->test6);
}
public function testSet(){
$container = new TestContainer;
$container->test1 = 'bar';
$container->test2 = 'what';
$container->test3 = 'nope';
$this->assertSame('bar', $container->test1);
$this->assertSame('what', $container->test2);
$this->assertNull($container->test3);
// unset
unset($container->test1);
$this->assertFalse(isset($container->test1));
// custom setter
$container->test5 = 'bar';
$this->assertSame('bar_test5', $container->test5);
}
public function testToArray(){
$container = new TestContainer(['test1' => 'no', 'test2' => true, 'testConstruct' => 'success']);
$this->assertSame(['test1' => 'no', 'test2' => true, 'testConstruct' => 'success', 'test4' => null, 'test5' => null, 'test6' => null], $container->toArray());
}
public function testToJSON(){
$container = (new TestContainer)->fromJSON('{"test1":"no","test2":true,"testConstruct":"success"}');
$expected = '{"test1":"no","test2":true,"testConstruct":"success","test4":null,"test5":null,"test6":null}';
$this->assertSame($expected, $container->toJSON());
$this->assertSame($expected, (string)$container);
}
public function testFromJsonException(){
$this->expectException(Exception::class);
(new TestContainer)->fromJSON('-');
}
public function testFromJsonTypeError(){
$this->expectException(TypeError::class);
(new TestContainer)->fromJSON('2');
}
}

View File

@ -0,0 +1,29 @@
<?php
/**
* Class TestContainer
*
* @filesource TestContainer.php
* @created 28.08.2018
* @package chillerlan\SettingsTest
* @author Smiley <smiley@chillerlan.net>
* @copyright 2018 Smiley
* @license MIT
*/
namespace chillerlan\SettingsTest;
use chillerlan\Settings\SettingsContainerAbstract;
/**
* @property $test1
* @property $test2
* @property $test3
* @property $test4
* @property $test5
* @property $test6
*/
class TestContainer extends SettingsContainerAbstract{
use TestOptionsTrait;
private $test3 = 'what';
}

View File

@ -0,0 +1,42 @@
<?php
/**
* Trait TestOptionsTrait
*
* @filesource TestOptionsTrait.php
* @created 28.08.2018
* @package chillerlan\SettingsTest
* @author smiley <smiley@chillerlan.net>
* @copyright 2018 smiley
* @license MIT
*/
namespace chillerlan\SettingsTest;
trait TestOptionsTrait{
protected $test1 = 'foo';
protected $test2;
protected $testConstruct;
protected $test4;
protected $test5;
protected $test6;
protected function TestOptionsTrait(){
$this->testConstruct = 'success';
}
protected function set_test5($value){
$this->test5 = $value.'_test5';
}
protected function get_test6(){
return $this->test6 === null
? 'null'
: sha1($this->test6);
}
}

View File

@ -9,6 +9,8 @@ return array(
'think\\composer\\' => array($vendorDir . '/topthink/think-installer/src'),
'think\\' => array($baseDir . '/thinkphp/library/think'),
'clagiordano\\weblibs\\configmanager\\' => array($vendorDir . '/clagiordano/weblibs-configmanager/src'),
'chillerlan\\Settings\\' => array($vendorDir . '/chillerlan/php-settings-container/src'),
'chillerlan\\QRCode\\' => array($vendorDir . '/chillerlan/php-qrcode/src'),
'app\\' => array($baseDir . '/application'),
'ZipStream\\' => array($vendorDir . '/maennchen/zipstream-php/src'),
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),

View File

@ -31,6 +31,8 @@ class ComposerStaticInit2bc4f313dba415539e266f7ac2c87dcd
'c' =>
array (
'clagiordano\\weblibs\\configmanager\\' => 34,
'chillerlan\\Settings\\' => 20,
'chillerlan\\QRCode\\' => 18,
),
'a' =>
array (
@ -127,6 +129,14 @@ class ComposerStaticInit2bc4f313dba415539e266f7ac2c87dcd
array (
0 => __DIR__ . '/..' . '/clagiordano/weblibs-configmanager/src',
),
'chillerlan\\Settings\\' =>
array (
0 => __DIR__ . '/..' . '/chillerlan/php-settings-container/src',
),
'chillerlan\\QRCode\\' =>
array (
0 => __DIR__ . '/..' . '/chillerlan/php-qrcode/src',
),
'app\\' =>
array (
0 => __DIR__ . '/../..' . '/application',

View File

@ -200,6 +200,152 @@
},
"install-path": "../bacon/bacon-qr-code"
},
{
"name": "chillerlan/php-qrcode",
"version": "3.4.1",
"version_normalized": "3.4.1.0",
"source": {
"type": "git",
"url": "https://github.com/chillerlan/php-qrcode.git",
"reference": "468603b687a5fe75c1ff33857a45f1726c7b95a9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/468603b687a5fe75c1ff33857a45f1726c7b95a9",
"reference": "468603b687a5fe75c1ff33857a45f1726c7b95a9",
"shasum": ""
},
"require": {
"chillerlan/php-settings-container": "^1.2.2",
"ext-mbstring": "*",
"php": "^7.2 || ^8.0"
},
"require-dev": {
"phan/phan": "^3.2.2",
"phpunit/phpunit": "^8.5",
"setasign/fpdf": "^1.8.2"
},
"suggest": {
"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
"setasign/fpdf": "Required to use the QR FPDF output."
},
"time": "2021-09-03T17:54:45+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"chillerlan\\QRCode\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kazuhiko Arase",
"homepage": "https://github.com/kazuhikoarase"
},
{
"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. PHP 7.2+",
"homepage": "https://github.com/chillerlan/php-qrcode",
"keywords": [
"phpqrcode",
"qr",
"qr code",
"qrcode",
"qrcode-generator"
],
"support": {
"issues": "https://github.com/chillerlan/php-qrcode/issues",
"source": "https://github.com/chillerlan/php-qrcode/tree/3.4.1"
},
"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": "1.2.3",
"version_normalized": "1.2.3.0",
"source": {
"type": "git",
"url": "https://github.com/chillerlan/php-settings-container.git",
"reference": "92636df53ad1bc903521d29993de0631e07ca931"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/92636df53ad1bc903521d29993de0631e07ca931",
"reference": "92636df53ad1bc903521d29993de0631e07ca931",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": "^7.2 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^8.5"
},
"time": "2023-12-12T13:50:01+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. PHP 7.2+",
"homepage": "https://github.com/chillerlan/php-settings-container",
"keywords": [
"PHP7",
"Settings",
"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": "clagiordano/weblibs-configmanager",
"version": "v1.5.0",

View File

@ -3,7 +3,7 @@
'name' => 'topthink/think',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '54ded1dc34cc77383bcb8844d6b1b1949b5af4d5',
'reference' => '6942e1101ca09a1ef5aa4a441108ceddf774383f',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -37,6 +37,24 @@
'aliases' => array(),
'dev_requirement' => false,
),
'chillerlan/php-qrcode' => array(
'pretty_version' => '3.4.1',
'version' => '3.4.1.0',
'reference' => '468603b687a5fe75c1ff33857a45f1726c7b95a9',
'type' => 'library',
'install_path' => __DIR__ . '/../chillerlan/php-qrcode',
'aliases' => array(),
'dev_requirement' => false,
),
'chillerlan/php-settings-container' => array(
'pretty_version' => '1.2.3',
'version' => '1.2.3.0',
'reference' => '92636df53ad1bc903521d29993de0631e07ca931',
'type' => 'library',
'install_path' => __DIR__ . '/../chillerlan/php-settings-container',
'aliases' => array(),
'dev_requirement' => false,
),
'clagiordano/weblibs-configmanager' => array(
'pretty_version' => 'v1.5.0',
'version' => '1.5.0.0',
@ -490,7 +508,7 @@
'topthink/think' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '54ded1dc34cc77383bcb8844d6b1b1949b5af4d5',
'reference' => '6942e1101ca09a1ef5aa4a441108ceddf774383f',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),