Update php-qrcode and php-settings-container for PHP 8.1

By running the following command after updating composer.json

```
composer update chillerlan/php-qrcode chillerlan/php-settings-container
```

This change fixes a deprecation warning from Preferences ->
Personal data / Authentication -> Authenticator (OTP).

```
Return type of chillerlan\Settings\SettingsContainerAbstract::jsonSerialize() should either be compatible with JsonSerializable::jsonSerialize(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice
1. vendor/chillerlan/php-settings-container/src/SettingsContainerAbstract.php(19): ttrss_error_handler(Return type of chillerlan\Settings\SettingsContainerAbstract::jsonSerialize() should either be compatible with JsonSerializable::jsonSerialize(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice, vendor/chillerlan/php-settings-container/src/SettingsContainerAbstract.php)
2. vendor/composer/ClassLoader.php(571): include(/usr/share/webapps/tt-rss/vendor/chillerlan/php-settings-container/src/SettingsContainerAbstract.php)
3. vendor/composer/ClassLoader.php(428): Composer\Autoload\includeFile(/usr/share/webapps/tt-rss/vendor/composer/../chillerlan/php-settings-container/src/SettingsContainerAbstract.php)
4. vendor/chillerlan/php-qrcode/src/QROptions.php(59): loadClass(chillerlan\Settings\SettingsContainerAbstract)
5. vendor/composer/ClassLoader.php(571): include(/usr/share/webapps/tt-rss/vendor/chillerlan/php-qrcode/src/QROptions.php)
6. vendor/composer/ClassLoader.php(428): Composer\Autoload\includeFile(/usr/share/webapps/tt-rss/vendor/composer/../chillerlan/php-qrcode/src/QROptions.php)
7. vendor/chillerlan/php-qrcode/src/QRCode.php(113): loadClass(chillerlan\QRCode\QROptions)
8. classes/pref/prefs.php(958): __construct()
9. classes/pref/prefs.php(469): _get_otp_qrcode_img()
10. classes/pref/prefs.php(541): index_auth_2fa()
11. backend.php(136): index_auth()
```

The issue is fixed in php-settings-container 2.1.1 [1] Here I use the
latest php-qrcode version for another PHP 8.1 fix [2].

[1] 68bc5019c8 (diff-359c7f7a6d32d9935951e1b0742eb116fb654f4a932c8d40328bb5dcab2fa111L162)
[2] https://github.com/chillerlan/php-qrcode/issues/97
This commit is contained in:
Chih-Hsuan Yen 2022-07-02 22:01:51 +08:00 committed by Andrew Dolgov
parent d9861038bc
commit 4b61618920
No known key found for this signature in database
GPG Key ID: 1A56B4FA25D4AF2A
90 changed files with 8964 additions and 1910 deletions

View File

@ -1,7 +1,7 @@
{
"require": {
"spomky-labs/otphp": "^10.0",
"chillerlan/php-qrcode": "^3.3",
"chillerlan/php-qrcode": "^4.3.3",
"mervick/material-design-icons": "^2.2",
"j4mie/idiorm": "^1.5"
},

48
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "0c52fd6b8f33561f7c03f12df6f5480f",
"content-hash": "8bb918e9a6d0b833ebcb0b525885d36d",
"packages": [
{
"name": "beberlei/assert",
@ -75,26 +75,26 @@
},
{
"name": "chillerlan/php-qrcode",
"version": "3.4.1",
"version": "4.3.3",
"source": {
"type": "git",
"url": "https://github.com/chillerlan/php-qrcode.git",
"reference": "468603b687a5fe75c1ff33857a45f1726c7b95a9"
"reference": "6356b246948ac1025882b3f55e7c68ebd4515ae3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/468603b687a5fe75c1ff33857a45f1726c7b95a9",
"reference": "468603b687a5fe75c1ff33857a45f1726c7b95a9",
"url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/6356b246948ac1025882b3f55e7c68ebd4515ae3",
"reference": "6356b246948ac1025882b3f55e7c68ebd4515ae3",
"shasum": ""
},
"require": {
"chillerlan/php-settings-container": "^1.2.2",
"chillerlan/php-settings-container": "^2.1",
"ext-mbstring": "*",
"php": "^7.2 || ^8.0"
"php": "^7.4 || ^8.0"
},
"require-dev": {
"phan/phan": "^3.2.2",
"phpunit/phpunit": "^8.5",
"phan/phan": "^5.3",
"phpunit/phpunit": "^9.5",
"setasign/fpdf": "^1.8.2"
},
"suggest": {
@ -126,7 +126,7 @@
"homepage": "https://github.com/chillerlan/php-qrcode/graphs/contributors"
}
],
"description": "A QR code generator. PHP 7.2+",
"description": "A QR code generator. PHP 7.4+",
"homepage": "https://github.com/chillerlan/php-qrcode",
"keywords": [
"phpqrcode",
@ -135,6 +135,10 @@
"qrcode",
"qrcode-generator"
],
"support": {
"issues": "https://github.com/chillerlan/php-qrcode/issues",
"source": "https://github.com/chillerlan/php-qrcode/tree/4.3.3"
},
"funding": [
{
"url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4",
@ -145,28 +149,29 @@
"type": "ko_fi"
}
],
"time": "2021-09-03T17:54:45+00:00"
"time": "2021-11-25T22:38:09+00:00"
},
{
"name": "chillerlan/php-settings-container",
"version": "1.2.2",
"version": "2.1.3",
"source": {
"type": "git",
"url": "https://github.com/chillerlan/php-settings-container.git",
"reference": "d1b5284d6eb3a767459738bb0b20073f0cb3eeaf"
"reference": "125dd573b45ffc7cabecf385986a356ba2c6f602"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/d1b5284d6eb3a767459738bb0b20073f0cb3eeaf",
"reference": "d1b5284d6eb3a767459738bb0b20073f0cb3eeaf",
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/125dd573b45ffc7cabecf385986a356ba2c6f602",
"reference": "125dd573b45ffc7cabecf385986a356ba2c6f602",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": "^7.2 || ^8.0"
"php": "^7.4 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^8.4"
"phan/phan": "^5.3",
"phpunit/phpunit": "^9.5"
},
"type": "library",
"autoload": {
@ -185,14 +190,19 @@
"homepage": "https://github.com/codemasher"
}
],
"description": "A container class for immutable settings objects. Not a DI container. PHP 7.2+",
"description": "A container class for immutable settings objects. Not a DI container. PHP 7.4+",
"homepage": "https://github.com/chillerlan/php-settings-container",
"keywords": [
"PHP7",
"Settings",
"configuration",
"container",
"helper"
],
"support": {
"issues": "https://github.com/chillerlan/php-settings-container/issues",
"source": "https://github.com/chillerlan/php-settings-container"
},
"funding": [
{
"url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4",
@ -203,7 +213,7 @@
"type": "ko_fi"
}
],
"time": "2021-09-03T17:33:25+00:00"
"time": "2022-03-09T13:18:58+00:00"
},
{
"name": "j4mie/idiorm",

View File

@ -1 +1,2 @@
ko_fi: codemasher
custom: "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4"

View File

@ -2,8 +2,12 @@
# https://github.com/sebastianbergmann/phpunit/blob/master/.github/workflows/ci.yml
on:
- pull_request
- push
push:
branches:
- v4.3.x
pull_request:
branches:
- v4.3.x
name: "Continuous Integration"
@ -46,14 +50,17 @@ jobs:
matrix:
os:
- ubuntu-latest
# - windows-latest
- windows-latest
php-version:
- "7.2"
- "7.3"
- "7.4"
- "8.0"
- "8.1"
steps:
# - name: "Configure git to avoid issues with line endings"
# if: matrix.os == 'windows-latest'
# run: git config --global core.autocrlf false
- name: "Checkout"
uses: actions/checkout@v2
@ -69,7 +76,7 @@ jobs:
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
run: php vendor/bin/phpunit --configuration=phpunit.xml
- name: "Send code coverage report to Codecov.io"
uses: codecov/codecov-action@v1

View File

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

View File

@ -0,0 +1,55 @@
<?php
/**
* This configuration will be read and overlaid on top of the
* default configuration. Command-line arguments will be applied
* after this file is read.
*/
return [
// Supported values: `'5.6'`, `'7.0'`, `'7.1'`, `'7.2'`, `'7.3'`,
// `'7.4'`, `null`.
// If this is set to `null`,
// then Phan assumes the PHP version which is closest to the minor version
// of the php executable used to execute Phan.
//
// Note that the **only** effect of choosing `'5.6'` is to infer
// that functions removed in php 7.0 exist.
// (See `backward_compatibility_checks` for additional options)
'target_php_version' => '7.4',
// A list of directories that should be parsed for class and
// method information. After excluding the directories
// defined in exclude_analysis_directory_list, the remaining
// files will be statically analyzed for errors.
//
// Thus, both first-party and third-party code being used by
// your application should be included in this list.
'directory_list' => [
'examples',
'src',
'tests',
'vendor',
'.phan/stubs'
],
// A regex used to match every file name that you want to
// exclude from parsing. Actual value will exclude every
// "test", "tests", "Test" and "Tests" folders found in
// "vendor/" directory.
'exclude_file_regex' => '@^vendor/.*/(tests?|Tests?)/@',
// A directory list that defines files that will be excluded
// from static analysis, but whose class and method
// information should be included.
//
// Generally, you'll want to include the directories for
// third-party code (such as "vendor/") in this list.
//
// n.b.: If you'd like to parse but not analyze 3rd
// party code, directories containing that code
// should be added to both the `directory_list`
// and `exclude_analysis_directory_list` arrays.
'exclude_analysis_directory_list' => [
'vendor/',
'.phan/stubs'
],
];

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,16 @@
build:
nodes:
analysis:
tests:
override:
- php-scrutinizer-run
environment:
php: 8.0.0
filter:
excluded_paths:
- examples/*
- tests/*
- vendor/*
- .github/*
- .phan/*

View File

@ -1,24 +0,0 @@
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)

View File

@ -1,66 +1,72 @@
# 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),
A PHP 7.4+ 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.
[![PHP Version Support][php-badge]][php]
[![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]
[![Packagist downloads][downloads-badge]][downloads]<br/>
[![Continuous Integration][gh-action-badge]][gh-action]
[![Continuous Integration][gh-action-badge]][gh-action]
[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-qrcode.svg?style=flat-square
[php-badge]: https://img.shields.io/packagist/php-v/chillerlan/php-qrcode?logo=php&color=8892BF
[php]: https://www.php.net/supported-versions.php
[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-qrcode.svg
[packagist]: https://packagist.org/packages/chillerlan/php-qrcode
[license-badge]: https://img.shields.io/github/license/chillerlan/php-qrcode.svg?style=flat-square
[license-badge]: https://img.shields.io/github/license/chillerlan/php-qrcode.svg
[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-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-qrcode.svg?logo=codecov
[coverage]: https://codecov.io/github/chillerlan/php-qrcode
[scrutinizer-badge]: https://img.shields.io/scrutinizer/g/chillerlan/php-qrcode.svg?style=flat-square
[scrutinizer-badge]: https://img.shields.io/scrutinizer/g/chillerlan/php-qrcode.svg?logo=scrutinizer
[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-badge]: https://img.shields.io/packagist/dt/chillerlan/php-qrcode.svg
[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
[gh-action]: https://github.com/chillerlan/php-qrcode/actions?query=workflow%3A%22Continuous+Integration%22+branch%3Av4.3.x
## Documentation
See [the wiki](https://github.com/chillerlan/php-qrcode/wiki) for advanced documentation.
An API documentation created with [phpDocumentor](https://www.phpdoc.org/) can be found at https://chillerlan.github.io/php-qrcode/ (WIP).
### Requirements
- PHP 7.2+
- PHP 7.4+
- `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
- `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`)
*composer.json*
```json
{
"require": {
"php": "^7.2",
"chillerlan/php-qrcode": "^3.4"
"php": "^7.4",
"chillerlan/php-qrcode": "dev-main"
}
}
```
### Usage
Note: replace `dev-main` with a [version constraint](https://getcomposer.org/doc/articles/versions.md#writing-version-constraints), e.g. `^3.2` - see [releases](https://github.com/chillerlan/php-qrcode/releases) for valid versions.
For PHP version ...
- 7.4+ use `^4.3`
- 7.2+ use `^3.4.1` (PHP 7.2 is EOL and 7.3 soon! v3.4.1 also supports PHP8 - however, it's no longer supported)
- 7.0+ use `^2.0` (PHP 7.0 and 7.1 are EOL!)
- 5.6+ use `^1.0` (please let PHP 5 die!)
### Quickstart
We want to encode this URI for a mobile authenticator into a QRcode image:
```php
$data = 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net';
//quick and simple:
// quick and simple:
echo '<img src="'.(new QRCode)->render($data).'" alt="QR Code" />';
```
@ -69,310 +75,7 @@ echo '<img src="'.(new QRCode)->render($data).'" alt="QR Code" />';
<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
Wait, what was that? Please again, slower! See [Advanced usage](https://github.com/chillerlan/php-qrcode/wiki/Advanced-usage) on the wiki.
### Framework Integration
- Drupal [Google Authenticator Login `ga_login`](https://www.drupal.org/project/ga_login)
@ -381,12 +84,20 @@ http://www.denso-wave.com/qrcode/faqpatent-e.html
- 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)
- [twill](https://github.com/area17/twill)
- other uses: [dependents](https://github.com/chillerlan/php-qrcode/network/dependents) / [packages](https://github.com/chillerlan/php-qrcode/network/dependents?dependent_type=PACKAGE)
### Shameless advertising
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
### 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

View File

@ -1,6 +1,6 @@
{
"name": "chillerlan/php-qrcode",
"description": "A QR code generator. PHP 7.2+",
"description": "A QR code generator. PHP 7.4+",
"homepage": "https://github.com/chillerlan/php-qrcode",
"license": "MIT",
"minimum-stability": "stable",
@ -24,13 +24,13 @@
}
],
"require": {
"php": "^7.2 || ^8.0",
"php": "^7.4 || ^8.0",
"ext-mbstring": "*",
"chillerlan/php-settings-container": "^1.2.2"
"chillerlan/php-settings-container": "^2.1"
},
"require-dev": {
"phpunit/phpunit": "^8.5",
"phan/phan": "^3.2.2",
"phpunit/phpunit": "^9.5",
"phan/phan": "^5.3",
"setasign/fpdf": "^1.8.2"
},
"suggest": {
@ -48,5 +48,14 @@
"chillerlan\\QRCodeTest\\": "tests/",
"chillerlan\\QRCodeExamples\\": "examples/"
}
},
"scripts": {
"phpunit": "@php vendor/bin/phpunit",
"phan": "@php vendor/bin/phan"
},
"config": {
"lock": false,
"sort-packages": true,
"platform-check": true
}
}

View File

@ -28,6 +28,8 @@ class MyCustomOutput extends QROutputAbstract{
for($col = 0; $col < $this->moduleCount; $col++){
$output .= (int)$this->matrix->check($col, $row);
}
$output .= \PHP_EOL;
}
return $output;

View File

@ -19,6 +19,7 @@
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;
@ -31,29 +32,24 @@ class QRImageWithText extends QRImage{
* @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);
// set returnResource to true to skip further processing for now
$this->options->returnResource = true;
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]);
}
}
// there's no need to save the result of dump() into $this->image here
parent::dump($file);
// render text output if a string is given
if($text !== null){
$this->addText($text);
}
$imageData = $this->dumpImage($file);
$imageData = $this->dumpImage();
if((bool)$this->options->imageBase64){
if($file !== null){
$this->saveToFile($imageData, $file);
}
if($this->options->imageBase64){
$imageData = 'data:image/'.$this->options->outputType.';base64,'.base64_encode($imageData);
}
@ -80,7 +76,7 @@ class QRImageWithText extends QRImage{
$background = imagecolorallocate($this->image, ...$textBG);
// allow transparency
if((bool)$this->options->imageTransparent && in_array($this->options->outputType, $this::TRANSPARENCY_TYPES, true)){
if($this->options->imageTransparent && in_array($this->options->outputType, $this::TRANSPARENCY_TYPES, true)){
imagecolortransparent($this->image, $background);
}
@ -97,7 +93,7 @@ class QRImageWithText extends QRImage{
// 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);
imagechar($this->image, $textSize, (int)($i * $w + $x), $this->length, $chr, $fontColor);
}
}

View File

@ -17,15 +17,16 @@ require_once __DIR__.'/../vendor/autoload.php';
$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s';
$options = new QROptions([
'version' => 7,
'version' => 10,
'outputType' => QRCode::OUTPUT_IMAGE_PNG,
'eccLevel' => QRCode::ECC_L,
'eccLevel' => QRCode::ECC_H,
'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
5632 => [241, 28, 163], // finder dot, dark (true)
// alignment
2560 => [255, 0, 255],
10 => [255, 255, 255],
@ -33,7 +34,7 @@ $options = new QROptions([
3072 => [255, 0, 0],
12 => [255, 255, 255],
// format
3584 => [67, 191, 84],
3584 => [67, 99, 84],
14 => [255, 255, 255],
// version
4096 => [62, 174, 190],
@ -47,6 +48,8 @@ $options = new QROptions([
8 => [255, 255, 255],
// quietzone
18 => [255, 255, 255],
// logo (requires a call to QRMatrix::setLogoSpace())
20 => [255, 255, 255],
],
]);

View File

@ -23,8 +23,8 @@ $data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s';
*/
class LogoOptions extends QROptions{
// size in QR modules, multiply with QROptions::$scale for pixel size
protected $logoSpaceWidth;
protected $logoSpaceHeight;
protected int $logoSpaceWidth;
protected int $logoSpaceHeight;
}
$options = new LogoOptions;

View File

@ -20,6 +20,7 @@ $gzip = true;
$options = new QROptions([
'version' => 7,
'outputType' => QRCode::OUTPUT_MARKUP_SVG,
'imageBase64' => false,
'eccLevel' => QRCode::ECC_L,
'svgViewBoxSize' => 530,
'addQuietzone' => true,

View File

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

View File

@ -1,34 +0,0 @@
<?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

@ -1,23 +1,26 @@
<?xml version="1.0"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheResultFile=".build/phpunit.result.cache"
colors="true"
verbose="true"
>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</include>
<report>
<clover outputFile=".build/coverage/clover.xml"/>
<xml outputDirectory=".build/coverage/coverage-xml"/>
</report>
</coverage>
<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"/>
<junit outputFile=".build/logs/junit.xml"/>
</logging>
</phpunit>

View File

@ -14,22 +14,19 @@ namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\QRCode;
use function array_search, ord, sprintf;
use function ord, sprintf;
/**
* Alphanumeric mode: 0 to 9, A to Z, space, $ % * + - . / :
*
* ISO/IEC 18004:2000 Section 8.3.3
* ISO/IEC 18004:2000 Section 8.4.3
*/
class AlphaNum extends QRDataAbstract{
final class AlphaNum extends QRDataAbstract{
/**
* @inheritdoc
*/
protected $datamode = QRCode::DATA_ALPHANUM;
protected int $datamode = QRCode::DATA_ALPHANUM;
/**
* @inheritdoc
*/
protected $lengthBits = [9, 11, 13];
protected array $lengthBits = [9, 11, 13];
/**
* @inheritdoc
@ -47,19 +44,17 @@ class AlphaNum extends QRDataAbstract{
}
/**
* @param string $chr
* get the code for the given character
*
* @return int
* @throws \chillerlan\QRCode\Data\QRCodeDataException
* @throws \chillerlan\QRCode\Data\QRCodeDataException on an illegal character occurence
*/
protected function getCharCode(string $chr):int{
$i = array_search($chr, $this::ALPHANUM_CHAR_MAP);
if($i !== false){
return $i;
if(!isset($this::CHAR_MAP_ALPHANUM[$chr])){
throw new QRCodeDataException(sprintf('illegal char: "%s" [%d]', $chr, ord($chr)));
}
throw new QRCodeDataException(sprintf('illegal char: "%s" [%d]', $chr, ord($chr)));
return $this::CHAR_MAP_ALPHANUM[$chr];
}
}

View File

@ -18,18 +18,15 @@ use function ord;
/**
* Byte mode, ISO-8859-1 or UTF-8
*
* ISO/IEC 18004:2000 Section 8.3.4
* ISO/IEC 18004:2000 Section 8.4.4
*/
class Byte extends QRDataAbstract{
final class Byte extends QRDataAbstract{
/**
* @inheritdoc
*/
protected $datamode = QRCode::DATA_BYTE;
protected int $datamode = QRCode::DATA_BYTE;
/**
* @inheritdoc
*/
protected $lengthBits = [8, 16, 16];
protected array $lengthBits = [8, 16, 16];
/**
* @inheritdoc

View File

@ -18,18 +18,15 @@ use function mb_strlen, ord, sprintf, strlen;
/**
* Kanji mode: double-byte characters from the Shift JIS character set
*
* ISO/IEC 18004:2000 Section 8.3.5
* ISO/IEC 18004:2000 Section 8.4.5
*/
class Kanji extends QRDataAbstract{
final class Kanji extends QRDataAbstract{
/**
* @inheritdoc
*/
protected $datamode = QRCode::DATA_KANJI;
protected int $datamode = QRCode::DATA_KANJI;
/**
* @inheritdoc
*/
protected $lengthBits = [8, 10, 12];
protected array $lengthBits = [8, 10, 12];
/**
* @inheritdoc
@ -40,6 +37,8 @@ class Kanji extends QRDataAbstract{
/**
* @inheritdoc
*
* @throws \chillerlan\QRCode\Data\QRCodeDataException on an illegal character occurence
*/
protected function write(string $data):void{
$len = strlen($data);
@ -47,17 +46,17 @@ class Kanji extends QRDataAbstract{
for($i = 0; $i + 1 < $len; $i += 2){
$c = ((0xff & ord($data[$i])) << 8) | (0xff & ord($data[$i + 1]));
if(0x8140 <= $c && $c <= 0x9FFC){
if($c >= 0x8140 && $c <= 0x9FFC){
$c -= 0x8140;
}
elseif(0xE040 <= $c && $c <= 0xEBBF){
elseif($c >= 0xE040 && $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);
$this->bitBuffer->put(((($c >> 8) & 0xff) * 0xC0) + ($c & 0xff), 13);
}

View File

@ -8,41 +8,51 @@
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*
* @noinspection PhpUnused
*/
namespace chillerlan\QRCode\Data;
use function abs, call_user_func_array;
use function abs, array_search, call_user_func_array, min;
/**
* The sole purpose of this class is to receive a QRMatrix object and run the pattern tests on it.
* Receives a QRDataInterface object and runs the mask pattern tests on it.
*
* @link http://www.thonky.com/qr-code-tutorial/data-masking
* ISO/IEC 18004:2000 Section 8.8.2 - Evaluation of masking results
*
* @see http://www.thonky.com/qr-code-tutorial/data-masking
*/
class MaskPatternTester{
final class MaskPatternTester{
/**
* @var \chillerlan\QRCode\Data\QRMatrix
* The data interface that contains the data matrix to test
*/
protected $matrix;
protected QRDataInterface $dataInterface;
/**
* @var int
*/
protected $moduleCount;
/**
* Receives the matrix an sets the module count
* Receives the QRDataInterface
*
* @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();
public function __construct(QRDataInterface $dataInterface){
$this->dataInterface = $dataInterface;
}
/**
* shoves a QRMatrix through the MaskPatternTester to find the lowest penalty mask pattern
*
* @see \chillerlan\QRCode\Data\MaskPatternTester
*/
public function getBestMaskPattern():int{
$penalties = [];
for($pattern = 0; $pattern < 8; $pattern++){
$penalties[$pattern] = $this->testPattern($pattern);
}
return array_search(min($penalties), $penalties, true);
}
/**
@ -50,15 +60,13 @@ class MaskPatternTester{
*
* @see \chillerlan\QRCode\QROptions::$maskPattern
* @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern
* @see \chillerlan\QRCode\QRCode::getBestMaskPattern()
*
* @return int
*/
public function testPattern():int{
$penalty = 0;
public function testPattern(int $pattern):int{
$matrix = $this->dataInterface->initMatrix($pattern, true);
$penalty = 0;
for($level = 1; $level <= 4; $level++){
$penalty += call_user_func_array([$this, 'testLevel'.$level], [$this->matrix->matrix(true)]);
$penalty += call_user_func_array([$this, 'testLevel'.$level], [$matrix->matrix(true), $matrix->size()]);
}
return (int)$penalty;
@ -66,10 +74,8 @@ class MaskPatternTester{
/**
* Checks for each group of five or more same-colored modules in a row (or column)
*
* @return int
*/
protected function testLevel1(array $m):int{
protected function testLevel1(array $m, int $size):int{
$penalty = 0;
foreach($m as $y => $row){
@ -78,13 +84,13 @@ class MaskPatternTester{
for($ry = -1; $ry <= 1; $ry++){
if($y + $ry < 0 || $this->moduleCount <= $y + $ry){
if($y + $ry < 0 || $size <= $y + $ry){
continue;
}
for($rx = -1; $rx <= 1; $rx++){
if(($ry === 0 && $rx === 0) || (($x + $rx) < 0 || $this->moduleCount <= ($x + $rx))){
if(($ry === 0 && $rx === 0) || (($x + $rx) < 0 || $size <= ($x + $rx))){
continue;
}
@ -107,21 +113,19 @@ class MaskPatternTester{
/**
* Checks for each 2x2 area of same-colored modules in the matrix
*
* @return int
*/
protected function testLevel2(array $m):int{
protected function testLevel2(array $m, int $size):int{
$penalty = 0;
foreach($m as $y => $row){
if($y > ($this->moduleCount - 2)){
if($y > $size - 2){
break;
}
foreach($row as $x => $val){
if($x > ($this->moduleCount - 2)){
if($x > $size - 2){
break;
}
@ -140,17 +144,15 @@ class MaskPatternTester{
/**
* 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{
protected function testLevel3(array $m, int $size):int{
$penalties = 0;
foreach($m as $y => $row){
foreach($row as $x => $val){
if(
($x + 6) < $this->moduleCount
$x + 6 < $size
&& $val
&& !$m[$y][$x + 1]
&& $m[$y][$x + 2]
@ -163,7 +165,7 @@ class MaskPatternTester{
}
if(
($y + 6) < $this->moduleCount
$y + 6 < $size
&& $val
&& !$m[$y + 1][$x]
&& $m[$y + 2][$x]
@ -183,10 +185,8 @@ class MaskPatternTester{
/**
* 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{
protected function testLevel4(array $m, int $size):float{
$count = 0;
foreach($m as $y => $row){
@ -197,7 +197,7 @@ class MaskPatternTester{
}
}
return (abs(100 * $count / $this->moduleCount / $this->moduleCount - 50) / 5) * 10;
return (abs(100 * $count / $size / $size - 50) / 5) * 10;
}
}

View File

@ -4,7 +4,7 @@
*
* @filesource Number.php
* @created 26.11.2015
* @package QRCode
* @package chillerlan\QRCode\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
@ -14,22 +14,19 @@ namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\QRCode;
use function ord, sprintf, substr;
use function ord, sprintf, str_split, substr;
/**
* Numeric mode: decimal digits 0 through 9
* Numeric mode: decimal digits 0 to 9
*
* ISO/IEC 18004:2000 Section 8.3.2
* ISO/IEC 18004:2000 Section 8.4.2
*/
class Number extends QRDataAbstract{
final class Number extends QRDataAbstract{
/**
* @inheritdoc
*/
protected $datamode = QRCode::DATA_NUMBER;
protected int $datamode = QRCode::DATA_NUMBER;
/**
* @inheritdoc
*/
protected $lengthBits = [10, 12, 14];
protected array $lengthBits = [10, 12, 14];
/**
* @inheritdoc
@ -56,20 +53,18 @@ class Number extends QRDataAbstract{
}
/**
* @param string $string
* get the code for the given numeric string
*
* @return int
* @throws \chillerlan\QRCode\Data\QRCodeDataException
* @throws \chillerlan\QRCode\Data\QRCodeDataException on an illegal character occurence
*/
protected function parseInt(string $string):int{
$num = 0;
$len = strlen($string);
for($i = 0; $i < $len; $i++){
$c = ord($string[$i]);
foreach(str_split($string) as $chr){
$c = ord($chr);
if(!in_array($string[$i], $this::NUMBER_CHAR_MAP, true)){
throw new QRCodeDataException(sprintf('illegal char: "%s" [%d]', $string[$i], $c));
if(!isset($this::CHAR_MAP_NUMBER[$chr])){
throw new QRCodeDataException(sprintf('illegal char: "%s" [%d]', $chr, $c));
}
$c = $c - 48; // ord('0')

View File

@ -12,7 +12,7 @@
namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\{QRCode, QRCodeException};
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\Helpers\{BitBuffer, Polynomial};
use chillerlan\Settings\SettingsContainerInterface;
@ -25,68 +25,50 @@ abstract class QRDataAbstract implements QRDataInterface{
/**
* the string byte count
*
* @var int
*/
protected $strlen;
protected ?int $strlen = null;
/**
* the current data mode: Num, Alphanum, Kanji, Byte
*
* @var int
*/
protected $datamode;
protected int $datamode;
/**
* mode length bits for the version breakpoints 1-9, 10-26 and 27-40
*
* @var array
* ISO/IEC 18004:2000 Table 3 - Number of bits in Character Count Indicator
*/
protected $lengthBits = [0, 0, 0];
protected array $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;
protected int $version;
/**
* ECC temp data
*
* @var array
*/
protected $ecdata;
protected array $ecdata;
/**
* ECC temp data
*/
protected array $dcdata;
/**
* the options instance
*
* @var array
* @var \chillerlan\Settings\SettingsContainerInterface|\chillerlan\QRCode\QROptions
*/
protected $dcdata;
protected SettingsContainerInterface $options;
/**
* @var \chillerlan\QRCode\QROptions
* a BitBuffer instance
*/
protected $options;
/**
* @var \chillerlan\QRCode\Helpers\BitBuffer
*/
protected $bitBuffer;
protected BitBuffer $bitBuffer;
/**
* QRDataInterface constructor.
*
* @param \chillerlan\Settings\SettingsContainerInterface $options
* @param string|null $data
*/
public function __construct(SettingsContainerInterface $options, string $data = null){
$this->options = $options;
@ -110,10 +92,7 @@ abstract class QRDataAbstract implements QRDataInterface{
? $this->getMinimumVersion()
: $this->options->version;
$this->matrixdata = $this
->writeBitBuffer($data)
->maskECC()
;
$this->writeBitBuffer($data);
return $this;
}
@ -123,21 +102,14 @@ abstract class QRDataAbstract implements QRDataInterface{
*/
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)
->init($maskPattern, $test)
->mapData($this->maskECC(), $maskPattern)
;
}
/**
* returns the length bits for the version breakpoints 1-9, 10-26 and 27-40
*
* @return int
* @throws \chillerlan\QRCode\Data\QRCodeDataException
* @codeCoverageIgnore
*/
@ -154,10 +126,6 @@ abstract class QRDataAbstract implements QRDataInterface{
/**
* returns the byte count of the $data string
*
* @param string $data
*
* @return int
*/
protected function getLength(string $data):int{
return strlen($data);
@ -166,15 +134,17 @@ abstract class QRDataAbstract implements QRDataInterface{
/**
* 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
$dataMode = QRCode::DATA_MODES[$this->datamode];
$eccMode = QRCode::ECC_MODES[$this->options->eccLevel];
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]];
$maxlength = $this::MAX_LENGTH[$version][$dataMode][$eccMode];
if($this->strlen <= $maxlength){
return $version;
@ -188,81 +158,72 @@ abstract class QRDataAbstract implements QRDataInterface{
* 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
* @throws \chillerlan\QRCode\QRCodeException on data overflow
*/
protected function writeBitBuffer(string $data):QRDataInterface{
protected function writeBitBuffer(string $data):void{
$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
// overflow, likely caused due to invalid version setting
if($this->bitBuffer->getLength() > $MAX_BITS){
throw new QRCodeDataException(sprintf('code length overflow. (%d > %d bit)', $this->bitBuffer->getLength(), $MAX_BITS));
}
// end code.
if($this->bitBuffer->length + 4 <= $MAX_BITS){
// add terminator (ISO/IEC 18004:2000 Table 2)
if($this->bitBuffer->getLength() + 4 <= $MAX_BITS){
$this->bitBuffer->put(0, 4);
}
// padding
while($this->bitBuffer->length % 8 !== 0){
while($this->bitBuffer->getLength() % 8 !== 0){
$this->bitBuffer->putBit(false);
}
// padding
while(true){
if($this->bitBuffer->length >= $MAX_BITS){
if($this->bitBuffer->getLength() >= $MAX_BITS){
break;
}
$this->bitBuffer->put(0xEC, 8);
if($this->bitBuffer->length >= $MAX_BITS){
if($this->bitBuffer->getLength() >= $MAX_BITS){
break;
}
$this->bitBuffer->put(0x11, 8);
}
return $this;
}
/**
* ECC masking
*
* @link http://www.thonky.com/qr-code-tutorial/error-correction-coding
* ISO/IEC 18004:2000 Section 8.5 ff
*
* @return array
* @see http://www.thonky.com/qr-code-tutorial/error-correction-coding
*/
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->ecdata = array_fill(0, $rsCount, []);
$this->dcdata = $this->ecdata;
if($l2 > 0){
@ -274,6 +235,8 @@ abstract class QRDataAbstract implements QRDataInterface{
$maxEcCount = 0;
$offset = 0;
$bitBuffer = $this->bitBuffer->getBuffer();
foreach($rsBlocks as $key => $block){
[$rsBlockTotal, $dcCount] = $block;
@ -283,12 +246,12 @@ abstract class QRDataAbstract implements QRDataInterface{
$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];
$this->dcdata[$key][$a] = 0xff & $bitBuffer[$a + $offset];
}
[$num, $add] = $this->poly($key, $ecCount);
foreach($this->ecdata[$key] as $c => $_z){
foreach($this->ecdata[$key] as $c => $_){
$modIndex = $c + $add;
$this->ecdata[$key][$c] = $modIndex >= 0 ? $num[$modIndex] : 0;
}
@ -300,7 +263,7 @@ abstract class QRDataAbstract implements QRDataInterface{
$data = array_fill(0, $totalCodeCount, null);
$index = 0;
$mask = function($arr, $count) use (&$data, &$index, $rsCount){
$mask = function(array $arr, int $count) use (&$data, &$index, $rsCount):void{
for($x = 0; $x < $count; $x++){
for($y = 0; $y < $rsCount; $y++){
if($x < count($arr[$y])){
@ -318,10 +281,7 @@ abstract class QRDataAbstract implements QRDataInterface{
}
/**
* @param int $key
* @param int $count
*
* @return int[]
* helper method for the polynomial operations
*/
protected function poly(int $key, int $count):array{
$rsPoly = new Polynomial;

View File

@ -13,23 +13,38 @@
namespace chillerlan\QRCode\Data;
/**
*
* Specifies the methods reqired for the data modules (Number, Alphanum, Byte and Kanji)
* and holds version information in several constants
*/
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', ' ', '$', '%', '*',
'+', '-', '.', '/', ':',
/**
* @var int[]
*/
const CHAR_MAP_NUMBER = [
'0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9,
];
/**
* @link http://www.qrcode.com/en/about/version.html
* ISO/IEC 18004:2000 Table 5
*
* @var int[]
*/
const CHAR_MAP_ALPHANUM = [
'0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7,
'8' => 8, '9' => 9, 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 'E' => 14, 'F' => 15,
'G' => 16, 'H' => 17, 'I' => 18, 'J' => 19, 'K' => 20, 'L' => 21, 'M' => 22, 'N' => 23,
'O' => 24, 'P' => 25, 'Q' => 26, 'R' => 27, 'S' => 28, 'T' => 29, 'U' => 30, 'V' => 31,
'W' => 32, 'X' => 33, 'Y' => 34, 'Z' => 35, ' ' => 36, '$' => 37, '%' => 38, '*' => 39,
'+' => 40, '-' => 41, '.' => 42, '/' => 43, ':' => 44,
];
/**
* ISO/IEC 18004:2000 Tables 7-11 - Number of symbol characters and input data capacity for versions 1 to 40
*
* @see http://www.qrcode.com/en/about/version.html
*
* @var int [][][]
*/
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
@ -75,6 +90,11 @@ interface QRDataInterface{
40 => [[7089, 5596, 3993, 3057], [4296, 3391, 2420, 1852], [2953, 2331, 1663, 1273], [1817, 1435, 1024, 784]], // 177
];
/**
* ISO/IEC 18004:2000 Tables 7-11 - Number of symbol characters and input data capacity for versions 1 to 40
*
* @var int [][]
*/
const MAX_BITS = [
// version => [L, M, Q, H ]
1 => [ 152, 128, 104, 72],
@ -120,7 +140,9 @@ interface QRDataInterface{
];
/**
* @link http://www.thonky.com/qr-code-tutorial/error-correction-table
* @see http://www.thonky.com/qr-code-tutorial/error-correction-table
*
* @var int [][][]
*/
const RSBLOCKS = [
1 => [[ 1, 0, 26, 19], [ 1, 0, 26, 16], [ 1, 0, 26, 13], [ 1, 0, 26, 9]],
@ -167,20 +189,11 @@ interface QRDataInterface{
/**
* 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;

165
vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php vendored Normal file → Executable file
View File

@ -18,29 +18,46 @@ 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
* Holds a numerical representation of the final QR Code;
* maps the ECC coded binary data and applies the mask pattern
*
* @see http://www.thonky.com/qr-code-tutorial/format-version-information
*/
class QRMatrix{
final class QRMatrix{
/** @var int */
public const M_NULL = 0x00;
/** @var int */
public const M_DARKMODULE = 0x02;
/** @var int */
public const M_DATA = 0x04;
/** @var int */
public const M_FINDER = 0x06;
/** @var int */
public const M_SEPARATOR = 0x08;
/** @var int */
public const M_ALIGNMENT = 0x0a;
/** @var int */
public const M_TIMING = 0x0c;
/** @var int */
public const M_FORMAT = 0x0e;
/** @var int */
public const M_VERSION = 0x10;
/** @var int */
public const M_QUIETZONE = 0x12;
/** @var int */
public const M_LOGO = 0x14;
/** @var int */
public const M_FINDER_DOT = 0x16;
/** @var int */
public const M_TEST = 0xff;
/**
* @link http://www.thonky.com/qr-code-tutorial/alignment-pattern-locations
* ISO/IEC 18004:2000 Annex E, Table E.1 - Row/column coordinates of center module of Alignment Patterns
*
* version -> pattern
* version -> pattern
*
* @var int[][]
*/
protected const alignmentPattern = [
1 => [],
@ -86,9 +103,11 @@ class QRMatrix{
];
/**
* @link http://www.thonky.com/qr-code-tutorial/format-version-tables
* ISO/IEC 18004:2000 Annex D, Table D.1 - Version information bit stream for each version
*
* no version pattern for QR Codes < 7
*
* @var int[]
*/
protected const versionPattern = [
7 => 0b000111110010010100,
@ -127,7 +146,13 @@ class QRMatrix{
40 => 0b101000110001101001,
];
// ECC level -> mask pattern
/**
* ISO/IEC 18004:2000 Section 8.9 - Format Information
*
* ECC level -> mask pattern
*
* @var int[][]
*/
protected const formatPattern = [
[ // L
0b111011111000100,
@ -172,36 +197,35 @@ class QRMatrix{
];
/**
* @var int
* the current QR Code version number
*/
protected $version;
protected int $version;
/**
* @var int
* the current ECC level
*/
protected $eclevel;
protected int $eclevel;
/**
* @var int
* the used mask pattern, set via QRMatrix::mapData()
*/
protected $maskPattern = QRCode::MASK_PATTERN_AUTO;
protected int $maskPattern = QRCode::MASK_PATTERN_AUTO;
/**
* @var int
* the size (side length) of the matrix
*/
protected $moduleCount;
protected int $moduleCount;
/**
* @var mixed[]
* the actual matrix data array
*
* @var int[][]
*/
protected $matrix;
protected array $matrix;
/**
* QRMatrix constructor.
*
* @param int $version
* @param int $eclevel
*
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
public function __construct(int $version, int $eclevel){
@ -220,6 +244,21 @@ class QRMatrix{
$this->matrix = array_fill(0, $this->moduleCount, array_fill(0, $this->moduleCount, $this::M_NULL));
}
/**
* shortcut to initialize the matrix
*/
public function init(int $maskPattern, bool $test = null):QRMatrix{
return $this
->setFinderPattern()
->setSeparators()
->setAlignmentPattern()
->setTimingPattern()
->setVersionNumber($test)
->setFormatInfo($maskPattern, $test)
->setDarkModule()
;
}
/**
* Returns the data matrix, returns a pure boolean representation if $boolean is set to true
*
@ -245,21 +284,21 @@ class QRMatrix{
}
/**
* @return int
* Returns the current version number
*/
public function version():int{
return $this->version;
}
/**
* @return int
* Returns the current ECC level
*/
public function eccLevel():int{
return $this->eclevel;
}
/**
* @return int
* Returns the current mask pattern
*/
public function maskPattern():int{
return $this->maskPattern;
@ -269,8 +308,6 @@ class QRMatrix{
* 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;
@ -278,11 +315,6 @@ class QRMatrix{
/**
* 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];
@ -293,13 +325,6 @@ class QRMatrix{
*
* 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);
@ -315,21 +340,14 @@ class QRMatrix{
*
* 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;
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);
@ -340,7 +358,7 @@ class QRMatrix{
/**
* Draws the 7x7 finder patterns in the corners top left/right and bottom left
*
* @return \chillerlan\QRCode\Data\QRMatrix
* ISO/IEC 18004:2000 Section 7.3.2
*/
public function setFinderPattern():QRMatrix{
@ -375,7 +393,7 @@ class QRMatrix{
/**
* Draws the separator lines around the finder patterns
*
* @return \chillerlan\QRCode\Data\QRMatrix
* ISO/IEC 18004:2000 Section 7.3.3
*/
public function setSeparators():QRMatrix{
@ -405,7 +423,7 @@ class QRMatrix{
/**
* Draws the 5x5 alignment patterns
*
* @return \chillerlan\QRCode\Data\QRMatrix
* ISO/IEC 18004:2000 Section 7.3.5
*/
public function setAlignmentPattern():QRMatrix{
@ -435,7 +453,7 @@ class QRMatrix{
/**
* Draws the timing pattern (h/v checkered line between the finder patterns)
*
* @return \chillerlan\QRCode\Data\QRMatrix
* ISO/IEC 18004:2000 Section 7.3.4
*/
public function setTimingPattern():QRMatrix{
@ -457,9 +475,7 @@ class QRMatrix{
/**
* Draws the version information, 2x 3x6 pixel
*
* @param bool|null $test
*
* @return \chillerlan\QRCode\Data\QRMatrix
* ISO/IEC 18004:2000 Section 8.10
*/
public function setVersionNumber(bool $test = null):QRMatrix{
$bits = $this::versionPattern[$this->version] ?? false;
@ -483,10 +499,7 @@ class QRMatrix{
/**
* Draws the format info along the finder patterns
*
* @param int $maskPattern
* @param bool|null $test
*
* @return \chillerlan\QRCode\Data\QRMatrix
* ISO/IEC 18004:2000 Section 8.9
*/
public function setFormatInfo(int $maskPattern, bool $test = null):QRMatrix{
$bits = $this::formatPattern[QRCode::ECC_MODES[$this->eclevel]][$maskPattern] ?? 0;
@ -524,9 +537,8 @@ class QRMatrix{
/**
* Draws the "quiet zone" of $size around the matrix
*
* @param int|null $size
* ISO/IEC 18004:2000 Section 7.3.7
*
* @return \chillerlan\QRCode\Data\QRMatrix
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
public function setQuietZone(int $size = null):QRMatrix{
@ -574,18 +586,12 @@ class QRMatrix{
*
* @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){
if($this->eclevel !== QRCode::ECC_H){
throw new QRCodeDataException('ECC level "H" required to add logo space');
}
@ -635,7 +641,8 @@ class QRMatrix{
}
/**
* Maps the binary $data array from QRDataInterface::maskECC() on the matrix, using $maskPattern
* Maps the binary $data array from QRDataInterface::maskECC() on the matrix,
* masking the data using $maskPattern (ISO/IEC 18004:2000 Section 8.8)
*
* @see \chillerlan\QRCode\Data\QRDataAbstract::maskECC()
*
@ -647,10 +654,13 @@ class QRMatrix{
public function mapData(array $data, int $maskPattern):QRMatrix{
$this->maskPattern = $maskPattern;
$byteCount = count($data);
$size = $this->moduleCount - 1;
$y = $this->moduleCount - 1;
$inc = -1;
$byteIndex = 0;
$bitIndex = 7;
$mask = $this->getMask($this->maskPattern);
for($i = $size, $y = $size, $inc = -1, $byteIndex = 0, $bitIndex = 7; $i > 0; $i -= 2){
for($i = $y; $i > 0; $i -= 2){
if($i === 6){
$i--;
@ -707,9 +717,6 @@ class QRMatrix{
*
* @internal
*
* @param int $maskPattern
*
* @return \Closure
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
protected function getMask(int $maskPattern):Closure{
@ -719,14 +726,14 @@ class QRMatrix{
}
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; },
0b000 => fn($x, $y):int => ($x + $y) % 2,
0b001 => fn($x, $y):int => $y % 2,
0b010 => fn($x, $y):int => $x % 3,
0b011 => fn($x, $y):int => ($x + $y) % 3,
0b100 => fn($x, $y):int => ((int)($y / 2) + (int)($x / 3)) % 2,
0b101 => fn($x, $y):int => (($x * $y) % 2) + (($x * $y) % 3),
0b110 => fn($x, $y):int => ((($x * $y) % 2) + (($x * $y) % 3)) % 2,
0b111 => fn($x, $y):int => ((($x * $y) % 3) + (($x + $y) % 2)) % 2,
][$maskPattern];
}

View File

@ -14,20 +14,25 @@ namespace chillerlan\QRCode\Helpers;
use function count, floor;
class BitBuffer{
/**
* Holds the raw binary data
*/
final class BitBuffer{
/**
* @var int[]
* The buffer content
*
* @var int[]
*/
public $buffer = [];
protected array $buffer = [];
/**
* @var int
* Length of the content (bits)
*/
public $length = 0;
protected int $length = 0;
/**
* @return \chillerlan\QRCode\Helpers\BitBuffer
* clears the buffer
*/
public function clear():BitBuffer{
$this->buffer = [];
@ -37,10 +42,7 @@ class BitBuffer{
}
/**
* @param int $num
* @param int $length
*
* @return \chillerlan\QRCode\Helpers\BitBuffer
* appends a sequence of bits
*/
public function put(int $num, int $length):BitBuffer{
@ -52,9 +54,7 @@ class BitBuffer{
}
/**
* @param bool $bit
*
* @return \chillerlan\QRCode\Helpers\BitBuffer
* appends a single bit
*/
public function putBit(bool $bit):BitBuffer{
$bufIndex = floor($this->length / 8);
@ -72,4 +72,18 @@ class BitBuffer{
return $this;
}
/**
* returns the current buffer length
*/
public function getLength():int{
return $this->length;
}
/**
* returns the buffer content
*/
public function getBuffer():array{
return $this->buffer;
}
}

View File

@ -17,12 +17,14 @@ use chillerlan\QRCode\QRCodeException;
use function array_fill, count, sprintf;
/**
* @link http://www.thonky.com/qr-code-tutorial/error-correction-coding
* Polynomial long division helpers
*
* @see http://www.thonky.com/qr-code-tutorial/error-correction-coding
*/
class Polynomial{
final class Polynomial{
/**
* @link http://www.thonky.com/qr-code-tutorial/log-antilog-table
* @see 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],
@ -60,29 +62,26 @@ class Polynomial{
];
/**
* @var array
* @var int[]
*/
protected $num = [];
protected array $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[] $num
* @param int|null $shift
*
* @return \chillerlan\QRCode\Helpers\Polynomial
@ -105,7 +104,7 @@ class Polynomial{
}
/**
* @param array $e
* @param int[] $e
*
* @return \chillerlan\QRCode\Helpers\Polynomial
*/
@ -127,7 +126,7 @@ class Polynomial{
}
/**
* @param array $e
* @param int[] $e
*
* @return \chillerlan\QRCode\Helpers\Polynomial
*/
@ -150,9 +149,6 @@ class Polynomial{
}
/**
* @param int $n
*
* @return int
* @throws \chillerlan\QRCode\QRCodeException
*/
public function glog(int $n):int{
@ -165,9 +161,7 @@ class Polynomial{
}
/**
* @param int $n
*
* @return int
*/
public function gexp(int $n):int{

View File

@ -69,7 +69,7 @@ class QRFpdf extends QROutputAbstract{
* @return string|\FPDF
*/
public function dump(string $file = null){
$file = $file ?? $this->options->cachefile;
$file ??= $this->options->cachefile;
$fpdf = new FPDF('P', $this->options->fpdfMeasureUnit, [$this->length, $this->length]);
$fpdf->AddPage();
@ -83,6 +83,7 @@ class QRFpdf extends QROutputAbstract{
$color = $this->moduleValues[$M_TYPE];
if($prevColor === null || $prevColor !== $color){
/** @phan-suppress-next-line PhanParamTooFewUnpack */
$fpdf->SetFillColor(...$color);
$prevColor = $color;
}

View File

@ -19,30 +19,36 @@ use chillerlan\QRCode\{QRCode, QRCodeException};
use chillerlan\Settings\SettingsContainerInterface;
use Exception;
use function array_values, base64_encode, call_user_func, count, imagecolorallocate, imagecolortransparent,
use function array_values, base64_encode, call_user_func, count, extension_loaded, 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
* Converts the matrix into GD images, raw or base64 output (requires ext-gd)
*
* @see http://php.net/manual/book.image.php
*/
class QRImage extends QROutputAbstract{
/**
* GD image types that support transparency
*
* @var string[]
*/
protected const TRANSPARENCY_TYPES = [
QRCode::OUTPUT_IMAGE_PNG,
QRCode::OUTPUT_IMAGE_GIF,
];
/**
* @var string
*/
protected $defaultMode = QRCode::OUTPUT_IMAGE_PNG;
protected string $defaultMode = QRCode::OUTPUT_IMAGE_PNG;
/**
* The GD image resource
*
* @see imagecreatetruecolor()
* @var resource
* @var resource|\GdImage
*
* @phan-suppress PhanUndeclaredTypeProperty
*/
protected $image;
@ -84,15 +90,20 @@ class QRImage extends QROutputAbstract{
/**
* @inheritDoc
*
* @return string|resource
* @return string|resource|\GdImage
*
* @phan-suppress PhanUndeclaredTypeReturnType, PhanTypeMismatchReturn
*/
public function dump(string $file = null){
$file ??= $this->options->cachefile;
$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);
$tbg = $this->options->imageTransparencyBG;
/** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
$background = imagecolorallocate($this->image, ...$tbg);
if((bool)$this->options->imageTransparent && in_array($this->options->outputType, $this::TRANSPARENCY_TYPES, true)){
imagecolortransparent($this->image, $background);
@ -110,7 +121,11 @@ class QRImage extends QROutputAbstract{
return $this->image;
}
$imageData = $this->dumpImage($file);
$imageData = $this->dumpImage();
if($file !== null){
$this->saveToFile($imageData, $file);
}
if($this->options->imageBase64){
$imageData = sprintf('data:image/%s;base64,%s', $this->options->outputType, base64_encode($imageData));
@ -120,11 +135,7 @@ class QRImage extends QROutputAbstract{
}
/**
* @param int $x
* @param int $y
* @param array $rgb
*
* @return void
* Creates a single QR pixel with the given settings
*/
protected function setPixel(int $x, int $y, array $rgb):void{
imagefilledrectangle(
@ -133,20 +144,17 @@ class QRImage extends QROutputAbstract{
$y * $this->scale,
($x + 1) * $this->scale,
($y + 1) * $this->scale,
/** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
imagecolorallocate($this->image, ...$rgb)
);
}
/**
* @param string|null $file
* Creates the final image by calling the desired GD output function
*
* @return string
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
*/
protected function dumpImage(string $file = null):string{
$file = $file ?? $this->options->cachefile;
protected function dumpImage():string{
ob_start();
try{
@ -164,14 +172,12 @@ class QRImage extends QROutputAbstract{
ob_end_clean();
if($file !== null){
$this->saveToFile($imageData, $file);
}
return $imageData;
}
/**
* PNG output
*
* @return void
*/
protected function png():void{
@ -186,6 +192,7 @@ class QRImage extends QROutputAbstract{
/**
* Jiff - like... JitHub!
*
* @return void
*/
protected function gif():void{
@ -193,6 +200,8 @@ class QRImage extends QROutputAbstract{
}
/**
* JPG output
*
* @return void
*/
protected function jpg():void{

View File

@ -19,24 +19,20 @@ use chillerlan\QRCode\QRCodeException;
use chillerlan\Settings\SettingsContainerInterface;
use Imagick, ImagickDraw, ImagickPixel;
use function is_string;
use function extension_loaded, is_string;
/**
* ImageMagick output module
* requires ext-imagick
* @link http://php.net/manual/book.imagick.php
* @link http://phpimagick.com
* ImageMagick output module (requires ext-imagick)
*
* @see http://php.net/manual/book.imagick.php
* @see http://phpimagick.com
*/
class QRImagick extends QROutputAbstract{
/**
* @var \Imagick
*/
protected $imagick;
protected Imagick $imagick;
/**
* @inheritDoc
* @throws \chillerlan\QRCode\QRCodeException
*/
public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
@ -72,7 +68,7 @@ class QRImagick extends QROutputAbstract{
* @return string|\Imagick
*/
public function dump(string $file = null){
$file = $file ?? $this->options->cachefile;
$file ??= $this->options->cachefile;
$this->imagick = new Imagick;
$this->imagick->newImage(
@ -98,7 +94,7 @@ class QRImagick extends QROutputAbstract{
}
/**
* @return void
* Creates the QR image via ImagickDraw
*/
protected function drawImage():void{
$draw = new ImagickDraw;

View File

@ -21,17 +21,13 @@ use function is_string, sprintf, strip_tags, trim;
*/
class QRMarkup extends QROutputAbstract{
/**
* @var string
*/
protected $defaultMode = QRCode::OUTPUT_MARKUP_SVG;
protected string $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">';
protected string $svgHeader = '<svg xmlns="http://www.w3.org/2000/svg" class="qr-svg %1$s" '.
'style="width: 100%%; height: auto;" viewBox="0 0 %2$d %2$d">';
/**
* @inheritDoc
@ -55,10 +51,15 @@ class QRMarkup extends QROutputAbstract{
}
/**
* @return string
* HTML output
*/
protected function html():string{
$html = '<div class="'.$this->options->cssClass.'">'.$this->options->eol;
protected function html(string $file = null):string{
$html = empty($this->options->cssClass)
? '<div>'
: '<div class="'.$this->options->cssClass.'">';
$html .= $this->options->eol;
foreach($this->matrix->matrix() as $row){
$html .= '<div>';
@ -72,19 +73,21 @@ class QRMarkup extends QROutputAbstract{
$html .= '</div>'.$this->options->eol;
if($this->options->cachefile){
return '<!DOCTYPE html><head><meta charset="UTF-8"></head><body>'.$this->options->eol.$html.'</body>';
if($file !== null){
return '<!DOCTYPE html>'.
'<head><meta charset="UTF-8"><title>QR Code</title></head>'.
'<body>'.$this->options->eol.$html.'</body>';
}
return $html;
}
/**
* @link https://github.com/codemasher/php-qrcode/pull/5
* SVG output
*
* @return string
* @see https://github.com/codemasher/php-qrcode/pull/5
*/
protected function svg():string{
protected function svg(string $file = null):string{
$matrix = $this->matrix->matrix();
$svg = sprintf($this->svgHeader, $this->options->cssClass, $this->options->svgViewBoxSize ?? $this->moduleCount)
@ -115,7 +118,9 @@ class QRMarkup extends QROutputAbstract{
}
if($count > 0){
$len = $count;
$len = $count;
$start ??= 0; // avoid type coercion in sprintf() - phan happy
$path .= sprintf('M%s %s h%s v1 h-%sZ ', $start, $y, $len, $len);
// reset count
@ -128,7 +133,10 @@ class QRMarkup extends QROutputAbstract{
}
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);
$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
);
}
}
@ -137,8 +145,9 @@ class QRMarkup extends QROutputAbstract{
$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($file !== null){
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){

View File

@ -15,7 +15,7 @@ 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;
use function call_user_func_array, dirname, file_put_contents, get_called_class, in_array, is_writable, sprintf;
/**
* common output abstract
@ -23,50 +23,53 @@ use function call_user_func, dirname, file_put_contents, get_called_class, in_ar
abstract class QROutputAbstract implements QROutputInterface{
/**
* @var int
* the current size of the QR matrix
*
* @see \chillerlan\QRCode\Data\QRMatrix::size()
*/
protected $moduleCount;
protected int $moduleCount;
/**
* @param \chillerlan\QRCode\Data\QRMatrix $matrix
* the current output mode
*
* @see \chillerlan\QRCode\QROptions::$outputType
*/
protected $matrix;
protected string $outputMode;
/**
* @var \chillerlan\QRCode\QROptions
* the default output mode of the current output module
*/
protected $options;
protected string $defaultMode;
/**
* @var string
* the current scaling for a QR pixel
*
* @see \chillerlan\QRCode\QROptions::$scale
*/
protected $outputMode;
protected int $scale;
/**
* @var string;
* the side length of the QR image (modules * scale)
*/
protected $defaultMode;
protected int $length;
/**
* @var int
* an (optional) array of color values for the several QR matrix parts
*/
protected $scale;
protected array $moduleValues;
/**
* @var int
* the (filled) data matrix object
*/
protected $length;
protected QRMatrix $matrix;
/**
* @var array
* @var \chillerlan\Settings\SettingsContainerInterface|\chillerlan\QRCode\QROptions
*/
protected $moduleValues;
protected SettingsContainerInterface $options;
/**
* QROutputAbstract constructor.
*
* @param \chillerlan\Settings\SettingsContainerInterface $options
* @param \chillerlan\QRCode\Data\QRMatrix $matrix
*/
public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
$this->options = $options;
@ -86,8 +89,6 @@ abstract class QROutputAbstract implements QROutputInterface{
/**
* Sets the initial module values (clean-up & defaults)
*
* @return void
*/
abstract protected function setModuleValues():void;
@ -97,10 +98,6 @@ abstract class QROutputAbstract implements QROutputInterface{
* @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{
@ -116,9 +113,11 @@ abstract class QROutputAbstract implements QROutputInterface{
* @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;
$file ??= $this->options->cachefile;
// call the built-in output method with the optional file path as parameter
// to make the called method aware if a cache file was given
$data = call_user_func_array([$this, $this->outputMode ?? $this->defaultMode], [$file]);
if($file !== null){
$this->saveToFile($data, $file);

View File

@ -21,6 +21,7 @@ interface QROutputInterface{
const DEFAULT_MODULE_VALUES = [
// light
QRMatrix::M_NULL => false, // 0
QRMatrix::M_DATA => false, // 4
QRMatrix::M_FINDER => false, // 6
QRMatrix::M_SEPARATOR => false, // 8
@ -46,8 +47,6 @@ interface QROutputInterface{
/**
* 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

@ -8,6 +8,9 @@
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*
* @noinspection PhpUnusedParameterInspection
* @noinspection PhpComposerExtensionStubsInspection
*/
namespace chillerlan\QRCode\Output;
@ -21,10 +24,7 @@ use function implode, is_string, json_encode;
*/
class QRString extends QROutputAbstract{
/**
* @var string
*/
protected $defaultMode = QRCode::OUTPUT_STRING_TEXT;
protected string $defaultMode = QRCode::OUTPUT_STRING_TEXT;
/**
* @inheritDoc
@ -48,9 +48,9 @@ class QRString extends QROutputAbstract{
}
/**
* @return string
* string output
*/
protected function text():string{
protected function text(string $file = null):string{
$str = [];
foreach($this->matrix->matrix() as $row){
@ -67,9 +67,9 @@ class QRString extends QROutputAbstract{
}
/**
* @return string
* JSON output
*/
protected function json():string{
protected function json(string $file = null):string{
return json_encode($this->matrix->matrix());
}

207
vendor/chillerlan/php-qrcode/src/QRCode.php vendored Normal file → Executable file
View File

@ -13,58 +13,49 @@
namespace chillerlan\QRCode;
use chillerlan\QRCode\Data\{
MaskPatternTester, QRCodeDataException, QRDataInterface, QRMatrix
AlphaNum, Byte, Kanji, MaskPatternTester, Number, 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;
use function call_user_func_array, class_exists, in_array, ord, strlen, strtolower, str_split;
/**
* 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/
* @see https://github.com/kazuhikoarase/qrcode-generator/tree/master/php
* @see http://www.qrcode.com/en/codes/model12.html
* @see https://www.swisseduc.ch/informatik/theoretische_informatik/qr_codes/docs/qr_standard.pdf
* @see https://en.wikipedia.org/wiki/QR_code
* @see 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';
/** @var int */
public const VERSION_AUTO = -1;
/** @var int */
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%.
// ISO/IEC 18004:2000 Table 2
/** @var int */
public const DATA_NUMBER = 0b0001;
/** @var int */
public const DATA_ALPHANUM = 0b0010;
/** @var int */
public const DATA_BYTE = 0b0100;
/** @var int */
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,
];
/**
* References to the keys of the following tables:
*
* @see \chillerlan\QRCode\Data\QRDataInterface::MAX_LENGTH
*
* @var int[]
*/
public const DATA_MODES = [
self::DATA_NUMBER => 0,
self::DATA_ALPHANUM => 1,
@ -72,6 +63,59 @@ class QRCode{
self::DATA_KANJI => 3,
];
// ISO/IEC 18004:2000 Tables 12, 25
/** @var int */
public const ECC_L = 0b01; // 7%.
/** @var int */
public const ECC_M = 0b00; // 15%.
/** @var int */
public const ECC_Q = 0b11; // 25%.
/** @var int */
public const ECC_H = 0b10; // 30%.
/**
* References to the keys of the following tables:
*
* @see \chillerlan\QRCode\Data\QRDataInterface::MAX_BITS
* @see \chillerlan\QRCode\Data\QRDataInterface::RSBLOCKS
* @see \chillerlan\QRCode\Data\QRMatrix::formatPattern
*
* @var int[]
*/
public const ECC_MODES = [
self::ECC_L => 0,
self::ECC_M => 1,
self::ECC_Q => 2,
self::ECC_H => 3,
];
/** @var string */
public const OUTPUT_MARKUP_HTML = 'html';
/** @var string */
public const OUTPUT_MARKUP_SVG = 'svg';
/** @var string */
public const OUTPUT_IMAGE_PNG = 'png';
/** @var string */
public const OUTPUT_IMAGE_JPG = 'jpg';
/** @var string */
public const OUTPUT_IMAGE_GIF = 'gif';
/** @var string */
public const OUTPUT_STRING_JSON = 'json';
/** @var string */
public const OUTPUT_STRING_TEXT = 'text';
/** @var string */
public const OUTPUT_IMAGICK = 'imagick';
/** @var string */
public const OUTPUT_FPDF = 'fpdf';
/** @var string */
public const OUTPUT_CUSTOM = 'custom';
/**
* Map of built-in output modules => capabilities
*
* @var string[][]
*/
public const OUTPUT_MODES = [
QRMarkup::class => [
self::OUTPUT_MARKUP_SVG,
@ -95,19 +139,33 @@ class QRCode{
];
/**
* @var \chillerlan\QRCode\QROptions|\chillerlan\Settings\SettingsContainerInterface
* Map of data mode => interface
*
* @var string[]
*/
protected $options;
protected const DATA_INTERFACES = [
'number' => Number::class,
'alphanum' => AlphaNum::class,
'kanji' => Kanji::class,
'byte' => Byte::class,
];
/**
* @var \chillerlan\QRCode\Data\QRDataInterface
* The settings container
*
* @var \chillerlan\QRCode\QROptions|\chillerlan\Settings\SettingsContainerInterface
*/
protected $dataInterface;
protected SettingsContainerInterface $options;
/**
* The selected data interface (Number, AlphaNum, Kanji, Byte)
*/
protected QRDataInterface $dataInterface;
/**
* QRCode constructor.
*
* @param \chillerlan\Settings\SettingsContainerInterface|null $options
* Sets the options instance, determines the current mb-encoding and sets it to UTF-8
*/
public function __construct(SettingsContainerInterface $options = null){
$this->options = $options ?? new QROptions;
@ -116,9 +174,6 @@ class QRCode{
/**
* 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){
@ -128,9 +183,6 @@ class QRCode{
/**
* 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{
@ -142,7 +194,7 @@ class QRCode{
$this->dataInterface = $this->initDataInterface($data);
$maskPattern = $this->options->maskPattern === $this::MASK_PATTERN_AUTO
? $this->getBestMaskPattern()
? (new MaskPatternTester($this->dataInterface))->getBestMaskPattern()
: $this->options->maskPattern;
$matrix = $this->dataInterface->initMatrix($maskPattern);
@ -154,49 +206,24 @@ class QRCode{
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;
$interface = $this::DATA_INTERFACES[strtolower($this->options->dataModeOverride)] ?? null;
return new $dataInterface($this->options, $data);
if($interface !== null){
return new $interface($this->options, $data);
}
foreach($dataModes as $mode){
$dataInterface = $dataNamespace.$mode;
foreach($this::DATA_INTERFACES as $mode => $dataInterface){
if(call_user_func_array([$this, 'is'.$mode], [$data]) && class_exists($dataInterface)){
if(call_user_func_array([$this, 'is'.$mode], [$data])){
return new $dataInterface($this->options, $data);
}
@ -208,14 +235,12 @@ class QRCode{
/**
* 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)){
/** @phan-suppress-next-line PhanTypeExpectedObjectOrClassName */
return new $this->options->outputInterface($this->options, $this->getMatrix($data));
}
@ -232,39 +257,25 @@ class QRCode{
/**
* 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);
return $this->checkString($string, QRDataInterface::CHAR_MAP_NUMBER);
}
/**
* 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);
return $this->checkString($string, QRDataInterface::CHAR_MAP_ALPHANUM);
}
/**
* 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)){
foreach(str_split($string) as $chr){
if(!isset($charmap[$chr])){
return false;
}
}
@ -274,10 +285,6 @@ class QRCode{
/**
* checks if a string qualifies as Kanji
*
* @param string $string
*
* @return bool
*/
public function isKanji(string $string):bool{
$i = 0;
@ -298,12 +305,8 @@ class QRCode{
/**
* a dummy
*
* @param $data
*
* @return bool
*/
protected function isByte(string $data):bool{
public function isByte(string $data):bool{
return !empty($data);
}

View File

@ -12,4 +12,9 @@
namespace chillerlan\QRCode;
class QRCodeException extends \Exception{}
use Exception;
/**
* An exception container
*/
class QRCodeException extends Exception{}

View File

@ -15,46 +15,39 @@ 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
* The QRCode settings container
*
* @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
* @property int $version
* @property int $versionMin
* @property int $versionMax
* @property int $eccLevel
* @property int $maskPattern
* @property bool $addQuietzone
* @property int $quietzoneSize
* @property string|null $dataModeOverride
* @property string $outputType
* @property string|null $outputInterface
* @property string|null $cachefile
* @property string $eol
* @property int $scale
* @property string $cssClass
* @property float $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|null $imagickBG
* @property string $fpdfMeasureUnit
* @property array|null $moduleValues
*/
class QROptions extends SettingsContainerAbstract{
use QROptionsTrait;

View File

@ -8,146 +8,125 @@
* @author smiley <smiley@chillerlan.net>
* @copyright 2018 smiley
* @license MIT
*
* @noinspection PhpUnused
*/
namespace chillerlan\QRCode;
use function array_values, count, in_array, is_array, is_numeric, max, min, sprintf, strtolower;
use function array_values, count, in_array, is_numeric, max, min, sprintf, strtolower;
/**
* The QRCode plug-in settings & setter functionality
*/
trait QROptionsTrait{
/**
* QR Code version number
*
* [1 ... 40] or QRCode::VERSION_AUTO
*
* @var int
* [1 ... 40] or QRCode::VERSION_AUTO
*/
protected $version = QRCode::VERSION_AUTO;
protected int $version = QRCode::VERSION_AUTO;
/**
* Minimum QR version (if $version = QRCode::VERSION_AUTO)
* Minimum QR version
*
* @var int
* if $version = QRCode::VERSION_AUTO
*/
protected $versionMin = 1;
protected int $versionMin = 1;
/**
* Maximum QR version
*
* @var int
*/
protected $versionMax = 40;
protected int $versionMax = 40;
/**
* Error correct level
*
* QRCode::ECC_X where X is
* L => 7%
* M => 15%
* Q => 25%
* H => 30%
* QRCode::ECC_X where X is:
*
* @var int
* - L => 7%
* - M => 15%
* - Q => 25%
* - H => 30%
*/
protected $eccLevel = QRCode::ECC_L;
protected int $eccLevel = QRCode::ECC_L;
/**
* Mask Pattern to use
*
* [0...7] or QRCode::MASK_PATTERN_AUTO
*
* @var int
* [0...7] or QRCode::MASK_PATTERN_AUTO
*/
protected $maskPattern = QRCode::MASK_PATTERN_AUTO;
protected int $maskPattern = QRCode::MASK_PATTERN_AUTO;
/**
* Add a "quiet zone" (margin) according to the QR code spec
*
* @var bool
*/
protected $addQuietzone = true;
protected bool $addQuietzone = true;
/**
* Size of the quiet zone
*
* internally clamped to [0 ... $moduleCount / 2], defaults to 4 modules
*
* @var int
* internally clamped to [0 ... $moduleCount / 2], defaults to 4 modules
*/
protected $quietzoneSize = 4;
protected int $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
*
* valid modes are: Number, AlphaNum, Kanji, Byte (case insensitive)
*
* @see https://github.com/chillerlan/php-qrcode/issues/39
*
* @var string|null
* @see https://github.com/chillerlan/php-qrcode/issues/97 (changed default value to '')
*/
protected $dataMode = null;
protected string $dataModeOverride = '';
/**
* 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
* The output type
*
* @var string
* - 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
*/
protected $outputType = QRCode::OUTPUT_IMAGE_PNG;
protected string $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;
protected ?string $outputInterface = null;
/**
* /path/to/cache.file
*
* @var string|null
*/
protected $cachefile = null;
protected ?string $cachefile = null;
/**
* newline string [HTML, SVG, TEXT]
*
* @var string
*/
protected $eol = PHP_EOL;
protected string $eol = PHP_EOL;
/**
* size of a QR code pixel [SVG, IMAGE_*]
* HTML -> via CSS
*
* @var int
* size of a QR code pixel [SVG, IMAGE_*], HTML via CSS
*/
protected $scale = 5;
protected int $scale = 5;
/**
* a common css class
*
* @var string
*/
protected $cssClass = '';
protected string $cssClass = '';
/**
* SVG opacity
*
* @var float
*/
protected $svgOpacity = 1.0;
protected float $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>';
protected string $svgDefs = '<style>rect{shape-rendering:crispEdges}</style>';
/**
* SVG viewBox size. a single integer number which defines width/height of the viewBox attribute.
@ -155,38 +134,28 @@ trait QROptionsTrait{
* viewBox="0 0 x x"
*
* @see https://css-tricks.com/scale-svg/#article-header-id-3
*
* @var int|null
*/
protected $svgViewBoxSize = null;
protected ?int $svgViewBoxSize = null;
/**
* string substitute for dark
*
* @var string
*/
protected $textDark = '🔴';
protected string $textDark = '🔴';
/**
* string substitute for light
*
* @var string
*/
protected $textLight = '⭕';
protected string $textLight = '⭕';
/**
* markup substitute for dark (CSS value)
*
* @var string
*/
protected $markupDark = '#000';
protected string $markupDark = '#000';
/**
* markup substitute for light (CSS value)
*
* @var string
*/
protected $markupLight = '#fff';
protected string $markupLight = '#fff';
/**
* Return the image resource instead of a render if applicable.
@ -194,7 +163,7 @@ trait QROptionsTrait{
*
* Supported by the following modules:
*
* - QRImage: resource
* - QRImage: resource (PHP < 8), GdImage
* - QRImagick: Imagick
* - QRFpdf: FPDF
*
@ -202,85 +171,66 @@ trait QROptionsTrait{
*
* @var bool
*/
protected $returnResource = false;
protected bool $returnResource = false;
/**
* toggle base64 or raw image data
*
* @var bool
*/
protected $imageBase64 = true;
protected bool $imageBase64 = true;
/**
* toggle transparency, not supported by jpg
*
* @var bool
*/
protected $imageTransparent = true;
protected bool $imageTransparent = true;
/**
* @see imagecolortransparent()
*
* @var array [R, G, B]
* [R, G, B]
*/
protected $imageTransparencyBG = [255, 255, 255];
protected array $imageTransparencyBG = [255, 255, 255];
/**
* @see imagepng()
*
* @var int
*/
protected $pngCompression = -1;
protected int $pngCompression = -1;
/**
* @see imagejpeg()
*
* @var int
*/
protected $jpegQuality = 85;
protected int $jpegQuality = 85;
/**
* Imagick output format
*
* @see Imagick::setType()
*
* @var string
* @see \Imagick::setType()
*/
protected $imagickFormat = 'png';
protected string $imagickFormat = 'png';
/**
* Imagick background color (defaults to "transparent")
*
* @see \ImagickPixel::__construct()
*
* @var string|null
*/
protected $imagickBG = null;
protected ?string $imagickBG = null;
/**
* Measurement unit for FPDF output: pt, mm, cm, in (defaults to "pt")
*
* @see \FPDF::__construct()
*/
protected $fpdfMeasureUnit = 'pt';
protected string $fpdfMeasureUnit = 'pt';
/**
* Module values map
*
* HTML, IMAGICK: #ABCDEF, cssname, rgb(), rgba()...
* IMAGE: [63, 127, 255] // R, G, B
*
* @var array|null
* - HTML, IMAGICK: #ABCDEF, cssname, rgb(), rgba()...
* - IMAGE: [63, 127, 255] // R, G, B
*/
protected $moduleValues = null;
protected ?array $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));
@ -292,10 +242,6 @@ trait QROptionsTrait{
/**
* sets the minimum version number
*
* @param int $version
*
* @return void
*/
protected function set_versionMin(int $version):void{
$this->setMinMaxVersion($version, $this->versionMax);
@ -303,10 +249,6 @@ trait QROptionsTrait{
/**
* sets the maximum version number
*
* @param int $version
*
* @return void
*/
protected function set_versionMax(int $version):void{
$this->setMinMaxVersion($this->versionMin, $version);
@ -315,9 +257,6 @@ trait QROptionsTrait{
/**
* sets the error correction level
*
* @param int $eccLevel
*
* @return void
* @throws \chillerlan\QRCode\QRCodeException
*/
protected function set_eccLevel(int $eccLevel):void{
@ -331,10 +270,6 @@ trait QROptionsTrait{
/**
* sets/clamps the mask pattern
*
* @param int $maskPattern
*
* @return void
*/
protected function set_maskPattern(int $maskPattern):void{
@ -347,15 +282,12 @@ trait QROptionsTrait{
/**
* sets the transparency background color
*
* @param mixed $imageTransparencyBG
*
* @return void
* @throws \chillerlan\QRCode\QRCodeException
*/
protected function set_imageTransparencyBG($imageTransparencyBG):void{
protected function set_imageTransparencyBG(array $imageTransparencyBG):void{
// invalid value - set to white as default
if(!is_array($imageTransparencyBG) || count($imageTransparencyBG) < 3){
if(count($imageTransparencyBG) < 3){
$this->imageTransparencyBG = [255, 255, 255];
return;
@ -363,6 +295,11 @@ trait QROptionsTrait{
foreach($imageTransparencyBG as $k => $v){
// cut off exceeding items
if($k > 2){
break;
}
if(!is_numeric($v)){
throw new QRCodeException('Invalid RGB value.');
}
@ -377,10 +314,6 @@ trait QROptionsTrait{
/**
* sets/clamps the version number
*
* @param int $version
*
* @return void
*/
protected function set_version(int $version):void{

View File

@ -12,13 +12,19 @@
namespace chillerlan\QRCodeTest\Data;
use chillerlan\QRCode\Data\{AlphaNum, QRCodeDataException};
use chillerlan\QRCode\Data\{AlphaNum, QRCodeDataException, QRDataInterface};
use chillerlan\QRCode\QROptions;
class AlphaNumTest extends DatainterfaceTestAbstract{
/**
* Tests the AlphaNum class
*/
final class AlphaNumTest extends DatainterfaceTestAbstract{
protected $FQCN = AlphaNum::class;
protected $testdata = '0 $%*+-./:';
protected $expected = [
/** @internal */
protected string $testdata = '0 $%*+-./:';
/** @internal */
protected array $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,
@ -34,7 +40,18 @@ class AlphaNumTest extends DatainterfaceTestAbstract{
92, 112, 20, 198, 27
];
public function testGetCharCodeException(){
/**
* @inheritDoc
* @internal
*/
protected function getDataInterfaceInstance(QROptions $options):QRDataInterface{
return new AlphaNum($options);
}
/**
* Tests if an exception is thrown when an invalid character is encountered
*/
public function testGetCharCodeException():void{
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('illegal char: "#" [35]');

View File

@ -13,12 +13,19 @@
namespace chillerlan\QRCodeTest\Data;
use chillerlan\QRCode\Data\Byte;
use chillerlan\QRCode\Data\QRDataInterface;
use chillerlan\QRCode\QROptions;
class ByteTest extends DatainterfaceTestAbstract{
/**
* Tests the Byte class
*/
final class ByteTest extends DatainterfaceTestAbstract{
protected $FQCN = Byte::class;
protected $testdata = '[¯\_(ツ)_/¯]';
protected $expected = [
/** @internal */
protected string $testdata = '[¯\_(ツ)_/¯]';
/** @internal */
protected array $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,
@ -34,5 +41,12 @@ class ByteTest extends DatainterfaceTestAbstract{
21, 47, 250, 101
];
/**
* @inheritDoc
* @internal
*/
protected function getDataInterfaceInstance(QROptions $options):QRDataInterface{
return new Byte($options);
}
}

View File

@ -12,54 +12,116 @@
namespace chillerlan\QRCodeTest\Data;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
use PHPUnit\Framework\TestCase;
use chillerlan\QRCode\Data\{QRCodeDataException, QRDataInterface, QRMatrix};
use chillerlan\QRCodeTest\QRTestAbstract;
use ReflectionClass;
abstract class DatainterfaceTestAbstract extends QRTestAbstract{
use function str_repeat;
/**
* The data interface test abstract
*/
abstract class DatainterfaceTestAbstract extends TestCase{
/** @internal */
protected ReflectionClass $reflection;
/** @internal */
protected QRDataInterface $dataInterface;
/** @internal */
protected string $testdata;
/** @internal */
protected array $expected;
/**
* @var \chillerlan\QRCode\Data\QRDataAbstract
* @internal
*/
protected $dataInterface;
protected $testdata;
protected $expected;
protected function setUp():void{
parent::setUp();
$this->dataInterface = $this->reflection->newInstanceArgs([new QROptions(['version' => 4])]);
$this->dataInterface = $this->getDataInterfaceInstance(new QROptions(['version' => 4]));
$this->reflection = new ReflectionClass($this->dataInterface);
}
public function testInstance(){
$this->dataInterface = $this->reflection->newInstanceArgs([new QROptions, $this->testdata]);
/**
* Returns a data interface instance
*
* @internal
*/
abstract protected function getDataInterfaceInstance(QROptions $options):QRDataInterface;
$this->assertInstanceOf(QRDataInterface::class, $this->dataInterface);
/**
* Verifies the data interface instance
*/
public function testInstance():void{
$this::assertInstanceOf(QRDataInterface::class, $this->dataInterface);
}
public function testSetData(){
/**
* Tests ecc masking and verifies against a sample
*/
public function testMaskEcc():void{
$this->dataInterface->setData($this->testdata);
$this->assertSame($this->expected, $this->getProperty('matrixdata')->getValue($this->dataInterface));
$maskECC = $this->reflection->getMethod('maskECC');
$maskECC->setAccessible(true);
$this::assertSame($this->expected, $maskECC->invoke($this->dataInterface));
}
public function testInitMatrix(){
$m = $this->dataInterface->setData($this->testdata)->initMatrix(0);
$this->assertInstanceOf(QRMatrix::class, $m);
/**
* @see testInitMatrix()
* @internal
* @return int[][]
*/
public function MaskPatternProvider():array{
return [[0], [1], [2], [3], [4], [5], [6], [7]];
}
public function testGetMinimumVersion(){
$this->assertSame(1, $this->getMethod('getMinimumVersion')->invoke($this->dataInterface));
/**
* Tests initializing the data matrix
*
* @dataProvider MaskPatternProvider
*/
public function testInitMatrix(int $maskPattern):void{
$this->dataInterface->setData($this->testdata);
$matrix = $this->dataInterface->initMatrix($maskPattern);
$this::assertInstanceOf(QRMatrix::class, $matrix);
$this::assertSame($maskPattern, $matrix->maskPattern());
}
public function testGetMinimumVersionException(){
/**
* Tests getting the minimum QR version for the given data
*/
public function testGetMinimumVersion():void{
$this->dataInterface->setData($this->testdata);
$getMinimumVersion = $this->reflection->getMethod('getMinimumVersion');
$getMinimumVersion->setAccessible(true);
$this::assertSame(1, $getMinimumVersion->invoke($this->dataInterface));
}
/**
* Tests if an exception is thrown when the data exceeds the maximum version while auto detecting
*/
public function testGetMinimumVersionException():void{
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('data exceeds');
$this->getProperty('strlen')->setValue($this->dataInterface, 13370);
$this->getMethod('getMinimumVersion')->invoke($this->dataInterface);
$this->dataInterface = $this->getDataInterfaceInstance(new QROptions(['version' => QRCode::VERSION_AUTO]));
$this->dataInterface->setData(str_repeat($this->testdata, 1337));
}
/**
* Tests if an exception is thrown on data overflow
*/
public function testCodeLengthOverflowException():void{
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('code length overflow');
$this->dataInterface->setData(str_repeat($this->testdata, 1337));
}
}

View File

@ -12,13 +12,19 @@
namespace chillerlan\QRCodeTest\Data;
use chillerlan\QRCode\Data\{Kanji, QRCodeDataException};
use chillerlan\QRCode\QROptions;
use chillerlan\QRCode\Data\{Kanji, QRCodeDataException, QRDataInterface};
class KanjiTest extends DatainterfaceTestAbstract{
/**
* Tests the Kanji class
*/
final class KanjiTest extends DatainterfaceTestAbstract{
protected $FQCN = Kanji::class;
protected $testdata = '茗荷茗荷茗荷茗荷茗荷';
protected $expected = [
/** @internal */
protected string $testdata = '茗荷茗荷茗荷茗荷茗荷';
/** @internal */
protected array $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,
@ -34,17 +40,32 @@ class KanjiTest extends DatainterfaceTestAbstract{
96, 113, 54, 191
];
public function testIllegalCharException1(){
/**
* @inheritDoc
* @internal
*/
protected function getDataInterfaceInstance(QROptions $options):QRDataInterface{
return new Kanji($options);
}
/**
* Tests if an exception is thrown when an invalid character is encountered
*/
public function testIllegalCharException1():void{
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('illegal char at 1 [16191]');
$this->dataInterface->setData('ÃÃ');
}
public function testIllegalCharException2(){
/**
* Tests if an exception is thrown when an invalid character is encountered
*/
public function testIllegalCharException2():void{
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('illegal char at 1');
$this->dataInterface->setData('Ã');
}
}

View File

@ -12,18 +12,31 @@
namespace chillerlan\QRCodeTest\Data;
use chillerlan\QRCode\{QROptions, Data\Byte, Data\MaskPatternTester};
use chillerlan\QRCodeTest\QRTestAbstract;
use chillerlan\QRCode\QROptions;
use chillerlan\QRCode\Data\{Byte, MaskPatternTester};
use PHPUnit\Framework\TestCase;
class MaskPatternTesterTest extends QRTestAbstract{
/**
* MaskPatternTester coverage test
*/
final class MaskPatternTesterTest extends TestCase{
protected $FQCN = MaskPatternTester::class;
/**
* Tests getting the best mask pattern
*/
public function testMaskpattern():void{
$dataInterface = new Byte(new QROptions(['version' => 10]), 'test');
// coverage
public function testMaskpattern(){
$matrix = (new Byte(new QROptions(['version' => 10]), 'test'))->initMatrix(3, true);
$this::assertSame(3, (new MaskPatternTester($dataInterface))->getBestMaskPattern());
}
$this->assertSame(4243, (new MaskPatternTester($matrix))->testPattern());
/**
* Tests getting the penalty value for a given mask pattern
*/
public function testMaskpatternID():void{
$dataInterface = new Byte(new QROptions(['version' => 10]), 'test');
$this::assertSame(4243, (new MaskPatternTester($dataInterface))->testPattern(3));
}
}

View File

@ -12,13 +12,19 @@
namespace chillerlan\QRCodeTest\Data;
use chillerlan\QRCode\Data\{Number, QRCodeDataException};
use chillerlan\QRCode\QROptions;
use chillerlan\QRCode\Data\{Number, QRCodeDataException, QRDataInterface};
class NumberTest extends DatainterfaceTestAbstract{
/**
* Tests the Number class
*/
final class NumberTest extends DatainterfaceTestAbstract{
protected $FQCN = Number::class;
protected $testdata = '0123456789';
protected $expected = [
/** @internal */
protected string $testdata = '0123456789';
/** @internal */
protected array $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,
@ -34,7 +40,18 @@ class NumberTest extends DatainterfaceTestAbstract{
89, 63, 168, 151
];
public function testGetCharCodeException(){
/**
* @inheritDoc
* @internal
*/
protected function getDataInterfaceInstance(QROptions $options):QRDataInterface{
return new Number($options);
}
/**
* Tests if an exception is thrown when an invalid character is encountered
*/
public function testGetCharCodeException():void{
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('illegal char: "#" [35]');

285
vendor/chillerlan/php-qrcode/tests/Data/QRMatrixTest.php vendored Normal file → Executable file
View File

@ -15,177 +15,294 @@ namespace chillerlan\QRCodeTest\Data;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
use chillerlan\QRCode\Data\{QRCodeDataException, QRMatrix};
use chillerlan\QRCodeTest\QRTestAbstract;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
class QRMatrixTest extends QRTestAbstract{
/**
* Tests the QRMatix class
*/
final class QRMatrixTest extends TestCase{
protected $FQCN = QRMatrix::class;
protected $version = 7;
/** @internal */
protected const version = 40;
/** @internal */
protected QRMatrix $matrix;
/**
* @var \chillerlan\QRCode\Data\QRMatrix
* invokes a QRMatrix object
*
* @internal
*/
protected $matrix;
protected function setUp():void{
parent::setUp();
$this->matrix = $this->reflection->newInstanceArgs([$this->version, QRCode::ECC_L]);
$this->matrix = $this->getMatrix($this::version);
}
public function testInvalidVersionException(){
/**
* shortcut
*
* @internal
*/
protected function getMatrix(int $version):QRMatrix{
return new QRMatrix($version, QRCode::ECC_L);
}
/**
* Validates the QRMatrix instance
*/
public function testInstance():void{
$this::assertInstanceOf(QRMatrix::class, $this->matrix);
}
/**
* Tests if an exception is thrown when an invalid QR version was given
*/
public function testInvalidVersionException():void{
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('invalid QR Code version');
$this->reflection->newInstanceArgs([42, 0]);
$this->matrix = new QRMatrix(42, 0);
}
public function testInvalidEccException(){
/**
* Tests if an exception is thrown when an invalid ECC level was given
*/
public function testInvalidEccException():void{
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('invalid ecc level');
$this->reflection->newInstanceArgs([1, 42]);
$this->matrix = new QRMatrix(1, 42);
}
public function testInstance(){
$this->assertInstanceOf($this->FQCN, $this->matrix);
/**
* Tests if size() returns the actual matrix size/count
*/
public function testSize():void{
$this::assertCount($this->matrix->size(), $this->matrix->matrix());
}
public function testSize(){
$this->assertCount($this->matrix->size(), $this->matrix->matrix());
/**
* Tests if version() returns the current (given) version
*/
public function testVersion():void{
$this::assertSame($this::version, $this->matrix->version());
}
public function testVersion(){
$this->assertSame($this->version, $this->matrix->version());
/**
* Tests if eccLevel() returns the current (given) ECC level
*/
public function testECC():void{
$this::assertSame(QRCode::ECC_L, $this->matrix->eccLevel());
}
public function testECC(){
$this->assertSame(QRCode::ECC_L, $this->matrix->eccLevel());
/**
* Tests if maskPattern() returns the current (or default) mask pattern
*/
public function testMaskPattern():void{
$this::assertSame(-1, $this->matrix->maskPattern()); // default
// @todo: actual mask pattern after mapData()
}
public function testMaskPattern(){
$this->assertSame(-1, $this->matrix->maskPattern());
}
public function testGetSetCheck(){
/**
* Tests the set(), get() and check() methods
*/
public function testGetSetCheck():void{
$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::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));
$this::assertSame(255, $this->matrix->get(20, 20));
$this::assertFalse($this->matrix->check(20, 20));
}
public function testSetDarkModule(){
$this->matrix->setDarkModule();
/**
* Version data provider for several pattern tests
*
* @return int[][]
* @internal
*/
public function versionProvider():array{
$versions = [];
$this->assertSame(QRMatrix::M_DARKMODULE << 8, $this->matrix->get(8, $this->matrix->size() - 8));
for($i = 1; $i <= 40; $i++){
$versions[] = [$i];
}
return $versions;
}
public function testSetFinderPattern(){
$this->matrix->setFinderPattern();
/**
* Tests setting the dark module and verifies its position
*
* @dataProvider versionProvider
*/
public function testSetDarkModule(int $version):void{
$matrix = $this->getMatrix($version)->setDarkModule();
$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));
$this::assertSame(QRMatrix::M_DARKMODULE << 8, $matrix->get(8, $matrix->size() - 8));
}
public function testSetSeparators(){
$this->matrix->setSeparators();
/**
* Tests setting the finder patterns and verifies their positions
*
* @dataProvider versionProvider
*/
public function testSetFinderPattern(int $version):void{
$matrix = $this->getMatrix($version)->setFinderPattern();
$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));
$this::assertSame(QRMatrix::M_FINDER << 8, $matrix->get(0, 0));
$this::assertSame(QRMatrix::M_FINDER << 8, $matrix->get(0, $matrix->size() - 1));
$this::assertSame(QRMatrix::M_FINDER << 8, $matrix->get($matrix->size() - 1, 0));
}
public function testSetAlignmentPattern(){
$this->matrix
/**
* Tests the separator patterns and verifies their positions
*
* @dataProvider versionProvider
*/
public function testSetSeparators(int $version):void{
$matrix = $this->getMatrix($version)->setSeparators();
$this::assertSame(QRMatrix::M_SEPARATOR, $matrix->get(7, 0));
$this::assertSame(QRMatrix::M_SEPARATOR, $matrix->get(0, 7));
$this::assertSame(QRMatrix::M_SEPARATOR, $matrix->get(0, $matrix->size() - 8));
$this::assertSame(QRMatrix::M_SEPARATOR, $matrix->get($matrix->size() - 8, 0));
}
/**
* Tests the alignment patterns and verifies their positions - version 1 (no pattern) skipped
*
* @dataProvider versionProvider
*/
public function testSetAlignmentPattern(int $version):void{
if($version === 1){
$this->markTestSkipped('N/A');
/** @noinspection PhpUnreachableStatementInspection */
return;
}
$matrix = $this
->getMatrix($version)
->setFinderPattern()
->setAlignmentPattern()
;
$alignmentPattern = (new ReflectionClass(QRMatrix::class))->getConstant('alignmentPattern')[$this->version];
$alignmentPattern = (new ReflectionClass(QRMatrix::class))->getConstant('alignmentPattern')[$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');
if($matrix->get($px, $py) === QRMatrix::M_FINDER << 8){
$this::assertSame(QRMatrix::M_FINDER << 8, $matrix->get($px, $py), 'skipped finder pattern');
continue;
}
$this->assertSame(QRMatrix::M_ALIGNMENT << 8, $this->matrix->get($px, $py));
$this::assertSame(QRMatrix::M_ALIGNMENT << 8, $matrix->get($px, $py));
}
}
}
public function testSetTimingPattern(){
$this->matrix
/**
* Tests the timing patterns and verifies their positions
*
* @dataProvider versionProvider
*/
public function testSetTimingPattern(int $version):void{
$matrix = $this
->getMatrix($version)
->setAlignmentPattern()
->setTimingPattern()
;
$size = $this->matrix->size();
$size = $matrix->size();
for($i = 7; $i < $size - 7; $i++){
if($i % 2 === 0){
$p1 = $this->matrix->get(6, $i);
$p1 = $matrix->get(6, $i);
if($p1 === QRMatrix::M_ALIGNMENT << 8){
$this->assertSame(QRMatrix::M_ALIGNMENT << 8, $p1, 'skipped alignment pattern');
$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));
$this::assertSame(QRMatrix::M_TIMING << 8, $p1);
$this::assertSame(QRMatrix::M_TIMING << 8, $matrix->get($i, 6));
}
}
}
public function testSetVersionNumber(){
$this->matrix->setVersionNumber(true);
/**
* Tests the version patterns and verifies their positions - version < 7 skipped
*
* @dataProvider versionProvider
*/
public function testSetVersionNumber(int $version):void{
$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));
if($version < 7){
$this->markTestSkipped('N/A');
/** @noinspection PhpUnreachableStatementInspection */
return;
}
$matrix = $this->getMatrix($version)->setVersionNumber(true);
$this::assertSame(QRMatrix::M_VERSION, $matrix->get($matrix->size() - 9, 0));
$this::assertSame(QRMatrix::M_VERSION, $matrix->get($matrix->size() - 11, 5));
$this::assertSame(QRMatrix::M_VERSION, $matrix->get(0, $matrix->size() - 9));
$this::assertSame(QRMatrix::M_VERSION, $matrix->get(5, $matrix->size() - 11));
}
public function testSetFormatInfo(){
$this->matrix->setFormatInfo(0, true);
/**
* Tests the format patterns and verifies their positions
*
* @dataProvider versionProvider
*/
public function testSetFormatInfo(int $version):void{
$matrix = $this->getMatrix($version)->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));
$this::assertSame(QRMatrix::M_FORMAT, $matrix->get(8, 0));
$this::assertSame(QRMatrix::M_FORMAT, $matrix->get(0, 8));
$this::assertSame(QRMatrix::M_FORMAT, $matrix->get($matrix->size() - 1, 8));
$this::assertSame(QRMatrix::M_FORMAT, $matrix->get($matrix->size() - 8, 8));
}
public function testSetQuietZone(){
$size = $this->matrix->size();
/**
* Tests the quiet zone pattern and verifies its position
*
* @dataProvider versionProvider
*/
public function testSetQuietZone(int $version):void{
$matrix = $this->getMatrix($version);
$size = $matrix->size();
$q = 5;
$this->matrix->set(0, 0, true, QRMatrix::M_TEST);
$this->matrix->set($size - 1, $size - 1, true, QRMatrix::M_TEST);
$matrix->set(0, 0, true, QRMatrix::M_TEST);
$matrix->set($size - 1, $size - 1, true, QRMatrix::M_TEST);
$this->matrix->setQuietZone($q);
$matrix->setQuietZone($q);
$this->assertCount($size + 2 * $q, $this->matrix->matrix());
$this->assertCount($size + 2 * $q, $this->matrix->matrix()[$size - 1]);
$this::assertCount($size + 2 * $q, $matrix->matrix());
$this::assertCount($size + 2 * $q, $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));
$size = $matrix->size();
$this::assertSame(QRMatrix::M_QUIETZONE, $matrix->get(0, 0));
$this::assertSame(QRMatrix::M_QUIETZONE, $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));
$this::assertSame(QRMatrix::M_TEST << 8, $matrix->get($q, $q));
$this::assertSame(QRMatrix::M_TEST << 8, $matrix->get($size - 1 - $q, $size - 1 - $q));
}
public function testSetQuietZoneException(){
/**
* Tests if an exception is thrown in an attempt to create it before data was written
*/
public function testSetQuietZoneException():void{
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('use only after writing data');

View File

@ -12,21 +12,22 @@
namespace chillerlan\QRCodeTest\Helpers;
use chillerlan\QRCode\{QRCode, Helpers\BitBuffer};
use chillerlan\QRCodeTest\QRTestAbstract;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\Helpers\BitBuffer;
use PHPUnit\Framework\TestCase;
class BitBufferTest extends QRTestAbstract{
/**
* BitBuffer coverage test
*/
final class BitBufferTest extends TestCase{
/**
* @var \chillerlan\QRCode\Helpers\BitBuffer
*/
protected $bitBuffer;
protected BitBuffer $bitBuffer;
protected function setUp():void{
$this->bitBuffer = new BitBuffer;
}
public function bitProvider(){
public function bitProvider():array{
return [
'number' => [QRCode::DATA_NUMBER, 16],
'alphanum' => [QRCode::DATA_ALPHANUM, 32],
@ -38,16 +39,16 @@ class BitBufferTest extends QRTestAbstract{
/**
* @dataProvider bitProvider
*/
public function testPut($data, $value){
public function testPut(int $data, int $value):void{
$this->bitBuffer->put($data, 4);
$this->assertSame($value, $this->bitBuffer->buffer[0]);
$this->assertSame(4, $this->bitBuffer->length);
$this::assertSame($value, $this->bitBuffer->getBuffer()[0]);
$this::assertSame(4, $this->bitBuffer->getLength());
}
public function testClear(){
public function testClear():void{
$this->bitBuffer->clear();
$this->assertSame([], $this->bitBuffer->buffer);
$this->assertSame(0, $this->bitBuffer->length);
$this::assertSame([], $this->bitBuffer->getBuffer());
$this::assertSame(0, $this->bitBuffer->getLength());
}
}

View File

@ -14,26 +14,26 @@ namespace chillerlan\QRCodeTest\Helpers;
use chillerlan\QRCode\Helpers\Polynomial;
use chillerlan\QRCode\QRCodeException;
use chillerlan\QRCodeTest\QRTestAbstract;
use PHPUnit\Framework\TestCase;
class PolynomialTest extends QRTestAbstract{
/**
* Polynomial coverage test
*/
final class PolynomialTest extends TestCase{
/**
* @var \chillerlan\QRCode\Helpers\Polynomial
*/
protected $polynomial;
protected Polynomial $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 testGexp():void{
$this::assertSame(142, $this->polynomial->gexp(-1));
$this::assertSame(133, $this->polynomial->gexp(128));
$this::assertSame(2, $this->polynomial->gexp(256));
}
public function testGlogException(){
public function testGlogException():void{
$this->expectException(QRCodeException::class);
$this->expectExceptionMessage('log(0)');

View File

@ -23,8 +23,6 @@ use function class_exists, substr;
*/
class QRFpdfTest extends QROutputTestAbstract{
protected $FQCN = QRFpdf::class;
/**
* @inheritDoc
* @internal
@ -33,12 +31,32 @@ class QRFpdfTest extends QROutputTestAbstract{
if(!class_exists(FPDF::class)){
$this->markTestSkipped('FPDF not available');
/** @noinspection PhpUnreachableStatementInspection */
return;
}
parent::setUp();
}
/**
* @inheritDoc
* @internal
*/
protected function getOutputInterface(QROptions $options):QROutputInterface{
return new QRFpdf($options, $this->matrix);
}
/**
* @inheritDoc
* @internal
*/
public function types():array{
return [
'fpdf' => [QRCode::OUTPUT_FPDF],
];
}
/**
* @inheritDoc
*/
@ -50,6 +68,7 @@ class QRFpdfTest extends QROutputTestAbstract{
4 => [255, 255, 255],
];
$this->outputInterface = $this->getOutputInterface($this->options);
$this->outputInterface->dump();
$this::assertTrue(true); // tricking the code coverage
@ -57,25 +76,22 @@ class QRFpdfTest extends QROutputTestAbstract{
/**
* @inheritDoc
* @dataProvider types
*/
public function testRenderImage():void{
$type = QRCode::OUTPUT_FPDF;
public function testRenderImage(string $type):void{
$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);
$expected = substr(file_get_contents(__DIR__.'/samples/'.$type), 0, 2500);
$actual = substr((new QRCode($this->options))->render('test'), 0, 2500);
$this::assertSame($expected, $actual);
}
public function testOutputGetResource():void{
$this->options->returnResource = true;
$this->setOutputInterface();
$this->outputInterface = $this->getOutputInterface($this->options);
$this::assertInstanceOf(FPDF::class, $this->outputInterface->dump());
}

View File

@ -12,14 +12,41 @@
namespace chillerlan\QRCodeTest\Output;
use chillerlan\QRCode\{QRCode, Output\QRImage};
use const PHP_MAJOR_VERSION;
use chillerlan\QRCode\{QRCode, QROptions};
use chillerlan\QRCode\Output\{QROutputInterface, QRImage};
/**
* Tests the QRImage output module
*/
class QRImageTest extends QROutputTestAbstract{
protected $FQCN = QRImage::class;
/**
* @inheritDoc
* @internal
*/
public function setUp():void{
public function types(){
if(!extension_loaded('gd')){
$this->markTestSkipped('ext-gd not loaded');
return;
}
parent::setUp();
}
/**
* @inheritDoc
* @internal
*/
protected function getOutputInterface(QROptions $options):QROutputInterface{
return new QRImage($options, $this->matrix);
}
/**
* @inheritDoc
* @internal
*/
public function types():array{
return [
'png' => [QRCode::OUTPUT_IMAGE_PNG],
'gif' => [QRCode::OUTPUT_IMAGE_GIF],
@ -28,25 +55,9 @@ class QRImageTest extends QROutputTestAbstract{
}
/**
* @dataProvider types
* @param $type
* @inheritDoc
*/
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(){
public function testSetModuleValues():void{
$this->options->moduleValues = [
// data
@ -54,24 +65,25 @@ class QRImageTest extends QROutputTestAbstract{
4 => [255, 255, 255],
];
$this->setOutputInterface()->dump();
$this->outputInterface = $this->getOutputInterface($this->options);
$this->outputInterface->dump();
$this->assertTrue(true); // tricking the code coverage
$this::assertTrue(true); // tricking the code coverage
}
/**
* @phan-suppress PhanUndeclaredClassReference
*/
public function testOutputGetResource():void{
$this->options->returnResource = true;
$this->outputInterface = $this->getOutputInterface($this->options);
$this->setOutputInterface();
$actual = $this->outputInterface->dump();
$data = $this->outputInterface->dump();
if(PHP_MAJOR_VERSION >= 8){
$this::assertInstanceOf('\\GdImage', $data);
}
else{
$this::assertIsResource($data);
}
/** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */
\PHP_MAJOR_VERSION >= 8
? $this::assertInstanceOf(\GdImage::class, $actual)
: $this::assertIsResource($actual);
}
}

View File

@ -9,40 +9,59 @@
* @copyright 2018 smiley
* @license MIT
*
* @noinspection PhpUndefinedClassInspection
* @noinspection PhpComposerExtensionStubsInspection
*/
namespace chillerlan\QRCodeTest\Output;
use Imagick;
use chillerlan\QRCode\{QRCode, Output\QRImagick};
use chillerlan\QRCode\{QRCode, QROptions};
use chillerlan\QRCode\Output\{QROutputInterface, QRImagick};
/**
* Tests the QRImagick output module
*/
class QRImagickTest extends QROutputTestAbstract{
protected $FQCN = QRImagick::class;
/**
* @inheritDoc
* @internal
*/
public function setUp():void{
if(!extension_loaded('imagick')){
$this->markTestSkipped('ext-imagick not loaded');
/** @noinspection PhpUnreachableStatementInspection */
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));
/**
* @inheritDoc
* @internal
*/
protected function getOutputInterface(QROptions $options):QROutputInterface{
return new QRImagick($options, $this->matrix);
}
public function testSetModuleValues(){
/**
* @inheritDoc
* @internal
*/
public function types():array{
return [
'imagick' => [QRCode::OUTPUT_IMAGICK],
];
}
/**
* @inheritDoc
*/
public function testSetModuleValues():void{
$this->options->moduleValues = [
// data
@ -50,17 +69,18 @@ class QRImagickTest extends QROutputTestAbstract{
4 => '#ECF9BE',
];
$this->setOutputInterface()->dump();
$this->outputInterface = $this->getOutputInterface($this->options);
$this->outputInterface->dump();
$this->assertTrue(true); // tricking the code coverage
$this::assertTrue(true); // tricking the code coverage
}
public function testOutputGetResource():void{
$this->options->returnResource = true;
$this->setOutputInterface();
$this->outputInterface = $this->getOutputInterface($this->options);
$this::assertInstanceOf(Imagick::class, $this->outputInterface->dump());
}
}

View File

@ -12,13 +12,27 @@
namespace chillerlan\QRCodeTest\Output;
use chillerlan\QRCode\{QRCode, Output\QRMarkup};
use chillerlan\QRCode\{QRCode, QROptions};
use chillerlan\QRCode\Output\{QROutputInterface, QRMarkup};
/**
* Tests the QRMarkup output module
*/
class QRMarkupTest extends QROutputTestAbstract{
protected $FQCN = QRMarkup::class;
/**
* @inheritDoc
* @internal
*/
protected function getOutputInterface(QROptions $options):QROutputInterface{
return new QRMarkup($options, $this->matrix);
}
public function types(){
/**
* @inheritDoc
* @internal
*/
public function types():array{
return [
'html' => [QRCode::OUTPUT_MARKUP_HTML],
'svg' => [QRCode::OUTPUT_MARKUP_SVG],
@ -26,43 +40,9 @@ class QRMarkupTest extends QROutputTestAbstract{
}
/**
* @dataProvider types
* @param $type
* @inheritDoc
*/
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(){
public function testSetModuleValues():void{
$this->options->imageBase64 = false;
$this->options->moduleValues = [
// data
@ -70,10 +50,10 @@ class QRMarkupTest extends QROutputTestAbstract{
4 => '#ECF9BE',
];
$this->setOutputInterface();
$this->outputInterface = $this->getOutputInterface($this->options);
$data = $this->outputInterface->dump();
$this->assertStringContainsString('#4A6000', $data);
$this->assertStringContainsString('#ECF9BE', $data);
$this::assertStringContainsString('#4A6000', $data);
$this::assertStringContainsString('#ECF9BE', $data);
}
}

View File

@ -12,60 +12,129 @@
namespace chillerlan\QRCodeTest\Output;
use chillerlan\QRCode\QROptions;
use chillerlan\QRCode\Data\Byte;
use chillerlan\QRCode\{QRCode, QROptions};
use chillerlan\QRCode\Data\{Byte, QRMatrix};
use chillerlan\QRCode\Output\{QRCodeOutputException, QROutputInterface};
use chillerlan\QRCodeTest\QRTestAbstract;
use PHPUnit\Framework\TestCase;
use function dirname, file_exists, mkdir;
use function file_exists, in_array, mkdir;
abstract class QROutputTestAbstract extends QRTestAbstract{
use const PHP_OS_FAMILY, PHP_VERSION_ID;
const cachefile = __DIR__.'/../../.build/output_test/test.';
/**
* Test abstract for the several (built-in) output modules,
* should also be used to test custom output modules
*/
abstract class QROutputTestAbstract extends TestCase{
/** @internal */
protected string $builddir = __DIR__.'/../../.build/output_test';
/** @internal */
protected QROutputInterface $outputInterface;
/** @internal */
protected QROptions $options;
/** @internal */
protected QRMatrix $matrix;
/**
* @var \chillerlan\QRCode\Output\QROutputInterface
* Attempts to create a directory under /.build and instances several required objects
*
* @internal
*/
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);
if(!file_exists($this->builddir)){
mkdir($this->builddir, 0777, true);
}
$this->options = new QROptions;
$this->setOutputInterface();
$this->options = new QROptions;
$this->matrix = (new Byte($this->options, 'testdata'))->initMatrix(0);
$this->outputInterface = $this->getOutputInterface($this->options);
}
protected function setOutputInterface(){
$this->outputInterface = $this->reflection->newInstanceArgs([$this->options, (new Byte($this->options, 'testdata'))->initMatrix(0)]);
return $this->outputInterface;
/**
* Returns a QROutputInterface instance with the given options and using $this->matrix
*
* @internal
*/
abstract protected function getOutputInterface(QROptions $options):QROutputInterface;
/**
* Validate the instance of the interface
*/
public function testInstance():void{
$this::assertInstanceOf(QROutputInterface::class, $this->outputInterface);
}
public function testInstance(){
$this->assertInstanceOf(QROutputInterface::class, $this->outputInterface);
}
public function testSaveException(){
/**
* Tests if an exception is thrown when trying to write a cache file to an invalid destination
*/
public function testSaveException():void{
$this->expectException(QRCodeOutputException::class);
$this->expectExceptionMessage('Could not write data to cache file: /foo');
$this->expectExceptionMessage('Could not write data to cache file: /foo/bar.test');
$this->options->cachefile = '/foo';
$this->setOutputInterface();
$this->options->cachefile = '/foo/bar.test';
$this->outputInterface = $this->getOutputInterface($this->options);
$this->outputInterface->dump();
}
/**
* covers the module values settings
*/
abstract public function testSetModuleValues():void;
/*
* additional, non-essential, potentially inaccurate coverage tests
*/
/**
* @see testStringOutput()
* @return string[][]
* @internal
*/
abstract public function types():array;
/**
* coverage of the built-in output modules
*
* @dataProvider types
*/
public function testStringOutput(string $type):void{
$this->options->outputType = $type;
$this->options->cachefile = $this->builddir.'/test.'.$type;
$this->options->imageBase64 = false;
$this->outputInterface = $this->getOutputInterface($this->options);
$data = $this->outputInterface->dump(); // creates the cache file
$this::assertSame($data, file_get_contents($this->options->cachefile));
}
/**
* covers the built-in output modules, tests against pre-rendered data
*
* @dataProvider types
*/
public function testRenderImage(string $type):void{
// may fail on CI, different PHP (platform) versions produce different output
// the samples were generated on php-7.4.3-Win32-vc15-x64
if(
(PHP_OS_FAMILY !== 'Windows' || PHP_VERSION_ID >= 80100)
&& in_array($type, [QRCode::OUTPUT_IMAGE_JPG, QRCode::OUTPUT_IMAGICK, QRCode::OUTPUT_MARKUP_SVG])
){
$this::markTestSkipped('may fail on CI');
/** @noinspection PhpUnreachableStatementInspection */
return;
}
$this->options->outputType = $type;
$this::assertSame(
trim(file_get_contents(__DIR__.'/samples/'.$type)),
trim((new QRCode($this->options))->render('test'))
);
}
}

View File

@ -12,13 +12,28 @@
namespace chillerlan\QRCodeTest\Output;
use chillerlan\QRCode\{QRCode, Output\QRString};
use chillerlan\QRCodeExamples\MyCustomOutput;
use chillerlan\QRCode\{QRCode, QROptions};
use chillerlan\QRCode\Output\{QROutputInterface, QRString};
/**
* Tests the QRString output module
*/
class QRStringTest extends QROutputTestAbstract{
protected $FQCN = QRString::class;
/**
* @inheritDoc
* @internal
*/
protected function getOutputInterface(QROptions $options):QROutputInterface{
return new QRString($options, $this->matrix);
}
public function types(){
/**
* @inheritDoc
* @internal
*/
public function types():array{
return [
'json' => [QRCode::OUTPUT_STRING_JSON],
'text' => [QRCode::OUTPUT_STRING_TEXT],
@ -26,19 +41,9 @@ class QRStringTest extends QROutputTestAbstract{
}
/**
* @dataProvider types
* @param $type
* @inheritDoc
*/
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(){
public function testSetModuleValues():void{
$this->options->moduleValues = [
// data
@ -46,11 +51,26 @@ class QRStringTest extends QROutputTestAbstract{
4 => 'B',
];
$this->setOutputInterface();
$data = $this->outputInterface->dump();
$this->outputInterface = $this->getOutputInterface($this->options);
$data = $this->outputInterface->dump();
$this->assertStringContainsString('A', $data);
$this->assertStringContainsString('B', $data);
$this::assertStringContainsString('A', $data);
$this::assertStringContainsString('B', $data);
}
/**
* covers the custom output functionality via an example
*/
public function testCustomOutput():void{
$this->options->version = 5;
$this->options->eccLevel = QRCode::ECC_L;
$this->options->outputType = QRCode::OUTPUT_CUSTOM;
$this->options->outputInterface = MyCustomOutput::class;
$this::assertSame(
file_get_contents(__DIR__.'/samples/custom'),
(new QRCode($this->options))->render('test')
);
}
}

View File

@ -0,0 +1,45 @@
000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000
000011111110111010000101111010000011111110000
000010000010111000001101011000001010000010000
000010111010101101011000001101011010111010000
000010111010110100111110010100111010111010000
000010111010000001101011000001101010111010000
000010000010100111110010100111110010000010000
000011111110101010101010101010101011111110000
000000000000010010100111110010100000000000000
000011001110000101111010000101111001011110000
000000000000111010000101111010000111100010000
000001011010100111110010100111110011001010000
000010000101111101011000001101011110011110000
000000011010100011000001101011000101110100000
000011001100001001101011000001101010011010000
000010110111110000001101011000001100110100000
000010000100100010100111110010100001100100000
000011111110111101111010000101111010100110000
000011010000111010000101111010000111100100000
000010101111111111110010100111110011001000000
000010110001110101011000001101011110011010000
000001001111100011000001101011000101110010000
000011000100110001101011000001101010011100000
000001000011001000001101011000001100110000000
000011101001011010100111110010100001100000000
000010111010001101111010000101111010100110000
000011100000001010000101111010000111100000000
000000001110110111110010100111110011001000000
000000011001011101011000001101011110011100000
000011111110101011000001101011001111110110000
000000000000110001101011000001101000111100000
000011111110001000001101011000011010110000000
000010000010101010100111110010101000100100000
000010111010111101111010000101111111100110000
000010111010011010000101111010001101100010000
000010111010000111110010100111100101101100000
000010000010101101011000001101001100111100000
000011111110101011000001101011000110010110000
000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000

Binary file not shown.

View File

@ -0,0 +1 @@
data:image/gif;base64,R0lGODlhkQCRAIAAAAQCBP///yH5BAEAAAEALAAAAACRAJEAAAL+jI+py+0Po5y02ouz3rz7D4biSJbmiabqyrbuC8fyTNf2jef6zvf+DwwKh8Si8YhMKpfMpvOpBEin1Kr1ek1gpY6qdgsOU1niMviL7Y4R5raV7I4D0NmGly13w/Nm+tu+dsDXtjcY5nfHkGhgWFY4x7GoIOlXGRmYQnmhKYg56clZEToyOjF6CuqJURrCGoE6pRaLN7vh+nH7AMsFWMuouglcIklIy9vpG0CcmlyMfJzJLGb5DFlN/dvcZ2wdre3Ija0s3T3+PR2+sgx+nd7+ng1tjg6Psk4fX36vT464JX5i3xl3+WTJE+esoDpyA+vtKodwW72ADP8RnAfxYkT+dgpVCLToUFjHBR/TaDRRsk7IZAAvJsT4yJ/Jlfh0VZw50t5NlTlfGuTHEebCc4dcSoSQ8s9ElDuV9jxqk2jDnBSlgnwa9KdMnkI9iqSQa9VXU2NblX11VkPYXgeZsgz2FsRaRWk9zCVZV2xcUXkv7QXb18LdT39FNKq5b+NhnDoXM05s1DHXqpIh0wRaObDazJaxyuM8WC9ozKQVg4bC1ilqHVNX52jt+gbs2DVm087nM/Lnrw9bh4YbR6vujEur+dacgY/wy8vxBv0tOE/zrlRPGr9KvXHw1Kanb1UNne/flw9bXu6snThu7OWtewaPXLR6jMfH9+PODT3l+eT+ed+naxVRMaVWX1vM4XedgEPtFuBkCK4nx0/hAaZggg4CyKAhEsYHXGn5YTfdYhsWZlaFEF7oXIaDjGggCejlhiFpyhHIYXQNzvedabnot6CMdRXoI4kHvpAUWY81ZaSKMRQpAZA5NvmfC0yideSNSXpIg5Pu0ceef1W22IKWxZ0In31f4tjji14itmaZYD6ipplsyukmml6daR6ZPFqI5W17vtfdbYA+mN2fUBgap5KCFtpmoGMeGiVhig4p5WhvbgmkbZVaeiWLQaIYpqV2VsfndxN2IOqo2T3J56l+jdYpjTe6akuNpL4HI5k2TNheqyB+KKQ3l1IJZlKachn+bHqoNgpsTazOwCuzvjL2rAzGRmqqtEhBtV+2JkpaVIxXHmtYU4hyu6W3w+JiLrbNqpYnuOSWqKKYKYZ777i/DtPut/M+am+os0b6763ITrppvV1aCSWdhnb7bqLwElotreIpjOe7scb4sFsYcyXxud8u2S/CjGYsbro9qtvnySBruyoM1/qLJKbuEllyy1Ma3PGd60b1sR7D9SqsqiEepiPMENc68HM1p+yz0YSKOLTSHksNtZ5Q7QwunJlVvK/BO6QKNrUqvyZq2aBanB5nak/86Nhpa0x31ovejXfeeu/Nd99+/w144IIPTnjhhh+OeOKKL854444/DnnkkhcCXgAAOw==

View File

@ -0,0 +1,31 @@
<div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #000;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
<div><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span><span style="background: #fff;"></span></div>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
[[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],[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],[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],[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],[18,18,18,18,1536,1536,1536,1536,1536,1536,1536,8,3584,1024,4,4,1024,8,1536,1536,1536,1536,1536,1536,1536,18,18,18,18],[18,18,18,18,1536,6,6,6,6,6,1536,8,14,1024,4,4,1024,8,1536,6,6,6,6,6,1536,18,18,18,18],[18,18,18,18,1536,6,5632,5632,5632,6,1536,8,3584,4,1024,4,1024,8,1536,6,5632,5632,5632,6,1536,18,18,18,18],[18,18,18,18,1536,6,5632,5632,5632,6,1536,8,3584,4,4,1024,4,8,1536,6,5632,5632,5632,6,1536,18,18,18,18],[18,18,18,18,1536,6,5632,5632,5632,6,1536,8,3584,1024,1024,4,4,8,1536,6,5632,5632,5632,6,1536,18,18,18,18],[18,18,18,18,1536,6,6,6,6,6,1536,8,14,4,4,4,4,8,1536,6,6,6,6,6,1536,18,18,18,18],[18,18,18,18,1536,1536,1536,1536,1536,1536,1536,8,3072,12,3072,12,3072,8,1536,1536,1536,1536,1536,1536,1536,18,18,18,18],[18,18,18,18,8,8,8,8,8,8,8,8,14,1024,1024,4,4,8,8,8,8,8,8,8,8,18,18,18,18],[18,18,18,18,3584,3584,3584,3584,14,14,3072,14,3584,4,1024,4,4,3584,14,14,3584,3584,3584,14,3584,18,18,18,18],[18,18,18,18,1024,4,4,1024,1024,1024,12,1024,4,4,1024,4,1024,4,4,1024,4,1024,1024,4,1024,18,18,18,18],[18,18,18,18,1024,4,1024,4,1024,1024,3072,4,4,4,1024,4,1024,1024,1024,1024,1024,4,4,1024,1024,18,18,18,18],[18,18,18,18,1024,4,1024,1024,4,4,12,1024,1024,4,1024,4,1024,1024,4,4,4,1024,4,1024,4,18,18,18,18],[18,18,18,18,4,1024,1024,4,1024,4,3072,4,4,1024,1024,1024,4,1024,4,4,1024,1024,4,1024,4,18,18,18,18],[18,18,18,18,8,8,8,8,8,8,8,8,512,4,1024,4,4,1024,4,1024,4,1024,4,1024,4,18,18,18,18],[18,18,18,18,1536,1536,1536,1536,1536,1536,1536,8,14,4,1024,4,4,1024,1024,4,1024,1024,1024,4,4,18,18,18,18],[18,18,18,18,1536,6,6,6,6,6,1536,8,14,1024,4,1024,1024,1024,1024,4,4,1024,1024,4,4,18,18,18,18],[18,18,18,18,1536,6,5632,5632,5632,6,1536,8,14,1024,1024,1024,4,4,1024,4,4,4,1024,1024,1024,18,18,18,18],[18,18,18,18,1536,6,5632,5632,5632,6,1536,8,3584,1024,4,4,4,1024,4,4,1024,1024,4,1024,4,18,18,18,18],[18,18,18,18,1536,6,5632,5632,5632,6,1536,8,3584,4,1024,1024,4,1024,4,4,1024,4,1024,4,4,18,18,18,18],[18,18,18,18,1536,6,6,6,6,6,1536,8,3584,1024,1024,1024,1024,4,1024,4,1024,1024,4,4,1024,18,18,18,18],[18,18,18,18,1536,1536,1536,1536,1536,1536,1536,8,3584,1024,4,1024,1024,4,4,1024,4,4,4,4,4,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,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,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,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,18,18,18,18]]

View File

@ -0,0 +1 @@
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJEAAACRCAIAAABMus10AAAABnRSTlMA/wD/AP83WBt9AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACd0lEQVR4nO3dQW7jIABA0fFo7n/ldFnJC0sMBvu3762bJtUXMnUwHJ/P5w8pf5/+AAzTrEezHs16NOvRrEezHs16NOvRrEezHs16NOvRrEezHs16NOvRrEeznn8zLz6O467Pce20aOX6fa9XuJxeO/SbbzSzDMc469GsR7MezXqm5iAnNy5vHZplrJs4bPuLhhhnPZr1aNajWc+dc5CToavu0NV+6F7GU/OIdQ8cGWc9mvVo1qNZz8I5yFO2TUmeYpz1aNajWY9mPT9hDjKzPKTIOOvRrEezHs16Fs5B1l38n5pWvGQ6Y5z1aNajWY9mPXfOQbY9VDKzHmToq5ltf9EQ46xHsx7NejTrOV7yv/0M38Xwdpr1aNajWc9j+4PcuIvHzKO6626LrFsaa5z1aNajWY9mPfv2BzldhGd2AJm5gM9MOp7a0OzEOOvRrEezHs169u0PMnQB3/ZA7Utumgwxzno069GsR7Oefc/mzkwrtq34uHGrtJnffM0469GsR7MezXqm5iAzx7hse3pl3eKRpxhnPZr1aNajWc9j58XM3DXYtle79SDcQ7MezXo060mem7ttecg717AYZz2a9WjWo1lP8tzc6x9ed7fCfRD+k2Y9mvVo1pM8N3fmfbftALJuHa1x1qNZj2Y9mvUkz6y78fJ+4+LXbef1Gmc9mvVo1qNZT3IOcjLzeM6697UehG+a9WjWo1lP8tzck3WLR2Yez5n54WvGWY9mPZr1aNaTPDf32szK0XW3NnwX86tp1qNZj2Y9P+Hc3N/GOOvRrEezHs16NOvRrEezHs16NOvRrEezHs16NOvRrEezHs16NOvRrEezni+wifc/oOyB/wAAAABJRU5ErkJggg==

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,29 @@
⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕
⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕
⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕
⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕
⭕⭕⭕⭕🔴🔴🔴🔴🔴🔴🔴⭕🔴🔴⭕⭕🔴⭕🔴🔴🔴🔴🔴🔴🔴⭕⭕⭕⭕
⭕⭕⭕⭕🔴⭕⭕⭕⭕⭕🔴⭕⭕🔴⭕⭕🔴⭕🔴⭕⭕⭕⭕⭕🔴⭕⭕⭕⭕
⭕⭕⭕⭕🔴⭕🔴🔴🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴🔴🔴⭕🔴⭕⭕⭕⭕
⭕⭕⭕⭕🔴⭕🔴🔴🔴⭕🔴⭕🔴⭕⭕🔴⭕⭕🔴⭕🔴🔴🔴⭕🔴⭕⭕⭕⭕
⭕⭕⭕⭕🔴⭕🔴🔴🔴⭕🔴⭕🔴🔴🔴⭕⭕⭕🔴⭕🔴🔴🔴⭕🔴⭕⭕⭕⭕
⭕⭕⭕⭕🔴⭕⭕⭕⭕⭕🔴⭕⭕⭕⭕⭕⭕⭕🔴⭕⭕⭕⭕⭕🔴⭕⭕⭕⭕
⭕⭕⭕⭕🔴🔴🔴🔴🔴🔴🔴⭕🔴⭕🔴⭕🔴⭕🔴🔴🔴🔴🔴🔴🔴⭕⭕⭕⭕
⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕🔴🔴⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕
⭕⭕⭕⭕🔴🔴🔴🔴⭕⭕🔴⭕🔴⭕🔴⭕⭕🔴⭕⭕🔴🔴🔴⭕🔴⭕⭕⭕⭕
⭕⭕⭕⭕🔴⭕⭕🔴🔴🔴⭕🔴⭕⭕🔴⭕🔴⭕⭕🔴⭕🔴🔴⭕🔴⭕⭕⭕⭕
⭕⭕⭕⭕🔴⭕🔴⭕🔴🔴🔴⭕⭕⭕🔴⭕🔴🔴🔴🔴🔴⭕⭕🔴🔴⭕⭕⭕⭕
⭕⭕⭕⭕🔴⭕🔴🔴⭕⭕⭕🔴🔴⭕🔴⭕🔴🔴⭕⭕⭕🔴⭕🔴⭕⭕⭕⭕⭕
⭕⭕⭕⭕⭕🔴🔴⭕🔴⭕🔴⭕⭕🔴🔴🔴⭕🔴⭕⭕🔴🔴⭕🔴⭕⭕⭕⭕⭕
⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕🔴⭕🔴⭕⭕🔴⭕🔴⭕🔴⭕🔴⭕⭕⭕⭕⭕
⭕⭕⭕⭕🔴🔴🔴🔴🔴🔴🔴⭕⭕⭕🔴⭕⭕🔴🔴⭕🔴🔴🔴⭕⭕⭕⭕⭕⭕
⭕⭕⭕⭕🔴⭕⭕⭕⭕⭕🔴⭕⭕🔴⭕🔴🔴🔴🔴⭕⭕🔴🔴⭕⭕⭕⭕⭕⭕
⭕⭕⭕⭕🔴⭕🔴🔴🔴⭕🔴⭕⭕🔴🔴🔴⭕⭕🔴⭕⭕⭕🔴🔴🔴⭕⭕⭕⭕
⭕⭕⭕⭕🔴⭕🔴🔴🔴⭕🔴⭕🔴🔴⭕⭕⭕🔴⭕⭕🔴🔴⭕🔴⭕⭕⭕⭕⭕
⭕⭕⭕⭕🔴⭕🔴🔴🔴⭕🔴⭕🔴⭕🔴🔴⭕🔴⭕⭕🔴⭕🔴⭕⭕⭕⭕⭕⭕
⭕⭕⭕⭕🔴⭕⭕⭕⭕⭕🔴⭕🔴🔴🔴🔴🔴⭕🔴⭕🔴🔴⭕⭕🔴⭕⭕⭕⭕
⭕⭕⭕⭕🔴🔴🔴🔴🔴🔴🔴⭕🔴🔴⭕🔴🔴⭕⭕🔴⭕⭕⭕⭕⭕⭕⭕⭕⭕
⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕
⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕
⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕
⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕

181
vendor/chillerlan/php-qrcode/tests/QRCodeTest.php vendored Normal file → Executable file
View File

@ -13,128 +13,139 @@
namespace chillerlan\QRCodeTest;
use chillerlan\QRCode\{QROptions, QRCode};
use chillerlan\QRCode\Data\{AlphaNum, Byte, Number, QRCodeDataException};
use chillerlan\QRCode\Data\{AlphaNum, Byte, Kanji, Number, QRCodeDataException};
use chillerlan\QRCode\Output\QRCodeOutputException;
use chillerlan\QRCodeExamples\MyCustomOutput;
use PHPUnit\Framework\TestCase;
use function random_bytes;
class QRCodeTest extends QRTestAbstract{
/**
* Tests basic functions of the QRCode class
*/
class QRCodeTest extends TestCase{
protected $FQCN = QRCode::class;
/** @internal */
protected QRCode $qrcode;
/** @internal */
protected QROptions $options;
/**
* @var \chillerlan\QRCode\QRCode
* invoke test instances
*
* @internal
*/
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],'],
];
$this->qrcode = new QRCode;
$this->options = new QROptions;
}
/**
* @dataProvider typeDataProvider
* @param $type
* isNumber() should pass on any number and fail on anything else
*/
public function testRenderImage($type, $expected){
$this->qrcode = $this->reflection->newInstanceArgs([new QROptions(['outputType' => $type])]);
public function testIsNumber():void{
$this::assertTrue($this->qrcode->isNumber('0123456789'));
$this->assertStringContainsString($expected, $this->qrcode->render('test'));
$this::assertFalse($this->qrcode->isNumber('ABC123'));
}
public function testInitDataInterfaceException(){
/**
* isAlphaNum() should pass on the 45 defined characters and fail on anything else (e.g. lowercase)
*/
public function testIsAlphaNum():void{
$this::assertTrue($this->qrcode->isAlphaNum('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:'));
$this::assertFalse($this->qrcode->isAlphaNum('abc'));
}
/**
* isKanji() should pass on Kanji/SJIS characters and fail on everything else
*/
public function testIsKanji():void{
$this::assertTrue($this->qrcode->isKanji('茗荷'));
$this::assertFalse($this->qrcode->isKanji('Ã'));
$this::assertFalse($this->qrcode->isKanji('ABC'));
$this::assertFalse($this->qrcode->isKanji('123'));
}
/**
* isByte() passses any binary string and only fails on empty strings
*/
public function testIsByte():void{
$this::assertTrue($this->qrcode->isByte("\x01\x02\x03"));
$this::assertTrue($this->qrcode->isByte(' ')); // not empty!
$this::assertFalse($this->qrcode->isByte(''));
}
/**
* tests if an exception is thrown when an invalid (built-in) output type is specified
*/
public function testInitDataInterfaceException():void{
$this->expectException(QRCodeOutputException::class);
$this->expectExceptionMessage('invalid output type');
(new QRCode(new QROptions(['outputType' => 'foo'])))->render('test');
$this->options->outputType = 'foo';
(new QRCode($this->options))->render('test');
}
public function testGetMatrixException(){
/**
* tests if an exception is thrown when trying to call getMatrix() without data (empty string, no data set)
*/
public function testGetMatrixException():void{
$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
/**
* test whether stings are trimmed (they are not) - i'm still torn on that (see isByte)
*/
public function testAvoidTrimming():void{
$m1 = $this->qrcode->getMatrix('hello')->matrix();
$m2 = $this->qrcode->getMatrix('hello ')->matrix(); // added space
$this->assertNotEquals($m1, $m2);
$this::assertNotSame($m1, $m2);
}
public function testImageTransparencyBGDefault(){
$this->qrcode = $this->reflection->newInstanceArgs([new QROptions(['imageTransparencyBG' => 'foo'])]);
/**
* tests if the data mode is overriden if QROptions::$dataModeOverride is set to a valid value
*
* @see https://github.com/chillerlan/php-qrcode/issues/39
*/
public function testDataModeOverride():void{
$this->assertSame([255,255,255], $this->getProperty('options')->getValue($this->qrcode)->imageTransparencyBG);
// no (or invalid) value set - auto detection
$this->options->dataModeOverride = 'foo';
$this->qrcode = new QRCode;
$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::assertInstanceOf(Kanji::class, $this->qrcode->initDataInterface('茗荷'));
// data mode set: force the given data mode
$this->options->dataModeOverride = 'Byte';
$this->qrcode = new QRCode($this->options);
$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)));
$this::assertInstanceOf(Byte::class, $this->qrcode->initDataInterface('茗荷'));
}
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(){
/**
* tests if an exception is thrown when an invalid character occurs when forcing a data mode other than Byte
*/
public function testDataModeOverrideError():void{
$this->expectException(QRCodeDataException::class);
$this->expectExceptionMessage('illegal char:');
$this->qrcode = $this->reflection->newInstanceArgs([new QROptions(['dataMode' => 'AlphaNum'])]);
$this->options->dataModeOverride = 'AlphaNum';
$this->qrcode->initDataInterface(random_bytes(32));
(new QRCode($this->options))->initDataInterface(random_bytes(32));
}
}

View File

@ -8,6 +8,8 @@
* @author smiley <smiley@chillerlan.net>
* @copyright 2018 smiley
* @license MIT
*
* @noinspection PhpUnusedLocalVariableInspection
*/
namespace chillerlan\QRCodeTest;
@ -15,66 +17,128 @@ namespace chillerlan\QRCodeTest;
use chillerlan\QRCode\{QRCode, QRCodeException, QROptions};
use PHPUnit\Framework\TestCase;
/**
* QROptions test
*/
class QROptionsTest extends TestCase{
/**
* @var \chillerlan\QRCode\QROptions
* @see testVersionClamp()
* @return int[][]
* @internal
*/
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 VersionProvider():array{
return [
'values > 40 should be clamped to 40' => [42, 40],
'values < 1 should be clamped to 1' => [-42, 1],
'values in between shold not be touched' => [21, 21],
'value -1 should be treated as is (default)' => [QRCode::VERSION_AUTO, -1],
];
}
public function testVersionMinMaxClamp(){
// normal clamp
$o = new QROptions(['versionMin' => 5, 'versionMax' => 10]);
$this->assertSame(5, $o->versionMin);
$this->assertSame(10, $o->versionMax);
/**
* Tests the $version clamping
*
* @dataProvider VersionProvider
*/
public function testVersionClamp(int $version, int $expected):void{
$o = new QROptions(['version' => $version]);
// 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);
$this::assertSame($expected, $o->version);
}
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
/**
* @see testVersionMinMaxClamp()
* @return int[][]
* @internal
*/
public function VersionMinMaxProvider():array{
return [
'normal clamp' => [5, 10, 5, 10],
'exceeding values' => [-42, 42, 1, 40],
'min > max' => [10, 5, 5, 10],
'min > max, exceeding' => [42, -42, 1, 40],
];
}
public function testInvalidEccLevelException(){
/**
* Tests the $versionMin/$versionMax clamping
*
* @dataProvider VersionMinMaxProvider
*/
public function testVersionMinMaxClamp(int $versionMin, int $versionMax, int $expectedMin, int $expectedMax):void{
$o = new QROptions(['versionMin' => $versionMin, 'versionMax' => $versionMax]);
$this::assertSame($expectedMin, $o->versionMin);
$this::assertSame($expectedMax, $o->versionMax);
}
/**
* @see testMaskPatternClamp()
* @return int[][]
* @internal
*/
public function MaskPatternProvider():array{
return [
'exceed max' => [42, 7,],
'exceed min' => [-42, 0],
'default (-1)' => [QRCode::MASK_PATTERN_AUTO, -1],
];
}
/**
* Tests the $maskPattern clamping
*
* @dataProvider MaskPatternProvider
*/
public function testMaskPatternClamp(int $maskPattern, int $expected):void{
$o = new QROptions(['maskPattern' => $maskPattern]);
$this::assertSame($expected, $o->maskPattern);
}
/**
* Tests if an exception is thrown on an incorrect ECC level
*/
public function testInvalidEccLevelException():void{
$this->expectException(QRCodeException::class);
$this->expectExceptionMessage('Invalid error correct level: 42');
new QROptions(['eccLevel' => 42]);
$o = 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]);
/**
* @see testClampRGBValues()
* @return int[][][]
* @internal
*/
public function RGBProvider():array{
return [
'exceeding values' => [[-1, 0, 999], [0, 0 ,255]],
'too few values' => [[1, 2], [255, 255, 255]],
'too many values' => [[1, 2, 3, 4, 5], [1, 2, 3]],
];
}
public function testInvalidRGBValueException(){
/**
* Tests clamping of the RGB values for $imageTransparencyBG
*
* @dataProvider RGBProvider
*/
public function testClampRGBValues(array $rgb, array $expected):void{
$o = new QROptions(['imageTransparencyBG' => $rgb]);
$this::assertSame($expected, $o->imageTransparencyBG);
}
/**
* Tests if an exception is thrown when a non-numeric RGB value was encoutered
*/
public function testInvalidRGBValueException():void{
$this->expectException(QRCodeException::class);
$this->expectExceptionMessage('Invalid RGB value.');
new QROptions(['imageTransparencyBG' => ['r', 'g', 'b']]);
$o = new QROptions(['imageTransparencyBG' => ['r', 'g', 'b']]);
}
}

View File

@ -1,72 +0,0 @@
<?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,2 @@
ko_fi: codemasher
custom: "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4"

View File

@ -0,0 +1,107 @@
# 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:
- main
pull_request:
branches:
- main
name: "CI"
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"
tools: pecl
coverage: none
extensions: ast, json
- name: "Update dependencies with composer"
run: composer update --no-interaction --no-ansi --no-progress --no-suggest
- name: "Run phan"
run: php vendor/bin/phan
build-docs:
name: "Build and publish Docs"
runs-on: ubuntu-latest
steps:
- name: "Checkout sources"
uses: actions/checkout@v2
- name: "Install PHP"
uses: shivammathur/setup-php@v2
with:
php-version: "7.4"
coverage: none
tools: phpDocumentor
extensions: json
- name: "Build Docs"
run: phpdoc --config=phpdoc.xml
- name: "Publish Docs to gh-pages"
uses: JamesIves/github-pages-deploy-action@v4.2.5
with:
branch: gh-pages
folder: docs
clean: true
tests:
name: "Unit Tests"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
php-version:
- "7.4"
- "8.0"
- "8.1"
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
extensions: json
- 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

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

View File

@ -0,0 +1,54 @@
<?php
/**
* This configuration will be read and overlaid on top of the
* default configuration. Command-line arguments will be applied
* after this file is read.
*/
return [
// Supported values: `'5.6'`, `'7.0'`, `'7.1'`, `'7.2'`, `'7.3'`,
// `'7.4'`, `null`.
// If this is set to `null`,
// then Phan assumes the PHP version which is closest to the minor version
// of the php executable used to execute Phan.
//
// Note that the **only** effect of choosing `'5.6'` is to infer
// that functions removed in php 7.0 exist.
// (See `backward_compatibility_checks` for additional options)
'target_php_version' => '7.4',
// A list of directories that should be parsed for class and
// method information. After excluding the directories
// defined in exclude_analysis_directory_list, the remaining
// files will be statically analyzed for errors.
//
// Thus, both first-party and third-party code being used by
// your application should be included in this list.
'directory_list' => [
'examples',
'src',
'tests',
'vendor',
],
// A regex used to match every file name that you want to
// exclude from parsing. Actual value will exclude every
// "test", "tests", "Test" and "Tests" folders found in
// "vendor/" directory.
'exclude_file_regex' => '@^vendor/.*/(tests?|Tests?)/@',
// A directory list that defines files that will be excluded
// from static analysis, but whose class and method
// information should be included.
//
// Generally, you'll want to include the directories for
// third-party code (such as "vendor/") in this list.
//
// n.b.: If you'd like to parse but not analyze 3rd
// party code, directories containing that code
// should be added to both the `directory_list`
// and `exclude_analysis_directory_list` arrays.
'exclude_analysis_directory_list' => [
'tests',
'vendor',
],
];

View File

@ -1,3 +1,12 @@
build:
nodes:
analysis:
tests:
override:
- php-scrutinizer-run
environment:
php: 8.0.0
filter:
excluded_paths:
- examples/*

View File

@ -1,18 +0,0 @@
language: php
matrix:
include:
- php: 7.2
- php: 7.3
- php: 7.4
- php: 8.0
- php: nightly
allow_failures:
- php: nightly
before_script: 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)

View File

@ -1,52 +1,46 @@
# 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
A container class for immutable settings objects. Not a DI container. PHP 7.4+
- [`SettingsContainerInterface`](https://github.com/chillerlan/php-settings-container/blob/main/src/SettingsContainerInterface.php) provides immutable properties with magic getter & setter and some fancy - decouple configuration logic from your application!
[![PHP Version Support][php-badge]][php]
[![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]
[![Continuous Integration][gh-action-badge]][gh-action]
[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-settings-container.svg?style=flat-square
[php-badge]: https://img.shields.io/packagist/php-v/chillerlan/php-settings-container?logo=php&color=8892BF
[php]: https://www.php.net/supported-versions.php
[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-settings-container.svg?logo=packagist
[packagist]: https://packagist.org/packages/chillerlan/php-settings-container
[license-badge]: https://img.shields.io/github/license/chillerlan/php-settings-container.svg?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
[license-badge]: https://img.shields.io/github/license/chillerlan/php-settings-container.svg
[license]: https://github.com/chillerlan/php-settings-container/blob/main/LICENSE
[coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-settings-container.svg?logo=codecov
[coverage]: https://codecov.io/github/chillerlan/php-settings-container
[scrutinizer-badge]: https://img.shields.io/scrutinizer/g/chillerlan/php-settings-container.svg?style=flat-square
[scrutinizer-badge]: https://img.shields.io/scrutinizer/g/chillerlan/php-settings-container.svg?logo=scrutinizer
[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-badge]: https://img.shields.io/packagist/dt/chillerlan/php-settings-container.svg?logo=packagist
[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
[gh-action-badge]: https://github.com/chillerlan/php-settings-container/workflows/CI/badge.svg
[gh-action]: https://github.com/chillerlan/php-settings-container/actions?query=workflow%3A%22CI%22
## Documentation
### Installation
**requires [composer](https://getcomposer.org)**
*composer.json* (note: replace `dev-master` with a version boundary)
*composer.json* (note: replace `dev-main` with a [version constraint](https://getcomposer.org/doc/articles/versions.md#writing-version-constraints), e.g. `^2.1` - see [releases](https://github.com/chillerlan/php-settings-container/releases) for valid versions)
```json
{
"require": {
"php": "^7.2",
"chillerlan/php-settings-container": "^1.0"
"php": "^7.4",
"chillerlan/php-settings-container": "dev-main"
}
}
```
### 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
@ -140,7 +134,7 @@ var_dump($container->what); // -> md5 hash of "some value"
### API
#### [`SettingsContainerAbstract`](https://github.com/chillerlan/php-settings-container/blob/master/src/SettingsContainerAbstract.php)
#### [`SettingsContainerAbstract`](https://github.com/chillerlan/php-settings-container/blob/main/src/SettingsContainerAbstract.php)
method | return | info
-------- | ---- | -----------

View File

@ -1,12 +1,12 @@
{
"name": "chillerlan/php-settings-container",
"description": "A container class for immutable settings objects. Not a DI container. PHP 7.2+",
"description": "A container class for immutable settings objects. Not a DI container. PHP 7.4+",
"homepage": "https://github.com/chillerlan/php-settings-container",
"license": "MIT",
"type": "library",
"minimum-stability": "stable",
"keywords": [
"php7", "helper", "container", "settings"
"php7", "helper", "container", "settings", "configuration"
],
"authors": [
{
@ -20,11 +20,12 @@
"source": "https://github.com/chillerlan/php-settings-container"
},
"require": {
"php": "^7.2 || ^8.0",
"php": "^7.4 || ^8.0",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^8.4"
"phan/phan": "^5.3",
"phpunit/phpunit": "^9.5"
},
"autoload": {
"psr-4": {
@ -36,5 +37,14 @@
"chillerlan\\SettingsTest\\": "tests/",
"chillerlan\\SettingsExamples\\": "examples/"
}
},
"scripts": {
"phpunit": "@php vendor/bin/phpunit",
"phan": "@php vendor/bin/phan"
},
"config": {
"lock": false,
"sort-packages": true,
"platform-check": true
}
}

View File

@ -0,0 +1,13 @@
# Auto generated API documentation
The API documentation can be auto generated with [phpDocumentor](https://www.phpdoc.org/).
There is an [online version available](https://chillerlan.github.io/php-settings-container/) via the [gh-pages branch](https://github.com/chillerlan/php-settings-container/tree/gh-pages) that is [automatically deployed](https://github.com/chillerlan/php-settings-container/deployments) on each push to main.
Locally created docs will appear in this directory. If you'd like to create local docs, please follow these steps:
- [download phpDocumentor](https://github.com/phpDocumentor/phpDocumentor/releases) v3+ as .phar archive
- run it in the repository root directory:
- on Windows `c:\path\to\php.exe c:\path\to\phpDocumentor.phar --config=phpdoc.xml`
- on Linux just `php /path/to/phpDocumentor.phar --config=phpdoc.xml`
- open [index.html](./index.html) in a browser
- profit!

View File

@ -1,6 +1,5 @@
<?php
/**
* @filesource advanced.php
* @created 28.08.2018
* @author smiley <smiley@chillerlan.net>
* @copyright 2018 smiley
@ -15,7 +14,7 @@ require_once __DIR__.'/../vendor/autoload.php';
// from library #1
trait SomeOptions{
protected $foo;
protected string $foo = '';
// this method will be called in SettingsContainerAbstract::__construct() after the properties have been set
protected function SomeOptions(){
@ -26,7 +25,7 @@ trait SomeOptions{
// from library #2
trait MoreOptions{
protected $bar = 'whatever'; // provide default values
protected string $bar = 'whatever'; // provide default values
}
$commonOptions = [
@ -37,10 +36,16 @@ $commonOptions = [
];
// now plug the several library options together to a single object
/** @var \chillerlan\Settings\SettingsContainerInterface $container */
$container = new class ($commonOptions) extends SettingsContainerAbstract{
/**
* @property string $foo
* @property string $bar
*/
class MySettings extends SettingsContainerAbstract{
use SomeOptions, MoreOptions; // ...
};
$container = new MySettings($commonOptions);
var_dump($container->foo); // -> WHATEVER (constructor ran strtoupper on the value)
var_dump($container->bar); // -> nothing

View File

@ -1,6 +1,5 @@
<?php
/**
* @filesource simple.php
* @created 28.08.2018
* @author smiley <smiley@chillerlan.net>
* @copyright 2018 smiley

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<phpdoc>
<parser>
<target>docs</target>
<encoding>utf8</encoding>
<markers>
<item>TODO</item>
</markers>
</parser>
<transformer>
<target>docs</target>
</transformer>
<files>
<directory>src</directory>
<directory>tests</directory>
</files>
<transformations>
<template name="responsive-twig"/>
</transformations>
</phpdoc>

View File

@ -1,35 +0,0 @@
<?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

@ -1,22 +1,26 @@
<?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"
<?xml version="1.0"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/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-settings-container test suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
<report>
<clover outputFile=".build/coverage/clover.xml"/>
<xml outputDirectory=".build/coverage/coverage-xml"/>
</report>
</coverage>
<testsuites>
<testsuite name="php-settings-container test suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
<logging>
<junit outputFile=".build/logs/junit.xml"/>
</logging>
</phpunit>

View File

@ -2,9 +2,7 @@
/**
* Class SettingsContainerAbstract
*
* @filesource SettingsContainerAbstract.php
* @created 28.08.2018
* @package chillerlan\Settings
* @author Smiley <smiley@chillerlan.net>
* @copyright 2018 Smiley
* @license MIT
@ -12,16 +10,15 @@
namespace chillerlan\Settings;
use Exception, ReflectionClass, ReflectionProperty;
use ReflectionClass, ReflectionProperty;
use function call_user_func, call_user_func_array, get_object_vars, json_decode, json_encode, method_exists, property_exists;
use function get_object_vars, json_decode, json_encode, method_exists, property_exists;
use const JSON_THROW_ON_ERROR;
abstract class SettingsContainerAbstract implements SettingsContainerInterface{
/**
* SettingsContainerAbstract constructor.
*
* @param iterable|null $properties
*/
public function __construct(iterable $properties = null){
@ -35,8 +32,6 @@ abstract class SettingsContainerAbstract implements SettingsContainerInterface{
/**
* 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();
@ -45,7 +40,7 @@ abstract class SettingsContainerAbstract implements SettingsContainerInterface{
$method = $trait->getShortName();
if(method_exists($this, $method)){
call_user_func([$this, $method]);
$this->{$method}();
}
}
@ -56,16 +51,17 @@ abstract class SettingsContainerAbstract implements SettingsContainerInterface{
*/
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};
if(!property_exists($this, $property) || $this->isPrivate($property)){
return null;
}
return null;
$method = 'get_'.$property;
if(method_exists($this, $method)){
return $this->{$method}();
}
return $this->{$property};
}
/**
@ -77,8 +73,10 @@ abstract class SettingsContainerAbstract implements SettingsContainerInterface{
return;
}
if(method_exists($this, 'set_'.$property)){
call_user_func_array([$this, 'set_'.$property], [$value]);
$method = 'set_'.$property;
if(method_exists($this, $method)){
$this->{$method}($value);
return;
}
@ -95,10 +93,6 @@ abstract class SettingsContainerAbstract implements SettingsContainerInterface{
/**
* @internal Checks if a property is private
*
* @param string $property
*
* @return bool
*/
protected function isPrivate(string $property):bool{
return (new ReflectionProperty($this, $property))->isPrivate();
@ -152,12 +146,7 @@ abstract class SettingsContainerAbstract implements SettingsContainerInterface{
* @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');
}
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
return $this->fromIterable($data);
}
@ -165,7 +154,7 @@ abstract class SettingsContainerAbstract implements SettingsContainerInterface{
/**
* @inheritdoc
*/
public function jsonSerialize(){
public function jsonSerialize():array{
return $this->toArray();
}

View File

@ -2,9 +2,7 @@
/**
* Interface SettingsContainerInterface
*
* @filesource SettingsContainerInterface.php
* @created 28.08.2018
* @package chillerlan\Settings
* @author Smiley <smiley@chillerlan.net>
* @copyright 2018 Smiley
* @license MIT
@ -22,9 +20,7 @@ interface SettingsContainerInterface extends JsonSerializable{
/**
* Retrieve the value of $property
*
* @param string $property
*
* @return mixed
* @return mixed|null
*/
public function __get(string $property);
@ -33,69 +29,43 @@ interface SettingsContainerInterface extends JsonSerializable{
*
* @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
*/

View File

@ -2,9 +2,7 @@
/**
* Class ContainerTraitTest
*
* @filesource ContainerTraitTest.php
* @created 28.08.2018
* @package chillerlan\SettingsTest
* @author Smiley <smiley@chillerlan.net>
* @copyright 2018 Smiley
* @license MIT
@ -13,74 +11,86 @@
namespace chillerlan\SettingsTest;
use PHPUnit\Framework\TestCase;
use Exception, TypeError;
use JsonException, TypeError;
use function sha1;
class ContainerTraitTest extends TestCase{
class ContainerTest extends TestCase{
public function testConstruct(){
$container = new TestContainer([
'test1' => 'test1',
'test2' => 'test2',
'test2' => true,
'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('test1', $container->test1);
$this::assertSame(true, $container->test2);
$this::assertNull($container->test3);
$this::assertSame('test4', $container->test4);
$this->assertSame('success', $container->testConstruct);
$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);
$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));
$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);
$this::assertSame(sha1('foo'), $container->test6);
// nullable/isset test
$container->test6 = null;
$this->assertFalse(isset($container->test6));
$this->assertSame('null', $container->test6);
$this::assertFalse(isset($container->test6));
$this::assertSame('null', $container->test6);
}
public function testSet(){
$container = new TestContainer;
$container->test1 = 'bar';
$container->test2 = 'what';
$container->test2 = false;
$container->test3 = 'nope';
$this->assertSame('bar', $container->test1);
$this->assertSame('what', $container->test2);
$this->assertNull($container->test3);
$this::assertSame('bar', $container->test1);
$this::assertSame(false, $container->test2);
$this::assertNull($container->test3);
// unset
unset($container->test1);
$this->assertFalse(isset($container->test1));
$this::assertFalse(isset($container->test1));
// custom setter
$container->test5 = 'bar';
$this->assertSame('bar_test5', $container->test5);
$this::assertSame('bar_test5', $container->test5);
}
public function testToArray(){
$container = new TestContainer(['test1' => 'no', 'test2' => true, 'testConstruct' => 'success']);
$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());
$this::assertSame([
'test1' => 'no',
'test2' => true,
'testConstruct' => 'success',
'test4' => null,
'test5' => null,
'test6' => null
], $container->toArray());
}
public function testToJSON(){
@ -88,12 +98,12 @@ class ContainerTraitTest extends TestCase{
$expected = '{"test1":"no","test2":true,"testConstruct":"success","test4":null,"test5":null,"test6":null}';
$this->assertSame($expected, $container->toJSON());
$this->assertSame($expected, (string)$container);
$this::assertSame($expected, $container->toJSON());
$this::assertSame($expected, (string)$container);
}
public function testFromJsonException(){
$this->expectException(Exception::class);
$this->expectException(JsonException::class);
(new TestContainer)->fromJSON('-');
}

View File

@ -2,9 +2,7 @@
/**
* Class TestContainer
*
* @filesource TestContainer.php
* @created 28.08.2018
* @package chillerlan\SettingsTest
* @author Smiley <smiley@chillerlan.net>
* @copyright 2018 Smiley
* @license MIT
@ -25,5 +23,5 @@ use chillerlan\Settings\SettingsContainerAbstract;
class TestContainer extends SettingsContainerAbstract{
use TestOptionsTrait;
private $test3 = 'what';
private string $test3 = 'what';
}

View File

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

View File

@ -72,34 +72,34 @@
},
{
"name": "chillerlan/php-qrcode",
"version": "3.4.1",
"version_normalized": "3.4.1.0",
"version": "4.3.3",
"version_normalized": "4.3.3.0",
"source": {
"type": "git",
"url": "https://github.com/chillerlan/php-qrcode.git",
"reference": "468603b687a5fe75c1ff33857a45f1726c7b95a9"
"reference": "6356b246948ac1025882b3f55e7c68ebd4515ae3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/468603b687a5fe75c1ff33857a45f1726c7b95a9",
"reference": "468603b687a5fe75c1ff33857a45f1726c7b95a9",
"url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/6356b246948ac1025882b3f55e7c68ebd4515ae3",
"reference": "6356b246948ac1025882b3f55e7c68ebd4515ae3",
"shasum": ""
},
"require": {
"chillerlan/php-settings-container": "^1.2.2",
"chillerlan/php-settings-container": "^2.1",
"ext-mbstring": "*",
"php": "^7.2 || ^8.0"
"php": "^7.4 || ^8.0"
},
"require-dev": {
"phan/phan": "^3.2.2",
"phpunit/phpunit": "^8.5",
"phan/phan": "^5.3",
"phpunit/phpunit": "^9.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",
"time": "2021-11-25T22:38:09+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -126,7 +126,7 @@
"homepage": "https://github.com/chillerlan/php-qrcode/graphs/contributors"
}
],
"description": "A QR code generator. PHP 7.2+",
"description": "A QR code generator. PHP 7.4+",
"homepage": "https://github.com/chillerlan/php-qrcode",
"keywords": [
"phpqrcode",
@ -135,6 +135,10 @@
"qrcode",
"qrcode-generator"
],
"support": {
"issues": "https://github.com/chillerlan/php-qrcode/issues",
"source": "https://github.com/chillerlan/php-qrcode/tree/4.3.3"
},
"funding": [
{
"url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4",
@ -149,27 +153,28 @@
},
{
"name": "chillerlan/php-settings-container",
"version": "1.2.2",
"version_normalized": "1.2.2.0",
"version": "2.1.3",
"version_normalized": "2.1.3.0",
"source": {
"type": "git",
"url": "https://github.com/chillerlan/php-settings-container.git",
"reference": "d1b5284d6eb3a767459738bb0b20073f0cb3eeaf"
"reference": "125dd573b45ffc7cabecf385986a356ba2c6f602"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/d1b5284d6eb3a767459738bb0b20073f0cb3eeaf",
"reference": "d1b5284d6eb3a767459738bb0b20073f0cb3eeaf",
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/125dd573b45ffc7cabecf385986a356ba2c6f602",
"reference": "125dd573b45ffc7cabecf385986a356ba2c6f602",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": "^7.2 || ^8.0"
"php": "^7.4 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^8.4"
"phan/phan": "^5.3",
"phpunit/phpunit": "^9.5"
},
"time": "2021-09-03T17:33:25+00:00",
"time": "2022-03-09T13:18:58+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -188,14 +193,19 @@
"homepage": "https://github.com/codemasher"
}
],
"description": "A container class for immutable settings objects. Not a DI container. PHP 7.2+",
"description": "A container class for immutable settings objects. Not a DI container. PHP 7.4+",
"homepage": "https://github.com/chillerlan/php-settings-container",
"keywords": [
"PHP7",
"Settings",
"configuration",
"container",
"helper"
],
"support": {
"issues": "https://github.com/chillerlan/php-settings-container/issues",
"source": "https://github.com/chillerlan/php-settings-container"
},
"funding": [
{
"url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4",

View File

@ -3,7 +3,7 @@
'name' => '__root__',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => 'b148d2f5153f9711120b3a3ac50ee84509c9cdfb',
'reference' => '1e668c7572346f6adc47cc0359c2fd616481e730',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -13,7 +13,7 @@
'__root__' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => 'b148d2f5153f9711120b3a3ac50ee84509c9cdfb',
'reference' => '1e668c7572346f6adc47cc0359c2fd616481e730',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -29,18 +29,18 @@
'dev_requirement' => false,
),
'chillerlan/php-qrcode' => array(
'pretty_version' => '3.4.1',
'version' => '3.4.1.0',
'reference' => '468603b687a5fe75c1ff33857a45f1726c7b95a9',
'pretty_version' => '4.3.3',
'version' => '4.3.3.0',
'reference' => '6356b246948ac1025882b3f55e7c68ebd4515ae3',
'type' => 'library',
'install_path' => __DIR__ . '/../chillerlan/php-qrcode',
'aliases' => array(),
'dev_requirement' => false,
),
'chillerlan/php-settings-container' => array(
'pretty_version' => '1.2.2',
'version' => '1.2.2.0',
'reference' => 'd1b5284d6eb3a767459738bb0b20073f0cb3eeaf',
'pretty_version' => '2.1.3',
'version' => '2.1.3.0',
'reference' => '125dd573b45ffc7cabecf385986a356ba2c6f602',
'type' => 'library',
'install_path' => __DIR__ . '/../chillerlan/php-settings-container',
'aliases' => array(),

View File

@ -4,8 +4,8 @@
$issues = array();
if (!(PHP_VERSION_ID >= 70200)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.0". You are running ' . PHP_VERSION . '.';
if (!(PHP_VERSION_ID >= 70400)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {