port totp mfa from streams with some adjustions

This commit is contained in:
Mario
2023-03-08 10:04:29 +00:00
parent d43a56614c
commit 234bb64250
109 changed files with 17279 additions and 14 deletions

View File

@@ -0,0 +1,72 @@
<?php
namespace Zotlabs\Module\Settings;
use App;
use chillerlan\QRCode\QRCode;
use Zotlabs\Lib\AConfig;
use Zotlabs\Lib\System;
use OTPHP\TOTP;
use ParagonIE\ConstantTime\Base32;
class Multifactor {
public function post() {
$account = App::get_account();
if (!$account) {
return;
}
$enable_mfa = isset($_POST['enable_mfa']) ? (int) $_POST['enable_mfa'] : false;
AConfig::Set($account['account_id'], 'system', 'mfa_enabled', $enable_mfa);
}
public function get() {
$account = App::get_account();
if (!$account) {
return '';
}
if (!$account['account_external']) {
$otp = TOTP::create();
$otp->setLabel($account['account_email']);
// $otp->setLabel(rawurlencode(System::get_platform_name()));
$otp->setIssuer(rawurlencode(System::get_platform_name()));
$mySecret = trim(Base32::encodeUpper(random_bytes(32)), '=');
$otp = TOTP::create($mySecret);
q("UPDATE account set account_external = '%s' where account_id = %d",
dbesc($otp->getSecret()),
intval($account['account_id'])
);
$account['account_external'] = $otp->getSecret();
}
$otp = TOTP::create($account['account_external']);
$otp->setLabel($account['account_email']);
$otp->setIssuer(rawurlencode(System::get_platform_name()));
$uri = $otp->getProvisioningUri();
return replace_macros(get_markup_template('totp_setup.tpl'),
[
'$form_security_token' => get_form_security_token("settings_mfa"),
'$title' => t('Multifactor Settings'),
'$totp_setup_text' => t('Multi-Factor Authentication Setup'),
'$secret_text' => t('This is your generated secret. This may be used in some cases if the QR image cannot be read. Please save it.'),
'$test_title' => t('Please enter the code from your authenticator'),
'$qrcode' => (new QRCode())->render($uri),
'$uri' => $uri,
'$secret' => ($account['account_external'] ?? ''),
'$test_pass' => t("That code is correct."),
'$test_fail' => t("Incorrect code."),
'$enable_mfa' => [
'enable_mfa',
t('Enable Multi-factor Authentication'),
AConfig::Get($account['account_id'], 'system', 'mfa_enabled'),
'',
[t('No'), t('Yes')]
],
'$submit' => t('Submit'),
'$test' => t('Test')
]
);
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Zotlabs\Module;
use App;
use Zotlabs\Web\Controller;
use OTPHP\TOTP;
class Totp_check extends Controller {
public function post() {
$retval = ['status' => false];
$static = $_POST['totp_code_static'] ?? false;
if (!local_channel()) {
if ($static) {
goaway(z_root());
}
json_return_and_die($retval);
}
$account = App::get_account();
if (!$account) {
json_return_and_die($retval);
}
$secret = $account['account_external'];
$input = (isset($_POST['totp_code'])) ? trim($_POST['totp_code']) : '';
if ($secret && $input) {
$otp = TOTP::create($secret); // create TOTP object from the secret.
if ($otp->verify($_POST['totp_code']) || $input === $secret ) {
logger('otp_success');
$_SESSION['2FA_VERIFIED'] = true;
if ($static) {
goaway(z_root());
}
$retval['status'] = true;
json_return_and_die($retval);
}
logger('otp_fail');
}
if ($static) {
if(empty($_SESSION['totp_try_count'])) {
$_SESSION['totp_try_count'] = 1;
}
if ($_SESSION['totp_try_count'] > 2) {
goaway('logout');
}
$_SESSION['totp_try_count']++;
goaway(z_root());
}
json_return_and_die($retval);
}
public function get() {
if (!local_channel()) {
return;
}
$account = App::get_account();
if (!$account) {
return t('Account not found.');
}
$id = $account['account_email'];
return replace_macros(get_markup_template('totp.tpl'),
[
'$header' => t('Multifactor Verification'),
'$id' => $id,
'$desc' => t('Please enter the verification key from your authenticator app'),
//'$success' => t('Success!'),
//'$fail' => t('Invalid code, please try again.'),
//'$maxfails' => t('Too many invalid codes...'),
'$submit' => t('Verify'),
'$static' => $static
]
);
}
}

View File

@@ -45,7 +45,9 @@
"blueimp/jquery-file-upload": "^10.3", "blueimp/jquery-file-upload": "^10.3",
"desandro/imagesloaded": "^4.1", "desandro/imagesloaded": "^4.1",
"phpseclib/phpseclib": "~2.0", "phpseclib/phpseclib": "~2.0",
"jbroadway/urlify": "^1.2" "jbroadway/urlify": "^1.2",
"chillerlan/php-qrcode": "^4.3",
"spomky-labs/otphp": "^11.1"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^9.4", "phpunit/phpunit": "^9.4",

291
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "9bcd9511f1fd87b42eb4381dae723dfb", "content-hash": "12bbabd1d50360fa40c1bcd8d8458c2d",
"packages": [ "packages": [
{ {
"name": "blueimp/jquery-file-upload", "name": "blueimp/jquery-file-upload",
@@ -194,6 +194,148 @@
}, },
"time": "2022-05-31T16:12:58+00:00" "time": "2022-05-31T16:12:58+00:00"
}, },
{
"name": "chillerlan/php-qrcode",
"version": "4.3.4",
"source": {
"type": "git",
"url": "https://github.com/chillerlan/php-qrcode.git",
"reference": "2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d",
"reference": "2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d",
"shasum": ""
},
"require": {
"chillerlan/php-settings-container": "^2.1.4",
"ext-mbstring": "*",
"php": "^7.4 || ^8.0"
},
"require-dev": {
"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."
},
"type": "library",
"autoload": {
"psr-4": {
"chillerlan\\QRCode\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kazuhiko Arase",
"homepage": "https://github.com/kazuhikoarase"
},
{
"name": "Smiley",
"email": "smiley@chillerlan.net",
"homepage": "https://github.com/codemasher"
},
{
"name": "Contributors",
"homepage": "https://github.com/chillerlan/php-qrcode/graphs/contributors"
}
],
"description": "A QR code generator. PHP 7.4+",
"homepage": "https://github.com/chillerlan/php-qrcode",
"keywords": [
"phpqrcode",
"qr",
"qr code",
"qrcode",
"qrcode-generator"
],
"support": {
"issues": "https://github.com/chillerlan/php-qrcode/issues",
"source": "https://github.com/chillerlan/php-qrcode/tree/4.3.4"
},
"funding": [
{
"url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4",
"type": "custom"
},
{
"url": "https://ko-fi.com/codemasher",
"type": "ko_fi"
}
],
"time": "2022-07-25T09:12:45+00:00"
},
{
"name": "chillerlan/php-settings-container",
"version": "2.1.4",
"source": {
"type": "git",
"url": "https://github.com/chillerlan/php-settings-container.git",
"reference": "1beb7df3c14346d4344b0b2e12f6f9a74feabd4a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/1beb7df3c14346d4344b0b2e12f6f9a74feabd4a",
"reference": "1beb7df3c14346d4344b0b2e12f6f9a74feabd4a",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": "^7.4 || ^8.0"
},
"require-dev": {
"phan/phan": "^5.3",
"phpunit/phpunit": "^9.5"
},
"type": "library",
"autoload": {
"psr-4": {
"chillerlan\\Settings\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Smiley",
"email": "smiley@chillerlan.net",
"homepage": "https://github.com/codemasher"
}
],
"description": "A container class for immutable settings objects. Not a DI container. 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",
"type": "custom"
},
{
"url": "https://ko-fi.com/codemasher",
"type": "ko_fi"
}
],
"time": "2022-07-05T22:32:14+00:00"
},
{ {
"name": "commerceguys/intl", "name": "commerceguys/intl",
"version": "v1.1.1", "version": "v1.1.1",
@@ -602,6 +744,73 @@
}, },
"time": "2022-09-26T12:21:08+00:00" "time": "2022-09-26T12:21:08+00:00"
}, },
{
"name": "paragonie/constant_time_encoding",
"version": "v2.6.3",
"source": {
"type": "git",
"url": "https://github.com/paragonie/constant_time_encoding.git",
"reference": "58c3f47f650c94ec05a151692652a868995d2938"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938",
"reference": "58c3f47f650c94ec05a151692652a868995d2938",
"shasum": ""
},
"require": {
"php": "^7|^8"
},
"require-dev": {
"phpunit/phpunit": "^6|^7|^8|^9",
"vimeo/psalm": "^1|^2|^3|^4"
},
"type": "library",
"autoload": {
"psr-4": {
"ParagonIE\\ConstantTime\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com",
"role": "Maintainer"
},
{
"name": "Steve 'Sc00bz' Thomas",
"email": "steve@tobtu.com",
"homepage": "https://www.tobtu.com",
"role": "Original Developer"
}
],
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
"keywords": [
"base16",
"base32",
"base32_decode",
"base32_encode",
"base64",
"base64_decode",
"base64_encode",
"bin2hex",
"encoding",
"hex",
"hex2bin",
"rfc4648"
],
"support": {
"email": "info@paragonie.com",
"issues": "https://github.com/paragonie/constant_time_encoding/issues",
"source": "https://github.com/paragonie/constant_time_encoding"
},
"time": "2022-06-14T06:56:20+00:00"
},
{ {
"name": "pear/text_languagedetect", "name": "pear/text_languagedetect",
"version": "v1.0.1", "version": "v1.0.1",
@@ -1579,6 +1788,86 @@
}, },
"time": "2022-11-22T21:47:32+00:00" "time": "2022-11-22T21:47:32+00:00"
}, },
{
"name": "spomky-labs/otphp",
"version": "11.1.0",
"source": {
"type": "git",
"url": "https://github.com/Spomky-Labs/otphp.git",
"reference": "4849ac1aa560bfc56c0d1534b0d72532da4665ab"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/4849ac1aa560bfc56c0d1534b0d72532da4665ab",
"reference": "4849ac1aa560bfc56c0d1534b0d72532da4665ab",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"paragonie/constant_time_encoding": "^2.0",
"php": "^8.1"
},
"require-dev": {
"ekino/phpstan-banned-code": "^1.0",
"infection/infection": "^0.26",
"php-parallel-lint/php-parallel-lint": "^1.3",
"phpstan/phpstan": "^1.0",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": "^9.5.26",
"qossmic/deptrac-shim": "^1.0",
"rector/rector": "^0.14",
"symfony/phpunit-bridge": "^6.1",
"symplify/easy-coding-standard": "^11.0"
},
"type": "library",
"autoload": {
"psr-4": {
"OTPHP\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky"
},
{
"name": "All contributors",
"homepage": "https://github.com/Spomky-Labs/otphp/contributors"
}
],
"description": "A PHP library for generating one time passwords according to RFC 4226 (HOTP Algorithm) and the RFC 6238 (TOTP Algorithm) and compatible with Google Authenticator",
"homepage": "https://github.com/Spomky-Labs/otphp",
"keywords": [
"FreeOTP",
"RFC 4226",
"RFC 6238",
"google authenticator",
"hotp",
"otp",
"totp"
],
"support": {
"issues": "https://github.com/Spomky-Labs/otphp/issues",
"source": "https://github.com/Spomky-Labs/otphp/tree/11.1.0"
},
"funding": [
{
"url": "https://github.com/Spomky",
"type": "github"
},
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2022-11-11T12:57:17+00:00"
},
{ {
"name": "symfony/polyfill-php81", "name": "symfony/polyfill-php81",
"version": "v1.26.0", "version": "v1.26.0",

View File

@@ -10,6 +10,7 @@
*/ */
use Zotlabs\Lib\Libzot; use Zotlabs\Lib\Libzot;
use Zotlabs\Lib\AConfig;
require_once('include/api_auth.php'); require_once('include/api_auth.php');
require_once('include/security.php'); require_once('include/security.php');
@@ -263,8 +264,16 @@ if((isset($_SESSION)) && (x($_SESSION, 'authenticated')) &&
App::$session->extend_cookie(); App::$session->extend_cookie();
$login_refresh = true; $login_refresh = true;
} }
$multiFactor = AConfig::Get(App::$account['account_id'], 'system', 'mfa_enabled');
if ($multiFactor && empty($_SESSION['2FA_VERIFIED']) && App::$module !== 'totp_check') {
$o = new Zotlabs\Module\Totp_check;
echo $o->get(true);
killme();
}
$ch = (($_SESSION['uid']) ? channelx_by_n($_SESSION['uid']) : null); $ch = (($_SESSION['uid']) ? channelx_by_n($_SESSION['uid']) : null);
authenticate_success($r[0], null, $ch, false, false, $login_refresh); authenticate_success($r[0], $ch, false, false, $login_refresh);
} }
else { else {
$_SESSION['account_id'] = 0; $_SESSION['account_id'] = 0;

View File

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

View File

@@ -0,0 +1,82 @@
# 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:
- v4.3.x
pull_request:
branches:
- v4.3.x
name: "Continuous Integration"
jobs:
static-code-analysis:
name: "Static Code Analysis"
runs-on: ubuntu-latest
env:
PHAN_ALLOW_XDEBUG: 0
PHAN_DISABLE_XDEBUG_WARN: 1
steps:
- name: "Checkout"
uses: actions/checkout@v3
- name: "Install PHP"
uses: shivammathur/setup-php@v2
with:
php-version: "7.4"
coverage: none
tools: pecl
extensions: ast, gd, imagick, json, mbstring
- name: "Update dependencies with composer"
run: composer update --no-interaction --no-ansi --no-progress --no-suggest
- name: "Run phan"
run: php vendor/bin/phan
tests:
name: "Unit Tests"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
php-version:
- "7.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@v3
- name: "Install PHP with extensions"
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
coverage: pcov
tools: pecl
extensions: gd, imagick, json, mbstring
- name: "Install dependencies with composer"
run: composer update --no-ansi --no-interaction --no-progress --no-suggest
- name: "Run tests with phpunit"
run: php vendor/bin/phpunit --configuration=phpunit.xml
- name: "Send code coverage report to Codecov.io"
uses: codecov/codecov-action@v3

View File

@@ -0,0 +1,5 @@
.build/*
.idea/*
docs/*
vendor/*
composer.lock

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

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

21
vendor/chillerlan/php-qrcode/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Smiley <smiley@chillerlan.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

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

@@ -0,0 +1,422 @@
# chillerlan/php-qrcode
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.
**Attention:** there is now also a javascript port: [chillerlan/js-qrcode](https://github.com/chillerlan/js-qrcode).
[![PHP Version Support][php-badge]][php]
[![Packagist version][packagist-badge]][packagist]
[![License][license-badge]][license]
[![CodeCov][coverage-badge]][coverage]
[![Scrunitizer CI][scrutinizer-badge]][scrutinizer]
[![Packagist downloads][downloads-badge]][downloads]<br/>
[![Continuous Integration][gh-action-badge]][gh-action]
[php-badge]: https://img.shields.io/packagist/php-v/chillerlan/php-qrcode?logo=php&color=8892BF
[php]: https://www.php.net/supported-versions.php
[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-qrcode.svg?logo=packagist
[packagist]: https://packagist.org/packages/chillerlan/php-qrcode
[license-badge]: https://img.shields.io/github/license/chillerlan/php-qrcode.svg
[license]: https://github.com/chillerlan/php-qrcode/blob/main/LICENSE
[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?logo=scrutinizer
[scrutinizer]: https://scrutinizer-ci.com/g/chillerlan/php-qrcode
[downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-qrcode.svg?logo=packagist
[downloads]: https://packagist.org/packages/chillerlan/php-qrcode/stats
[gh-action-badge]: https://github.com/chillerlan/php-qrcode/workflows/Continuous%20Integration/badge.svg
[gh-action]: https://github.com/chillerlan/php-qrcode/actions?query=workflow%3A%22Continuous+Integration%22+branch%3Av4.3.x
# Documentation
## Requirements
- 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
## Installation
**requires [composer](https://getcomposer.org)**
via terminal: `composer require chillerlan/php-qrcode`
*composer.json*
```json
{
"require": {
"php": "^7.4 || ^8.0",
"chillerlan/php-qrcode": "v4.3.x-dev"
}
}
```
Note: replace `v4.3.x-dev` with a [version constraint](https://getcomposer.org/doc/articles/versions.md#writing-version-constraints), e.g. `^4.3` - see [releases](https://github.com/chillerlan/php-qrcode/releases) for valid versions.
For PHP version ...
- 7.4+ use `^4.3`
- 7.2+ use `^3.4.1` (v3.4.1 also supports PHP8)
- 7.0+ use `^2.0`
- 5.6+ use `^1.0` (please let PHP 5 die!)
In case you want to keep using `v4.3.x-dev`, specify the hash of a commit to avoid running into unforseen issues like so: `v4.3.x-dev#c115f7bc51d466ccb24c544e88329804aad8c2a0`
PSA: [PHP 7.0 - 7.3 are EOL](https://www.php.net/supported-versions.php) and therefore the respective `QRCode` versions are also no longer supported!
## Quickstart
We want to encode this URI for a mobile authenticator into a QRcode image:
```php
$data = 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net';
// quick and simple:
echo '<img src="'.(new QRCode)->render($data).'" alt="QR Code" />';
```
<p align="center">
<img alt="QR codes are awesome!" src="https://raw.githubusercontent.com/chillerlan/php-qrcode/v4.3.x/examples/example_image.png">
<img alt="QR codes are awesome!" src="https://raw.githubusercontent.com/chillerlan/php-qrcode/v4.3.x/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 interface]].
```php
$matrix = $qrcode->getMatrix($data);
foreach($matrix->matrix() as $y => $row){
foreach($row as $x => $module){
// get a module's value
$value = $module;
// or via the matrix's getter method
$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 the examples folder](https://github.com/chillerlan/php-qrcode/tree/main/examples) for some more usage examples.
### 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 maybe setting the mask pattern manually (which may result in unreadable QR Codes).
Oh hey and don't forget to sanitize any user input!
## Custom output interface
Instead of bloating your code you can simply create your own output interface by creating a `QROutputInterface` (i.e. extending `QROutputAbstract`).
```php
class MyCustomOutput extends QROutputAbstract{
// inherited from QROutputAbstract
protected QRMatrix $matrix; // QRMatrix
protected int $moduleCount; // modules QRMatrix::size()
protected QROptions $options; // MyCustomOptions or QROptions
protected int $scale; // scale factor from options
protected int $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;
}
}
```
For more examples, have a look at the [built-in output modules](https://github.com/chillerlan/php-qrcode/tree/main/src/Output).
In case you need additional settings for your output module, just extend `QROptions`...
```
class MyCustomOptions extends QROptions{
protected string $myParam = 'defaultValue';
// ...
}
```
...or use the [`SettingsContainerInterface`](https://github.com/chillerlan/php-settings-container/blob/main/src/SettingsContainerInterface.php), which is the more flexible approach.
```php
trait MyCustomOptionsTrait{
protected string $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();
```
### Custom module values
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, i.e. to assign different colors for each part of the matrix (see the [image example](https://github.com/chillerlan/php-qrcode/blob/main/examples/image.php)).
The dark value is the module value (light) 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 => '#',
// ...
];
```
## Public API
### `QRCode` API
#### Methods
method | return | description
------ | ------ | -----------
`__construct(QROptions $options = null)` | - | see [`SettingsContainerInterface`](https://github.com/chillerlan/php-settings-container/blob/main/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`
`isByte(string $string)` | bool | checks if a string is non-empty
#### 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`
### `QRMatrix` API
#### Methods
method | return | description
------ | ------ | -----------
`__construct(int $version, int $eclevel)` | - | -
`init(int $maskPattern, bool $test = null)` | `QRMatrix` |
`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)
#### 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
### `QROptions` API
#### 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
`$dataModeOverride` | 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 interface]] and `QROutputInterface::DEFAULT_MODULE_VALUES`
## Framework Integration
- Drupal:
- [Google Authenticator Login `ga_login`](https://www.drupal.org/project/ga_login)
- Symfony
- [phpqrcode-bundle](https://github.com/jonasarts/phpqrcode-bundle)
- WordPress:
- [`wp-two-factor-auth`](https://github.com/sjinks/wp-two-factor-auth)
- [`simple-2fa`](https://wordpress.org/plugins/simple-2fa/)
- [`wordpress-seo`](https://github.com/Yoast/wordpress-seo)
- [`floating-share-button`](https://github.com/qriouslad/floating-share-button)
- WoltLab Suite
- [two-step-verification](http://pluginstore.woltlab.com/file/3007-two-step-verification/)
- [Appwrite](https://github.com/appwrite/appwrite)
- [Cachet](https://github.com/CachetHQ/Cachet)
- [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 a registered trademark of *DENSO WAVE INCORPORATED*<br>
https://www.qrcode.com/en/faq.html#patentH2Title

View File

@@ -0,0 +1,61 @@
{
"name": "chillerlan/php-qrcode",
"description": "A QR code generator. PHP 7.4+",
"homepage": "https://github.com/chillerlan/php-qrcode",
"license": "MIT",
"minimum-stability": "stable",
"type": "library",
"keywords": [
"QR code", "qrcode", "qr", "qrcode-generator", "phpqrcode"
],
"authors": [
{
"name": "Kazuhiko Arase",
"homepage": "https://github.com/kazuhikoarase"
},
{
"name": "Smiley",
"email": "smiley@chillerlan.net",
"homepage": "https://github.com/codemasher"
},
{
"name": "Contributors",
"homepage":"https://github.com/chillerlan/php-qrcode/graphs/contributors"
}
],
"require": {
"php": "^7.4 || ^8.0",
"ext-mbstring": "*",
"chillerlan/php-settings-container": "^2.1.4"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"phan/phan": "^5.3",
"setasign/fpdf": "^1.8.2"
},
"suggest": {
"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
"setasign/fpdf": "Required to use the QR FPDF output."
},
"autoload": {
"psr-4": {
"chillerlan\\QRCode\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"chillerlan\\QRCodePublic\\": "public/",
"chillerlan\\QRCodeTest\\": "tests/",
"chillerlan\\QRCodeExamples\\": "examples/"
}
},
"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,38 @@
<?php
/**
* Class MyCustomOutput
*
* @filesource MyCustomOutput.php
* @created 24.12.2017
* @package chillerlan\QRCodeExamples
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeExamples;
use chillerlan\QRCode\Output\QROutputAbstract;
class MyCustomOutput extends QROutputAbstract{
protected function setModuleValues():void{
// TODO: Implement setModuleValues() method.
}
public function dump(string $file = null){
$output = '';
for($row = 0; $row < $this->moduleCount; $row++){
for($col = 0; $col < $this->moduleCount; $col++){
$output .= (int)$this->matrix->check($col, $row);
}
$output .= \PHP_EOL;
}
return $output;
}
}

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

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

View File

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

View File

@@ -0,0 +1,63 @@
<?php
/**
*
* @filesource image.php
* @created 24.12.2017
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeExamples;
use chillerlan\QRCode\{QRCode, QROptions};
require_once __DIR__.'/../vendor/autoload.php';
$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s';
$options = new QROptions([
'version' => 10,
'outputType' => QRCode::OUTPUT_IMAGE_PNG,
'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],
// timing
3072 => [255, 0, 0],
12 => [255, 255, 255],
// format
3584 => [67, 99, 84],
14 => [255, 255, 255],
// version
4096 => [62, 174, 190],
16 => [255, 255, 255],
// data
1024 => [0, 0, 0],
4 => [255, 255, 255],
// darkmodule
512 => [0, 0, 0],
// separator
8 => [255, 255, 255],
// quietzone
18 => [255, 255, 255],
// logo (requires a call to QRMatrix::setLogoSpace())
20 => [255, 255, 255],
],
]);
header('Content-type: image/png');
echo (new QRCode($options))->render($data);

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

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

View File

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

20
vendor/chillerlan/php-qrcode/phpdoc.xml vendored Normal file
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

@@ -0,0 +1,26 @@
<?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"
>
<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-qrcode test suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
<logging>
<junit outputFile=".build/logs/junit.xml"/>
</logging>
</phpunit>

View File

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

View File

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

View File

@@ -0,0 +1,60 @@
<?php
/**
* Class AlphaNum
*
* @filesource AlphaNum.php
* @created 25.11.2015
* @package chillerlan\QRCode\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\QRCode;
use function 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
*/
final class AlphaNum extends QRDataAbstract{
protected int $datamode = QRCode::DATA_ALPHANUM;
protected array $lengthBits = [9, 11, 13];
/**
* @inheritdoc
*/
protected function write(string $data):void{
for($i = 0; $i + 1 < $this->strlen; $i += 2){
$this->bitBuffer->put($this->getCharCode($data[$i]) * 45 + $this->getCharCode($data[$i + 1]), 11);
}
if($i < $this->strlen){
$this->bitBuffer->put($this->getCharCode($data[$i]), 6);
}
}
/**
* get the code for the given character
*
* @throws \chillerlan\QRCode\Data\QRCodeDataException on an illegal character occurence
*/
protected function getCharCode(string $chr):int{
if(!isset($this::CHAR_MAP_ALPHANUM[$chr])){
throw new QRCodeDataException(sprintf('illegal char: "%s" [%d]', $chr, ord($chr)));
}
return $this::CHAR_MAP_ALPHANUM[$chr];
}
}

View File

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

View File

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

View File

@@ -0,0 +1,203 @@
<?php
/**
* Class MaskPatternTester
*
* @filesource MaskPatternTester.php
* @created 22.11.2017
* @package chillerlan\QRCode\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*
* @noinspection PhpUnused
*/
namespace chillerlan\QRCode\Data;
use function abs, array_search, call_user_func_array, min;
/**
* Receives a QRDataInterface object and runs the mask pattern tests on it.
*
* ISO/IEC 18004:2000 Section 8.8.2 - Evaluation of masking results
*
* @see http://www.thonky.com/qr-code-tutorial/data-masking
*/
final class MaskPatternTester{
/**
* The data interface that contains the data matrix to test
*/
protected QRDataInterface $dataInterface;
/**
* Receives the QRDataInterface
*
* @see \chillerlan\QRCode\QROptions::$maskPattern
* @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern
*/
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);
}
/**
* Returns the penalty for the given mask pattern
*
* @see \chillerlan\QRCode\QROptions::$maskPattern
* @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern
*/
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], [$matrix->matrix(true), $matrix->size()]);
}
return (int)$penalty;
}
/**
* Checks for each group of five or more same-colored modules in a row (or column)
*/
protected function testLevel1(array $m, int $size):int{
$penalty = 0;
foreach($m as $y => $row){
foreach($row as $x => $val){
$count = 0;
for($ry = -1; $ry <= 1; $ry++){
if($y + $ry < 0 || $size <= $y + $ry){
continue;
}
for($rx = -1; $rx <= 1; $rx++){
if(($ry === 0 && $rx === 0) || (($x + $rx) < 0 || $size <= ($x + $rx))){
continue;
}
if($m[$y + $ry][$x + $rx] === $val){
$count++;
}
}
}
if($count > 5){
$penalty += (3 + $count - 5);
}
}
}
return $penalty;
}
/**
* Checks for each 2x2 area of same-colored modules in the matrix
*/
protected function testLevel2(array $m, int $size):int{
$penalty = 0;
foreach($m as $y => $row){
if($y > $size - 2){
break;
}
foreach($row as $x => $val){
if($x > $size - 2){
break;
}
if(
$val === $m[$y][$x + 1]
&& $val === $m[$y + 1][$x]
&& $val === $m[$y + 1][$x + 1]
){
$penalty++;
}
}
}
return 3 * $penalty;
}
/**
* Checks if there are patterns that look similar to the finder patterns (1:1:3:1:1 ratio)
*/
protected function testLevel3(array $m, int $size):int{
$penalties = 0;
foreach($m as $y => $row){
foreach($row as $x => $val){
if(
$x + 6 < $size
&& $val
&& !$m[$y][$x + 1]
&& $m[$y][$x + 2]
&& $m[$y][$x + 3]
&& $m[$y][$x + 4]
&& !$m[$y][$x + 5]
&& $m[$y][$x + 6]
){
$penalties++;
}
if(
$y + 6 < $size
&& $val
&& !$m[$y + 1][$x]
&& $m[$y + 2][$x]
&& $m[$y + 3][$x]
&& $m[$y + 4][$x]
&& !$m[$y + 5][$x]
&& $m[$y + 6][$x]
){
$penalties++;
}
}
}
return $penalties * 40;
}
/**
* Checks if more than half of the modules are dark or light, with a larger penalty for a larger difference
*/
protected function testLevel4(array $m, int $size):float{
$count = 0;
foreach($m as $y => $row){
foreach($row as $x => $val){
if($val){
$count++;
}
}
}
return (abs(100 * $count / $size / $size - 50) / 5) * 10;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,311 @@
<?php
/**
* Class QRDataAbstract
*
* @filesource QRDataAbstract.php
* @created 25.11.2015
* @package chillerlan\QRCode\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\Helpers\{BitBuffer, Polynomial};
use chillerlan\Settings\SettingsContainerInterface;
use function array_fill, array_merge, count, max, mb_convert_encoding, mb_detect_encoding, range, sprintf, strlen;
/**
* Processes the binary data and maps it on a matrix which is then being returned
*/
abstract class QRDataAbstract implements QRDataInterface{
/**
* the string byte count
*/
protected ?int $strlen = null;
/**
* the current data mode: Num, Alphanum, Kanji, Byte
*/
protected int $datamode;
/**
* mode length bits for the version breakpoints 1-9, 10-26 and 27-40
*
* ISO/IEC 18004:2000 Table 3 - Number of bits in Character Count Indicator
*/
protected array $lengthBits = [0, 0, 0];
/**
* current QR Code version
*/
protected int $version;
/**
* ECC temp data
*/
protected array $ecdata;
/**
* ECC temp data
*/
protected array $dcdata;
/**
* the options instance
*
* @var \chillerlan\Settings\SettingsContainerInterface|\chillerlan\QRCode\QROptions
*/
protected SettingsContainerInterface $options;
/**
* a BitBuffer instance
*/
protected BitBuffer $bitBuffer;
/**
* QRDataInterface constructor.
*/
public function __construct(SettingsContainerInterface $options, string $data = null){
$this->options = $options;
if($data !== null){
$this->setData($data);
}
}
/**
* @inheritDoc
*/
public function setData(string $data):QRDataInterface{
if($this->datamode === QRCode::DATA_KANJI){
$data = mb_convert_encoding($data, 'SJIS', mb_detect_encoding($data));
}
$this->strlen = $this->getLength($data);
$this->version = $this->options->version === QRCode::VERSION_AUTO
? $this->getMinimumVersion()
: $this->options->version;
$this->writeBitBuffer($data);
return $this;
}
/**
* @inheritDoc
*/
public function initMatrix(int $maskPattern, bool $test = null):QRMatrix{
return (new QRMatrix($this->version, $this->options->eccLevel))
->init($maskPattern, $test)
->mapData($this->maskECC(), $maskPattern)
;
}
/**
* returns the length bits for the version breakpoints 1-9, 10-26 and 27-40
*
* @throws \chillerlan\QRCode\Data\QRCodeDataException
* @codeCoverageIgnore
*/
protected function getLengthBits():int{
foreach([9, 26, 40] as $key => $breakpoint){
if($this->version <= $breakpoint){
return $this->lengthBits[$key];
}
}
throw new QRCodeDataException(sprintf('invalid version number: %d', $this->version));
}
/**
* returns the byte count of the $data string
*/
protected function getLength(string $data):int{
return strlen($data);
}
/**
* returns the minimum version number for the given string
*
* @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][$dataMode][$eccMode];
if($this->strlen <= $maxlength){
return $version;
}
}
throw new QRCodeDataException(sprintf('data exceeds %d characters', $maxlength));
}
/**
* writes the actual data string to the BitBuffer
*
* @see \chillerlan\QRCode\Data\QRDataAbstract::writeBitBuffer()
*/
abstract protected function write(string $data):void;
/**
* creates a BitBuffer and writes the string data to it
*
* @throws \chillerlan\QRCode\QRCodeException on data overflow
*/
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
->put($this->datamode, 4)
->put($this->strlen, $this->getLengthBits())
;
$this->write($data);
// overflow, likely caused due to invalid version setting
if($this->bitBuffer->getLength() > $MAX_BITS){
throw new QRCodeDataException(sprintf('code length overflow. (%d > %d bit)', $this->bitBuffer->getLength(), $MAX_BITS));
}
// add terminator (ISO/IEC 18004:2000 Table 2)
if($this->bitBuffer->getLength() + 4 <= $MAX_BITS){
$this->bitBuffer->put(0, 4);
}
// padding
while($this->bitBuffer->getLength() % 8 !== 0){
$this->bitBuffer->putBit(false);
}
// padding
while(true){
if($this->bitBuffer->getLength() >= $MAX_BITS){
break;
}
$this->bitBuffer->put(0xEC, 8);
if($this->bitBuffer->getLength() >= $MAX_BITS){
break;
}
$this->bitBuffer->put(0x11, 8);
}
}
/**
* ECC masking
*
* ISO/IEC 18004:2000 Section 8.5 ff
*
* @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, []);
$this->dcdata = $this->ecdata;
if($l2 > 0){
$rsBlocks = array_merge($rsBlocks, array_fill(0, $l2, [$b1 + 1, $b2 + 1]));
}
$totalCodeCount = 0;
$maxDcCount = 0;
$maxEcCount = 0;
$offset = 0;
$bitBuffer = $this->bitBuffer->getBuffer();
foreach($rsBlocks as $key => $block){
[$rsBlockTotal, $dcCount] = $block;
$ecCount = $rsBlockTotal - $dcCount;
$maxDcCount = max($maxDcCount, $dcCount);
$maxEcCount = max($maxEcCount, $ecCount);
$this->dcdata[$key] = array_fill(0, $dcCount, null);
foreach($this->dcdata[$key] as $a => $_z){
$this->dcdata[$key][$a] = 0xff & $bitBuffer[$a + $offset];
}
[$num, $add] = $this->poly($key, $ecCount);
foreach($this->ecdata[$key] as $c => $_){
$modIndex = $c + $add;
$this->ecdata[$key][$c] = $modIndex >= 0 ? $num[$modIndex] : 0;
}
$offset += $dcCount;
$totalCodeCount += $rsBlockTotal;
}
$data = array_fill(0, $totalCodeCount, null);
$index = 0;
$mask = function(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])){
$data[$index] = $arr[$y][$x];
$index++;
}
}
}
};
$mask($this->dcdata, $maxDcCount);
$mask($this->ecdata, $maxEcCount);
return $data;
}
/**
* helper method for the polynomial operations
*/
protected function poly(int $key, int $count):array{
$rsPoly = new Polynomial;
$modPoly = new Polynomial;
for($i = 0; $i < $count; $i++){
$modPoly->setNum([1, $modPoly->gexp($i)]);
$rsPoly->multiply($modPoly->getNum());
}
$rsPolyCount = count($rsPoly->getNum());
$modPoly
->setNum($this->dcdata[$key], $rsPolyCount - 1)
->mod($rsPoly->getNum())
;
$this->ecdata[$key] = array_fill(0, $rsPolyCount - 1, null);
$num = $modPoly->getNum();
return [
$num,
count($num) - count($this->ecdata[$key]),
];
}
}

View File

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

View File

@@ -0,0 +1,740 @@
<?php
/**
* Class QRMatrix
*
* @filesource QRMatrix.php
* @created 15.11.2017
* @package chillerlan\QRCode\Data
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\QRCode;
use Closure;
use function array_fill, array_key_exists, array_push, array_unshift, count, floor, in_array, max, min, range;
/**
* 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
*/
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;
/**
* ISO/IEC 18004:2000 Annex E, Table E.1 - Row/column coordinates of center module of Alignment Patterns
*
* version -> pattern
*
* @var int[][]
*/
protected const alignmentPattern = [
1 => [],
2 => [6, 18],
3 => [6, 22],
4 => [6, 26],
5 => [6, 30],
6 => [6, 34],
7 => [6, 22, 38],
8 => [6, 24, 42],
9 => [6, 26, 46],
10 => [6, 28, 50],
11 => [6, 30, 54],
12 => [6, 32, 58],
13 => [6, 34, 62],
14 => [6, 26, 46, 66],
15 => [6, 26, 48, 70],
16 => [6, 26, 50, 74],
17 => [6, 30, 54, 78],
18 => [6, 30, 56, 82],
19 => [6, 30, 58, 86],
20 => [6, 34, 62, 90],
21 => [6, 28, 50, 72, 94],
22 => [6, 26, 50, 74, 98],
23 => [6, 30, 54, 78, 102],
24 => [6, 28, 54, 80, 106],
25 => [6, 32, 58, 84, 110],
26 => [6, 30, 58, 86, 114],
27 => [6, 34, 62, 90, 118],
28 => [6, 26, 50, 74, 98, 122],
29 => [6, 30, 54, 78, 102, 126],
30 => [6, 26, 52, 78, 104, 130],
31 => [6, 30, 56, 82, 108, 134],
32 => [6, 34, 60, 86, 112, 138],
33 => [6, 30, 58, 86, 114, 142],
34 => [6, 34, 62, 90, 118, 146],
35 => [6, 30, 54, 78, 102, 126, 150],
36 => [6, 24, 50, 76, 102, 128, 154],
37 => [6, 28, 54, 80, 106, 132, 158],
38 => [6, 32, 58, 84, 110, 136, 162],
39 => [6, 26, 54, 82, 110, 138, 166],
40 => [6, 30, 58, 86, 114, 142, 170],
];
/**
* 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,
8 => 0b001000010110111100,
9 => 0b001001101010011001,
10 => 0b001010010011010011,
11 => 0b001011101111110110,
12 => 0b001100011101100010,
13 => 0b001101100001000111,
14 => 0b001110011000001101,
15 => 0b001111100100101000,
16 => 0b010000101101111000,
17 => 0b010001010001011101,
18 => 0b010010101000010111,
19 => 0b010011010100110010,
20 => 0b010100100110100110,
21 => 0b010101011010000011,
22 => 0b010110100011001001,
23 => 0b010111011111101100,
24 => 0b011000111011000100,
25 => 0b011001000111100001,
26 => 0b011010111110101011,
27 => 0b011011000010001110,
28 => 0b011100110000011010,
29 => 0b011101001100111111,
30 => 0b011110110101110101,
31 => 0b011111001001010000,
32 => 0b100000100111010101,
33 => 0b100001011011110000,
34 => 0b100010100010111010,
35 => 0b100011011110011111,
36 => 0b100100101100001011,
37 => 0b100101010000101110,
38 => 0b100110101001100100,
39 => 0b100111010101000001,
40 => 0b101000110001101001,
];
/**
* ISO/IEC 18004:2000 Section 8.9 - Format Information
*
* ECC level -> mask pattern
*
* @var int[][]
*/
protected const formatPattern = [
[ // L
0b111011111000100,
0b111001011110011,
0b111110110101010,
0b111100010011101,
0b110011000101111,
0b110001100011000,
0b110110001000001,
0b110100101110110,
],
[ // M
0b101010000010010,
0b101000100100101,
0b101111001111100,
0b101101101001011,
0b100010111111001,
0b100000011001110,
0b100111110010111,
0b100101010100000,
],
[ // Q
0b011010101011111,
0b011000001101000,
0b011111100110001,
0b011101000000110,
0b010010010110100,
0b010000110000011,
0b010111011011010,
0b010101111101101,
],
[ // H
0b001011010001001,
0b001001110111110,
0b001110011100111,
0b001100111010000,
0b000011101100010,
0b000001001010101,
0b000110100001100,
0b000100000111011,
],
];
/**
* the current QR Code version number
*/
protected int $version;
/**
* the current ECC level
*/
protected int $eclevel;
/**
* the used mask pattern, set via QRMatrix::mapData()
*/
protected int $maskPattern = QRCode::MASK_PATTERN_AUTO;
/**
* the size (side length) of the matrix
*/
protected int $moduleCount;
/**
* the actual matrix data array
*
* @var int[][]
*/
protected array $matrix;
/**
* QRMatrix constructor.
*
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
public function __construct(int $version, int $eclevel){
if(!in_array($version, range(1, 40), true)){
throw new QRCodeDataException('invalid QR Code version');
}
if(!array_key_exists($eclevel, QRCode::ECC_MODES)){
throw new QRCodeDataException('invalid ecc level');
}
$this->version = $version;
$this->eclevel = $eclevel;
$this->moduleCount = $this->version * 4 + 17;
$this->matrix = array_fill(0, $this->moduleCount, array_fill(0, $this->moduleCount, $this::M_NULL));
}
/**
* 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
*
* @return int[][]|bool[][]
*/
public function matrix(bool $boolean = false):array{
if(!$boolean){
return $this->matrix;
}
$matrix = [];
foreach($this->matrix as $y => $row){
$matrix[$y] = [];
foreach($row as $x => $val){
$matrix[$y][$x] = ($val >> 8) > 0;
}
}
return $matrix;
}
/**
* Returns the current version number
*/
public function version():int{
return $this->version;
}
/**
* Returns the current ECC level
*/
public function eccLevel():int{
return $this->eclevel;
}
/**
* Returns the current mask pattern
*/
public function maskPattern():int{
return $this->maskPattern;
}
/**
* Returns the absoulute size of the matrix, including quiet zone (after setting it).
*
* size = version * 4 + 17 [ + 2 * quietzone size]
*/
public function size():int{
return $this->moduleCount;
}
/**
* Returns the value of the module at position [$x, $y]
*/
public function get(int $x, int $y):int{
return $this->matrix[$y][$x];
}
/**
* Sets the $M_TYPE value for the module at position [$x, $y]
*
* true => $M_TYPE << 8
* false => $M_TYPE
*/
public function set(int $x, int $y, bool $value, int $M_TYPE):QRMatrix{
$this->matrix[$y][$x] = $M_TYPE << ($value ? 8 : 0);
return $this;
}
/**
* Checks whether a module is true (dark) or false (light)
*
* true => $value >> 8 === $M_TYPE
* $value >> 8 > 0
*
* false => $value === $M_TYPE
* $value >> 8 === 0
*/
public function check(int $x, int $y):bool{
return ($this->matrix[$y][$x] >> 8) > 0;
}
/**
* Sets the "dark module", that is always on the same position 1x1px away from the bottom left finder
*/
public function setDarkModule():QRMatrix{
$this->set(8, 4 * $this->version + 9, true, $this::M_DARKMODULE);
return $this;
}
/**
* Draws the 7x7 finder patterns in the corners top left/right and bottom left
*
* ISO/IEC 18004:2000 Section 7.3.2
*/
public function setFinderPattern():QRMatrix{
$pos = [
[0, 0], // top left
[$this->moduleCount - 7, 0], // bottom left
[0, $this->moduleCount - 7], // top right
];
foreach($pos as $c){
for($y = 0; $y < 7; $y++){
for($x = 0; $x < 7; $x++){
// outer (dark) 7*7 square
if($x === 0 || $x === 6 || $y === 0 || $y === 6){
$this->set($c[0] + $y, $c[1] + $x, true, $this::M_FINDER);
}
// inner (light) 5*5 square
elseif($x === 1 || $x === 5 || $y === 1 || $y === 5){
$this->set($c[0] + $y, $c[1] + $x, false, $this::M_FINDER);
}
// 3*3 dot
else{
$this->set($c[0] + $y, $c[1] + $x, true, $this::M_FINDER_DOT);
}
}
}
}
return $this;
}
/**
* Draws the separator lines around the finder patterns
*
* ISO/IEC 18004:2000 Section 7.3.3
*/
public function setSeparators():QRMatrix{
$h = [
[7, 0],
[$this->moduleCount - 8, 0],
[7, $this->moduleCount - 8],
];
$v = [
[7, 7],
[$this->moduleCount - 1, 7],
[7, $this->moduleCount - 8],
];
for($c = 0; $c < 3; $c++){
for($i = 0; $i < 8; $i++){
$this->set($h[$c][0] , $h[$c][1] + $i, false, $this::M_SEPARATOR);
$this->set($v[$c][0] - $i, $v[$c][1] , false, $this::M_SEPARATOR);
}
}
return $this;
}
/**
* Draws the 5x5 alignment patterns
*
* ISO/IEC 18004:2000 Section 7.3.5
*/
public function setAlignmentPattern():QRMatrix{
foreach($this::alignmentPattern[$this->version] as $y){
foreach($this::alignmentPattern[$this->version] as $x){
// skip existing patterns
if($this->matrix[$y][$x] !== $this::M_NULL){
continue;
}
for($ry = -2; $ry <= 2; $ry++){
for($rx = -2; $rx <= 2; $rx++){
$v = ($ry === 0 && $rx === 0) || $ry === 2 || $ry === -2 || $rx === 2 || $rx === -2;
$this->set($x + $rx, $y + $ry, $v, $this::M_ALIGNMENT);
}
}
}
}
return $this;
}
/**
* Draws the timing pattern (h/v checkered line between the finder patterns)
*
* ISO/IEC 18004:2000 Section 7.3.4
*/
public function setTimingPattern():QRMatrix{
foreach(range(8, $this->moduleCount - 8 - 1) as $i){
if($this->matrix[6][$i] !== $this::M_NULL || $this->matrix[$i][6] !== $this::M_NULL){
continue;
}
$v = $i % 2 === 0;
$this->set($i, 6, $v, $this::M_TIMING); // h
$this->set(6, $i, $v, $this::M_TIMING); // v
}
return $this;
}
/**
* Draws the version information, 2x 3x6 pixel
*
* ISO/IEC 18004:2000 Section 8.10
*/
public function setVersionNumber(bool $test = null):QRMatrix{
$bits = $this::versionPattern[$this->version] ?? false;
if($bits !== false){
for($i = 0; $i < 18; $i++){
$a = (int)floor($i / 3);
$b = $i % 3 + $this->moduleCount - 8 - 3;
$v = !$test && (($bits >> $i) & 1) === 1;
$this->set($b, $a, $v, $this::M_VERSION); // ne
$this->set($a, $b, $v, $this::M_VERSION); // sw
}
}
return $this;
}
/**
* Draws the format info along the finder patterns
*
* 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;
for($i = 0; $i < 15; $i++){
$v = !$test && (($bits >> $i) & 1) === 1;
if($i < 6){
$this->set(8, $i, $v, $this::M_FORMAT);
}
elseif($i < 8){
$this->set(8, $i + 1, $v, $this::M_FORMAT);
}
else{
$this->set(8, $this->moduleCount - 15 + $i, $v, $this::M_FORMAT);
}
if($i < 8){
$this->set($this->moduleCount - $i - 1, 8, $v, $this::M_FORMAT);
}
elseif($i < 9){
$this->set(15 - $i, 8, $v, $this::M_FORMAT);
}
else{
$this->set(15 - $i - 1, 8, $v, $this::M_FORMAT);
}
}
$this->set(8, $this->moduleCount - 8, !$test, $this::M_FORMAT);
return $this;
}
/**
* Draws the "quiet zone" of $size around the matrix
*
* ISO/IEC 18004:2000 Section 7.3.7
*
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
public function setQuietZone(int $size = null):QRMatrix{
if($this->matrix[$this->moduleCount - 1][$this->moduleCount - 1] === $this::M_NULL){
throw new QRCodeDataException('use only after writing data');
}
$size = $size !== null
? max(0, min($size, floor($this->moduleCount / 2)))
: 4;
for($y = 0; $y < $this->moduleCount; $y++){
for($i = 0; $i < $size; $i++){
array_unshift($this->matrix[$y], $this::M_QUIETZONE);
array_push($this->matrix[$y], $this::M_QUIETZONE);
}
}
$this->moduleCount += ($size * 2);
$r = array_fill(0, $this->moduleCount, $this::M_QUIETZONE);
for($i = 0; $i < $size; $i++){
array_unshift($this->matrix, $r);
array_push($this->matrix, $r);
}
return $this;
}
/**
* Clears a space of $width * $height in order to add a logo or text.
*
* Additionally, the logo space can be positioned within the QR Code - respecting the main functional patterns -
* using $startX and $startY. If either of these are null, the logo space will be centered in that direction.
* ECC level "H" (30%) is required.
*
* Please note that adding a logo space minimizes the error correction capacity of the QR Code and
* created images may become unreadable, especially when printed with a chance to receive damage.
* Please test thoroughly before using this feature in production.
*
* This method should be called from within an output module (after the matrix has been filled with data).
* Note that there is no restiction on how many times this method could be called on the same matrix instance.
*
* @link https://github.com/chillerlan/php-qrcode/issues/52
*
* @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 !== QRCode::ECC_H){
throw new QRCodeDataException('ECC level "H" required to add logo space');
}
// we need uneven sizes to center the logo space, adjust if needed
if($startX === null && ($width % 2) === 0){
$width++;
}
if($startY === null && ($height % 2) === 0){
$height++;
}
// $this->moduleCount includes the quiet zone (if created), we need the QR size here
$length = $this->version * 4 + 17;
// throw if the logo space exceeds the maximum error correction capacity
if($width * $height > floor($length * $length * 0.2)){
throw new QRCodeDataException('logo space exceeds the maximum error correction capacity');
}
// quiet zone size
$qz = ($this->moduleCount - $length) / 2;
// skip quiet zone and the first 9 rows/columns (finder-, mode-, version- and timing patterns)
$start = $qz + 9;
// skip quiet zone
$end = $this->moduleCount - $qz;
// determine start coordinates
$startX = ($startX !== null ? $startX : ($length - $width) / 2) + $qz;
$startY = ($startY !== null ? $startY : ($length - $height) / 2) + $qz;
// clear the space
foreach($this->matrix as $y => $row){
foreach($row as $x => $val){
// out of bounds, skip
if($x < $start || $y < $start ||$x >= $end || $y >= $end){
continue;
}
// a match
if($x >= $startX && $x < ($startX + $width) && $y >= $startY && $y < ($startY + $height)){
$this->set($x, $y, false, $this::M_LOGO);
}
}
}
return $this;
}
/**
* Maps the binary $data array from QRDataInterface::maskECC() on the matrix,
* masking the data using $maskPattern (ISO/IEC 18004:2000 Section 8.8)
*
* @see \chillerlan\QRCode\Data\QRDataAbstract::maskECC()
*
* @param int[] $data
* @param int $maskPattern
*
* @return \chillerlan\QRCode\Data\QRMatrix
*/
public function mapData(array $data, int $maskPattern):QRMatrix{
$this->maskPattern = $maskPattern;
$byteCount = count($data);
$y = $this->moduleCount - 1;
$inc = -1;
$byteIndex = 0;
$bitIndex = 7;
$mask = $this->getMask($this->maskPattern);
for($i = $y; $i > 0; $i -= 2){
if($i === 6){
$i--;
}
while(true){
for($c = 0; $c < 2; $c++){
$x = $i - $c;
if($this->matrix[$y][$x] === $this::M_NULL){
$v = false;
if($byteIndex < $byteCount){
$v = (($data[$byteIndex] >> $bitIndex) & 1) === 1;
}
if($mask($x, $y) === 0){
$v = !$v;
}
$this->matrix[$y][$x] = $this::M_DATA << ($v ? 8 : 0);
$bitIndex--;
if($bitIndex === -1){
$byteIndex++;
$bitIndex = 7;
}
}
}
$y += $inc;
if($y < 0 || $this->moduleCount <= $y){
$y -= $inc;
$inc = -$inc;
break;
}
}
}
return $this;
}
/**
* ISO/IEC 18004:2000 Section 8.8.1
*
* Note that some versions of the QR code standard have had errors in the section about mask patterns.
* The information below has been corrected. (https://www.thonky.com/qr-code-tutorial/mask-patterns)
*
* @see \chillerlan\QRCode\QRMatrix::mapData()
*
* @internal
*
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
protected function getMask(int $maskPattern):Closure{
if((0b111 & $maskPattern) !== $maskPattern){
throw new QRCodeDataException('invalid mask pattern'); // @codeCoverageIgnore
}
return [
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

@@ -0,0 +1,89 @@
<?php
/**
* Class BitBuffer
*
* @filesource BitBuffer.php
* @created 25.11.2015
* @package chillerlan\QRCode\Helpers
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Helpers;
use function count, floor;
/**
* Holds the raw binary data
*/
final class BitBuffer{
/**
* The buffer content
*
* @var int[]
*/
protected array $buffer = [];
/**
* Length of the content (bits)
*/
protected int $length = 0;
/**
* clears the buffer
*/
public function clear():BitBuffer{
$this->buffer = [];
$this->length = 0;
return $this;
}
/**
* appends a sequence of bits
*/
public function put(int $num, int $length):BitBuffer{
for($i = 0; $i < $length; $i++){
$this->putBit((($num >> ($length - $i - 1)) & 1) === 1);
}
return $this;
}
/**
* appends a single bit
*/
public function putBit(bool $bit):BitBuffer{
$bufIndex = floor($this->length / 8);
if(count($this->buffer) <= $bufIndex){
$this->buffer[] = 0;
}
if($bit === true){
$this->buffer[(int)$bufIndex] |= (0x80 >> ($this->length % 8));
}
$this->length++;
return $this;
}
/**
* 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

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

View File

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

View File

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

View File

@@ -0,0 +1,217 @@
<?php
/**
* Class QRImage
*
* @filesource QRImage.php
* @created 05.12.2015
* @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*
* @noinspection PhpComposerExtensionStubsInspection
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\QRCode\{QRCode, QRCodeException};
use chillerlan\Settings\SettingsContainerInterface;
use Exception;
use function array_values, base64_encode, call_user_func, count, 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)
*
* @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,
];
protected string $defaultMode = QRCode::OUTPUT_IMAGE_PNG;
/**
* The GD image resource
*
* @see imagecreatetruecolor()
* @var resource|\GdImage
*
* @phan-suppress PhanUndeclaredTypeProperty
*/
protected $image;
/**
* @inheritDoc
*
* @throws \chillerlan\QRCode\QRCodeException
*/
public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
if(!extension_loaded('gd')){
throw new QRCodeException('ext-gd not loaded'); // @codeCoverageIgnore
}
parent::__construct($options, $matrix);
}
/**
* @inheritDoc
*/
protected function setModuleValues():void{
foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
$v = $this->options->moduleValues[$M_TYPE] ?? null;
if(!is_array($v) || count($v) < 3){
$this->moduleValues[$M_TYPE] = $defaultValue
? [0, 0, 0]
: [255, 255, 255];
}
else{
$this->moduleValues[$M_TYPE] = array_values($v);
}
}
}
/**
* @inheritDoc
*
* @return string|resource|\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;
/** @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);
}
imagefilledrectangle($this->image, 0, 0, $this->length, $this->length, $background);
foreach($this->matrix->matrix() as $y => $row){
foreach($row as $x => $M_TYPE){
$this->setPixel($x, $y, $this->moduleValues[$M_TYPE]);
}
}
if($this->options->returnResource){
return $this->image;
}
$imageData = $this->dumpImage();
if($file !== null){
$this->saveToFile($imageData, $file);
}
if($this->options->imageBase64){
$imageData = sprintf('data:image/%s;base64,%s', $this->options->outputType, base64_encode($imageData));
}
return $imageData;
}
/**
* Creates a single QR pixel with the given settings
*/
protected function setPixel(int $x, int $y, array $rgb):void{
imagefilledrectangle(
$this->image,
$x * $this->scale,
$y * $this->scale,
($x + 1) * $this->scale,
($y + 1) * $this->scale,
/** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
imagecolorallocate($this->image, ...$rgb)
);
}
/**
* Creates the final image by calling the desired GD output function
*
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
*/
protected function dumpImage():string{
ob_start();
try{
call_user_func([$this, $this->outputMode ?? $this->defaultMode]);
}
// not going to cover edge cases
// @codeCoverageIgnoreStart
catch(Exception $e){
throw new QRCodeOutputException($e->getMessage());
}
// @codeCoverageIgnoreEnd
$imageData = ob_get_contents();
imagedestroy($this->image);
ob_end_clean();
return $imageData;
}
/**
* PNG output
*
* @return void
*/
protected function png():void{
imagepng(
$this->image,
null,
in_array($this->options->pngCompression, range(-1, 9), true)
? $this->options->pngCompression
: -1
);
}
/**
* Jiff - like... JitHub!
*
* @return void
*/
protected function gif():void{
imagegif($this->image);
}
/**
* JPG output
*
* @return void
*/
protected function jpg():void{
imagejpeg(
$this->image,
null,
in_array($this->options->jpegQuality, range(0, 100), true)
? $this->options->jpegQuality
: 85
);
}
}

View File

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

View File

@@ -0,0 +1,160 @@
<?php
/**
* Class QRMarkup
*
* @filesource QRMarkup.php
* @created 17.12.2016
* @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2016 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\QRCode;
use function is_string, sprintf, strip_tags, trim;
/**
* Converts the matrix into markup types: HTML, SVG, ...
*/
class QRMarkup extends QROutputAbstract{
protected string $defaultMode = QRCode::OUTPUT_MARKUP_SVG;
/**
* @see \sprintf()
*/
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
*/
protected function setModuleValues():void{
foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
$v = $this->options->moduleValues[$M_TYPE] ?? null;
if(!is_string($v)){
$this->moduleValues[$M_TYPE] = $defaultValue
? $this->options->markupDark
: $this->options->markupLight;
}
else{
$this->moduleValues[$M_TYPE] = trim(strip_tags($v), '\'"');
}
}
}
/**
* HTML output
*/
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>';
foreach($row as $M_TYPE){
$html .= '<span style="background: '.$this->moduleValues[$M_TYPE].';"></span>';
}
$html .= '</div>'.$this->options->eol;
}
$html .= '</div>'.$this->options->eol;
if($file !== null){
return '<!DOCTYPE html>'.
'<head><meta charset="UTF-8"><title>QR Code</title></head>'.
'<body>'.$this->options->eol.$html.'</body>';
}
return $html;
}
/**
* SVG output
*
* @see https://github.com/codemasher/php-qrcode/pull/5
*/
protected function svg(string $file = null):string{
$matrix = $this->matrix->matrix();
$svg = sprintf($this->svgHeader, $this->options->cssClass, $this->options->svgViewBoxSize ?? $this->moduleCount)
.$this->options->eol
.'<defs>'.$this->options->svgDefs.'</defs>'
.$this->options->eol;
foreach($this->moduleValues as $M_TYPE => $value){
$path = '';
foreach($matrix as $y => $row){
//we'll combine active blocks within a single row as a lightweight compression technique
$start = null;
$count = 0;
foreach($row as $x => $module){
if($module === $M_TYPE){
$count++;
if($start === null){
$start = $x;
}
if(isset($row[$x + 1])){
continue;
}
}
if($count > 0){
$len = $count;
$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
$count = 0;
$start = null;
}
}
}
if(!empty($path)){
$svg .= sprintf(
'<path class="qr-%s %s" stroke="transparent" fill="%s" fill-opacity="%s" d="%s" />',
$M_TYPE, $this->options->cssClass, $value, $this->options->svgOpacity, $path
);
}
}
// close svg
$svg .= '</svg>'.$this->options->eol;
// if saving to file, append the correct headers
if($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){
$svg = sprintf('data:image/svg+xml;base64,%s', base64_encode($svg));
}
return $svg;
}
}

View File

@@ -0,0 +1,129 @@
<?php
/**
* Class QROutputAbstract
*
* @filesource QROutputAbstract.php
* @created 09.12.2015
* @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\{Data\QRMatrix, QRCode};
use chillerlan\Settings\SettingsContainerInterface;
use function call_user_func_array, dirname, file_put_contents, get_called_class, in_array, is_writable, sprintf;
/**
* common output abstract
*/
abstract class QROutputAbstract implements QROutputInterface{
/**
* the current size of the QR matrix
*
* @see \chillerlan\QRCode\Data\QRMatrix::size()
*/
protected int $moduleCount;
/**
* the current output mode
*
* @see \chillerlan\QRCode\QROptions::$outputType
*/
protected string $outputMode;
/**
* the default output mode of the current output module
*/
protected string $defaultMode;
/**
* the current scaling for a QR pixel
*
* @see \chillerlan\QRCode\QROptions::$scale
*/
protected int $scale;
/**
* the side length of the QR image (modules * scale)
*/
protected int $length;
/**
* an (optional) array of color values for the several QR matrix parts
*/
protected array $moduleValues;
/**
* the (filled) data matrix object
*/
protected QRMatrix $matrix;
/**
* @var \chillerlan\Settings\SettingsContainerInterface|\chillerlan\QRCode\QROptions
*/
protected SettingsContainerInterface $options;
/**
* QROutputAbstract constructor.
*/
public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
$this->options = $options;
$this->matrix = $matrix;
$this->moduleCount = $this->matrix->size();
$this->scale = $this->options->scale;
$this->length = $this->moduleCount * $this->scale;
$class = get_called_class();
if(isset(QRCode::OUTPUT_MODES[$class]) && in_array($this->options->outputType, QRCode::OUTPUT_MODES[$class])){
$this->outputMode = $this->options->outputType;
}
$this->setModuleValues();
}
/**
* Sets the initial module values (clean-up & defaults)
*/
abstract protected function setModuleValues():void;
/**
* saves the qr data to a file
*
* @see file_put_contents()
* @see \chillerlan\QRCode\QROptions::cachefile
*
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
*/
protected function saveToFile(string $data, string $file):bool{
if(!is_writable(dirname($file))){
throw new QRCodeOutputException(sprintf('Could not write data to cache file: %s', $file));
}
return (bool)file_put_contents($file, $data);
}
/**
* @inheritDoc
*/
public function dump(string $file = null){
$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);
}
return $data;
}
}

View File

@@ -0,0 +1,54 @@
<?php
/**
* Interface QROutputInterface,
*
* @filesource QROutputInterface.php
* @created 02.12.2015
* @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\Data\QRMatrix;
/**
* Converts the data matrix into readable output
*/
interface QROutputInterface{
const DEFAULT_MODULE_VALUES = [
// light
QRMatrix::M_NULL => false, // 0
QRMatrix::M_DATA => false, // 4
QRMatrix::M_FINDER => false, // 6
QRMatrix::M_SEPARATOR => false, // 8
QRMatrix::M_ALIGNMENT => false, // 10
QRMatrix::M_TIMING => false, // 12
QRMatrix::M_FORMAT => false, // 14
QRMatrix::M_VERSION => false, // 16
QRMatrix::M_QUIETZONE => false, // 18
QRMatrix::M_LOGO => false, // 20
QRMatrix::M_TEST => false, // 255
// dark
QRMatrix::M_DARKMODULE << 8 => true, // 512
QRMatrix::M_DATA << 8 => true, // 1024
QRMatrix::M_FINDER << 8 => true, // 1536
QRMatrix::M_ALIGNMENT << 8 => true, // 2560
QRMatrix::M_TIMING << 8 => true, // 3072
QRMatrix::M_FORMAT << 8 => true, // 3584
QRMatrix::M_VERSION << 8 => true, // 4096
QRMatrix::M_FINDER_DOT << 8 => true, // 5632
QRMatrix::M_TEST << 8 => true, // 65280
];
/**
* generates the output, optionally dumps it to a file, and returns it
*
* @return mixed
*/
public function dump(string $file = null);
}

View File

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

313
vendor/chillerlan/php-qrcode/src/QRCode.php vendored Executable file
View File

@@ -0,0 +1,313 @@
<?php
/**
* Class QRCode
*
* @filesource QRCode.php
* @created 26.11.2015
* @package chillerlan\QRCode
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode;
use chillerlan\QRCode\Data\{
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 call_user_func_array, class_exists, in_array, ord, strlen, strtolower, str_split;
/**
* Turns a text string into a Model 2 QR Code
*
* @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{
/** @var int */
public const VERSION_AUTO = -1;
/** @var int */
public const MASK_PATTERN_AUTO = -1;
// 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;
/**
* 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,
self::DATA_BYTE => 2,
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,
self::OUTPUT_MARKUP_HTML,
],
QRImage::class => [
self::OUTPUT_IMAGE_PNG,
self::OUTPUT_IMAGE_GIF,
self::OUTPUT_IMAGE_JPG,
],
QRString::class => [
self::OUTPUT_STRING_JSON,
self::OUTPUT_STRING_TEXT,
],
QRImagick::class => [
self::OUTPUT_IMAGICK,
],
QRFpdf::class => [
self::OUTPUT_FPDF
]
];
/**
* Map of data mode => interface
*
* @var string[]
*/
protected const DATA_INTERFACES = [
'number' => Number::class,
'alphanum' => AlphaNum::class,
'kanji' => Kanji::class,
'byte' => Byte::class,
];
/**
* The settings container
*
* @var \chillerlan\QRCode\QROptions|\chillerlan\Settings\SettingsContainerInterface
*/
protected SettingsContainerInterface $options;
/**
* The selected data interface (Number, AlphaNum, Kanji, Byte)
*/
protected QRDataInterface $dataInterface;
/**
* QRCode constructor.
*
* 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;
}
/**
* Renders a QR Code for the given $data and QROptions
*
* @return mixed
*/
public function render(string $data, string $file = null){
return $this->initOutputInterface($data)->dump($file);
}
/**
* Returns a QRMatrix object for the given $data and current QROptions
*
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
public function getMatrix(string $data):QRMatrix{
if(empty($data)){
throw new QRCodeDataException('QRCode::getMatrix() No data given.');
}
$this->dataInterface = $this->initDataInterface($data);
$maskPattern = $this->options->maskPattern === $this::MASK_PATTERN_AUTO
? (new MaskPatternTester($this->dataInterface))->getBestMaskPattern()
: $this->options->maskPattern;
$matrix = $this->dataInterface->initMatrix($maskPattern);
if((bool)$this->options->addQuietzone){
$matrix->setQuietZone($this->options->quietzoneSize);
}
return $matrix;
}
/**
* returns a fresh QRDataInterface for the given $data
*
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
public function initDataInterface(string $data):QRDataInterface{
// allow forcing the data mode
// see https://github.com/chillerlan/php-qrcode/issues/39
$interface = $this::DATA_INTERFACES[strtolower($this->options->dataModeOverride)] ?? null;
if($interface !== null){
return new $interface($this->options, $data);
}
foreach($this::DATA_INTERFACES as $mode => $dataInterface){
if(call_user_func_array([$this, 'is'.$mode], [$data])){
return new $dataInterface($this->options, $data);
}
}
throw new QRCodeDataException('invalid data type'); // @codeCoverageIgnore
}
/**
* returns a fresh (built-in) 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));
}
foreach($this::OUTPUT_MODES as $outputInterface => $modes){
if(in_array($this->options->outputType, $modes, true) && class_exists($outputInterface)){
return new $outputInterface($this->options, $this->getMatrix($data));
}
}
throw new QRCodeOutputException('invalid output type');
}
/**
* checks if a string qualifies as numeric
*/
public function isNumber(string $string):bool{
return $this->checkString($string, QRDataInterface::CHAR_MAP_NUMBER);
}
/**
* checks if a string qualifies as alphanumeric
*/
public function isAlphaNum(string $string):bool{
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.
*/
protected function checkString(string $string, array $charmap):bool{
foreach(str_split($string) as $chr){
if(!isset($charmap[$chr])){
return false;
}
}
return true;
}
/**
* checks if a string qualifies as Kanji
*/
public function isKanji(string $string):bool{
$i = 0;
$len = strlen($string);
while($i + 1 < $len){
$c = ((0xff & ord($string[$i])) << 8) | (0xff & ord($string[$i + 1]));
if(!($c >= 0x8140 && $c <= 0x9FFC) && !($c >= 0xE040 && $c <= 0xEBBF)){
return false;
}
$i += 2;
}
return $i >= $len;
}
/**
* a dummy
*/
public function isByte(string $data):bool{
return $data !== '';
}
}

View File

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

View File

@@ -0,0 +1,54 @@
<?php
/**
* Class QROptions
*
* @filesource QROptions.php
* @created 08.12.2015
* @package chillerlan\QRCode
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode;
use chillerlan\Settings\SettingsContainerAbstract;
/**
* The QRCode settings container
*
* @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

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

View File

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

View File

@@ -0,0 +1,105 @@
# 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@v3
- 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@v3
- name: "Install PHP"
uses: shivammathur/setup-php@v2
with:
php-version: "8.1"
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.3.4
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@v3
- name: "Install PHP with extensions"
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
coverage: pcov
extensions: json
- name: "Install dependencies with composer"
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@v3

View File

@@ -0,0 +1,4 @@
/.build
/.idea
/vendor
composer.lock

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

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

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 Smiley <smiley@chillerlan.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,156 @@
# chillerlan/php-settings-container
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]
[![Coverage][coverage-badge]][coverage]
[![Scrunitizer][scrutinizer-badge]][scrutinizer]
[![Packagist downloads][downloads-badge]][downloads]
[![Continuous Integration][gh-action-badge]][gh-action]
[php-badge]: https://img.shields.io/packagist/php-v/chillerlan/php-settings-container?logo=php&color=8892BF
[php]: https://www.php.net/supported-versions.php
[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-settings-container.svg?logo=packagist
[packagist]: https://packagist.org/packages/chillerlan/php-settings-container
[license-badge]: https://img.shields.io/github/license/chillerlan/php-settings-container.svg
[license]: https://github.com/chillerlan/php-settings-container/blob/main/LICENSE
[coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-settings-container.svg?logo=codecov
[coverage]: https://codecov.io/github/chillerlan/php-settings-container
[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?logo=packagist
[downloads]: https://packagist.org/packages/chillerlan/php-settings-container/stats
[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-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.4 || ^8.0",
"chillerlan/php-settings-container": "dev-main"
}
}
```
Profit!
## Usage
The `SettingsContainerInterface` (wrapped in`SettingsContainerAbstract` ) provides plug-in functionality for immutable object properties and adds some fancy, like loading/saving JSON, arrays etc.
It takes an `iterable` as the only constructor argument and calls a method with the trait's name on invocation (`MyTrait::MyTrait()`) for each used trait.
### Simple usage
```php
class MyContainer extends SettingsContainerAbstract{
protected $foo;
protected $bar;
}
```
Typed properties in PHP 7.4+:
```php
class MyContainer extends SettingsContainerAbstract{
protected string $foo;
protected string $bar;
}
```
```php
// use it just like a \stdClass
$container = new MyContainer;
$container->foo = 'what';
$container->bar = 'foo';
// which is equivalent to
$container = new MyContainer(['bar' => 'foo', 'foo' => 'what']);
// ...or try
$container->fromJSON('{"foo": "what", "bar": "foo"}');
// fetch all properties as array
$container->toArray(); // -> ['foo' => 'what', 'bar' => 'foo']
// or JSON
$container->toJSON(); // -> {"foo": "what", "bar": "foo"}
// JSON via JsonSerializable
$json = json_encode($container); // -> {"foo": "what", "bar": "foo"}
//non-existing properties will be ignored:
$container->nope = 'what';
var_dump($container->nope); // -> null
```
### Advanced usage
```php
trait SomeOptions{
protected $foo;
protected $what;
// this method will be called in SettingsContainerAbstract::construct()
// after the properties have been set
protected function SomeOptions(){
// just some constructor stuff...
$this->foo = strtoupper($this->foo);
}
// this method will be called from __set() when property $what is set
protected function set_what(string $value){
$this->what = md5($value);
}
}
trait MoreOptions{
protected $bar = 'whatever'; // provide default values
}
```
```php
$commonOptions = [
// SomeOptions
'foo' => 'whatever',
// MoreOptions
'bar' => 'nothing',
];
// now plug the several library options together to a single object
$container = new class ($commonOptions) extends SettingsContainerAbstract{
use SomeOptions, MoreOptions;
};
var_dump($container->foo); // -> WHATEVER (constructor ran strtoupper on the value)
var_dump($container->bar); // -> nothing
$container->what = 'some value';
var_dump($container->what); // -> md5 hash of "some value"
```
### API
#### [`SettingsContainerAbstract`](https://github.com/chillerlan/php-settings-container/blob/main/src/SettingsContainerAbstract.php)
method | return | info
-------- | ---- | -----------
`__construct(iterable $properties = null)` | - | calls `construct()` internally after the properties have been set
(protected) `construct()` | void | calls a method with trait name as replacement constructor for each used trait
`__get(string $property)` | mixed | calls `$this->{'get_'.$property}()` if such a method exists
`__set(string $property, $value)` | void | calls `$this->{'set_'.$property}($value)` if such a method exists
`__isset(string $property)` | bool |
`__unset(string $property)` | void |
`__toString()` | string | a JSON string
`toArray()` | array |
`fromIterable(iterable $properties)` | `SettingsContainerInterface` |
`toJSON(int $jsonOptions = null)` | string | accepts [JSON options constants](http://php.net/manual/json.constants.php)
`fromJSON(string $json)` | `SettingsContainerInterface` |
`jsonSerialize()` | mixed | implements the [`JsonSerializable`](https://www.php.net/manual/en/jsonserializable.jsonserialize.php) interface
## Disclaimer
This might be either an utterly genius or completely stupid idea - you decide. However, i like it and it works.
Also, this is not a dependency injection container. Stop using DI containers FFS.

View File

@@ -0,0 +1,50 @@
{
"name": "chillerlan/php-settings-container",
"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", "configuration"
],
"authors": [
{
"name": "Smiley",
"email": "smiley@chillerlan.net",
"homepage": "https://github.com/codemasher"
}
],
"support": {
"issues": "https://github.com/chillerlan/php-settings-container/issues",
"source": "https://github.com/chillerlan/php-settings-container"
},
"require": {
"php": "^7.4 || ^8.0",
"ext-json": "*"
},
"require-dev": {
"phan/phan": "^5.3",
"phpunit/phpunit": "^9.5"
},
"autoload": {
"psr-4": {
"chillerlan\\Settings\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"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

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

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

View File

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

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

@@ -0,0 +1,162 @@
<?php
/**
* Class SettingsContainerAbstract
*
* @created 28.08.2018
* @author Smiley <smiley@chillerlan.net>
* @copyright 2018 Smiley
* @license MIT
*/
namespace chillerlan\Settings;
use ReflectionClass, ReflectionProperty;
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.
*/
public function __construct(iterable $properties = null){
if(!empty($properties)){
$this->fromIterable($properties);
}
$this->construct();
}
/**
* calls a method with trait name as replacement constructor for each used trait
* (remember pre-php5 classname constructors? yeah, basically this.)
*/
protected function construct():void{
$traits = (new ReflectionClass($this))->getTraits();
foreach($traits as $trait){
$method = $trait->getShortName();
if(method_exists($this, $method)){
$this->{$method}();
}
}
}
/**
* @inheritdoc
*/
public function __get(string $property){
if(!property_exists($this, $property) || $this->isPrivate($property)){
return null;
}
$method = 'get_'.$property;
if(method_exists($this, $method)){
return $this->{$method}();
}
return $this->{$property};
}
/**
* @inheritdoc
*/
public function __set(string $property, $value):void{
if(!property_exists($this, $property) || $this->isPrivate($property)){
return;
}
$method = 'set_'.$property;
if(method_exists($this, $method)){
$this->{$method}($value);
return;
}
$this->{$property} = $value;
}
/**
* @inheritdoc
*/
public function __isset(string $property):bool{
return isset($this->{$property}) && !$this->isPrivate($property);
}
/**
* @internal Checks if a property is private
*/
protected function isPrivate(string $property):bool{
return (new ReflectionProperty($this, $property))->isPrivate();
}
/**
* @inheritdoc
*/
public function __unset(string $property):void{
if($this->__isset($property)){
unset($this->{$property});
}
}
/**
* @inheritdoc
*/
public function __toString():string{
return $this->toJSON();
}
/**
* @inheritdoc
*/
public function toArray():array{
return get_object_vars($this);
}
/**
* @inheritdoc
*/
public function fromIterable(iterable $properties):SettingsContainerInterface{
foreach($properties as $key => $value){
$this->__set($key, $value);
}
return $this;
}
/**
* @inheritdoc
*/
public function toJSON(int $jsonOptions = null):string{
return json_encode($this, $jsonOptions ?? 0);
}
/**
* @inheritdoc
*/
public function fromJSON(string $json):SettingsContainerInterface{
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
return $this->fromIterable($data);
}
/**
* @inheritdoc
*/
#[\ReturnTypeWillChange]
public function jsonSerialize():array{
return $this->toArray();
}
}

View File

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

View File

@@ -429,7 +429,8 @@ class ClassLoader
public function loadClass($class) public function loadClass($class)
{ {
if ($file = $this->findFile($class)) { if ($file = $this->findFile($class)) {
(self::$includeFile)($file); $includeFile = self::$includeFile;
$includeFile($file);
return true; return true;
} }
@@ -560,7 +561,10 @@ class ClassLoader
return false; return false;
} }
private static function initializeIncludeClosure(): void /**
* @return void
*/
private static function initializeIncludeClosure()
{ {
if (self::$includeFile !== null) { if (self::$includeFile !== null) {
return; return;
@@ -574,8 +578,8 @@ class ClassLoader
* @param string $file * @param string $file
* @return void * @return void
*/ */
self::$includeFile = static function($file) { self::$includeFile = \Closure::bind(static function($file) {
include $file; include $file;
}; }, null, null);
} }
} }

View File

@@ -379,6 +379,27 @@ return array(
'OAuth2\\TokenType\\Bearer' => $vendorDir . '/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Bearer.php', 'OAuth2\\TokenType\\Bearer' => $vendorDir . '/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Bearer.php',
'OAuth2\\TokenType\\Mac' => $vendorDir . '/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Mac.php', 'OAuth2\\TokenType\\Mac' => $vendorDir . '/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Mac.php',
'OAuth2\\TokenType\\TokenTypeInterface' => $vendorDir . '/bshaffer/oauth2-server-php/src/OAuth2/TokenType/TokenTypeInterface.php', 'OAuth2\\TokenType\\TokenTypeInterface' => $vendorDir . '/bshaffer/oauth2-server-php/src/OAuth2/TokenType/TokenTypeInterface.php',
'OTPHP\\Factory' => $vendorDir . '/spomky-labs/otphp/src/Factory.php',
'OTPHP\\FactoryInterface' => $vendorDir . '/spomky-labs/otphp/src/FactoryInterface.php',
'OTPHP\\HOTP' => $vendorDir . '/spomky-labs/otphp/src/HOTP.php',
'OTPHP\\HOTPInterface' => $vendorDir . '/spomky-labs/otphp/src/HOTPInterface.php',
'OTPHP\\OTP' => $vendorDir . '/spomky-labs/otphp/src/OTP.php',
'OTPHP\\OTPInterface' => $vendorDir . '/spomky-labs/otphp/src/OTPInterface.php',
'OTPHP\\ParameterTrait' => $vendorDir . '/spomky-labs/otphp/src/ParameterTrait.php',
'OTPHP\\TOTP' => $vendorDir . '/spomky-labs/otphp/src/TOTP.php',
'OTPHP\\TOTPInterface' => $vendorDir . '/spomky-labs/otphp/src/TOTPInterface.php',
'OTPHP\\Url' => $vendorDir . '/spomky-labs/otphp/src/Url.php',
'ParagonIE\\ConstantTime\\Base32' => $vendorDir . '/paragonie/constant_time_encoding/src/Base32.php',
'ParagonIE\\ConstantTime\\Base32Hex' => $vendorDir . '/paragonie/constant_time_encoding/src/Base32Hex.php',
'ParagonIE\\ConstantTime\\Base64' => $vendorDir . '/paragonie/constant_time_encoding/src/Base64.php',
'ParagonIE\\ConstantTime\\Base64DotSlash' => $vendorDir . '/paragonie/constant_time_encoding/src/Base64DotSlash.php',
'ParagonIE\\ConstantTime\\Base64DotSlashOrdered' => $vendorDir . '/paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php',
'ParagonIE\\ConstantTime\\Base64UrlSafe' => $vendorDir . '/paragonie/constant_time_encoding/src/Base64UrlSafe.php',
'ParagonIE\\ConstantTime\\Binary' => $vendorDir . '/paragonie/constant_time_encoding/src/Binary.php',
'ParagonIE\\ConstantTime\\EncoderInterface' => $vendorDir . '/paragonie/constant_time_encoding/src/EncoderInterface.php',
'ParagonIE\\ConstantTime\\Encoding' => $vendorDir . '/paragonie/constant_time_encoding/src/Encoding.php',
'ParagonIE\\ConstantTime\\Hex' => $vendorDir . '/paragonie/constant_time_encoding/src/Hex.php',
'ParagonIE\\ConstantTime\\RFC4648' => $vendorDir . '/paragonie/constant_time_encoding/src/RFC4648.php',
'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/src/AbstractLogger.php', 'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/src/AbstractLogger.php',
'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/src/InvalidArgumentException.php', 'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/src/InvalidArgumentException.php',
'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/src/LogLevel.php', 'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/src/LogLevel.php',
@@ -1198,6 +1219,7 @@ return array(
'Zotlabs\\Lib\\Libzotdir' => $baseDir . '/Zotlabs/Lib/Libzotdir.php', 'Zotlabs\\Lib\\Libzotdir' => $baseDir . '/Zotlabs/Lib/Libzotdir.php',
'Zotlabs\\Lib\\MarkdownSoap' => $baseDir . '/Zotlabs/Lib/MarkdownSoap.php', 'Zotlabs\\Lib\\MarkdownSoap' => $baseDir . '/Zotlabs/Lib/MarkdownSoap.php',
'Zotlabs\\Lib\\MessageFilter' => $baseDir . '/Zotlabs/Lib/MessageFilter.php', 'Zotlabs\\Lib\\MessageFilter' => $baseDir . '/Zotlabs/Lib/MessageFilter.php',
'Zotlabs\\Lib\\ObjCache' => $baseDir . '/Zotlabs/Lib/ObjCache.php',
'Zotlabs\\Lib\\PConfig' => $baseDir . '/Zotlabs/Lib/PConfig.php', 'Zotlabs\\Lib\\PConfig' => $baseDir . '/Zotlabs/Lib/PConfig.php',
'Zotlabs\\Lib\\Permcat' => $baseDir . '/Zotlabs/Lib/Permcat.php', 'Zotlabs\\Lib\\Permcat' => $baseDir . '/Zotlabs/Lib/Permcat.php',
'Zotlabs\\Lib\\PermissionDescription' => $baseDir . '/Zotlabs/Lib/PermissionDescription.php', 'Zotlabs\\Lib\\PermissionDescription' => $baseDir . '/Zotlabs/Lib/PermissionDescription.php',
@@ -1388,6 +1410,7 @@ return array(
'Zotlabs\\Module\\Settings\\Featured' => $baseDir . '/Zotlabs/Module/Settings/Featured.php', 'Zotlabs\\Module\\Settings\\Featured' => $baseDir . '/Zotlabs/Module/Settings/Featured.php',
'Zotlabs\\Module\\Settings\\Features' => $baseDir . '/Zotlabs/Module/Settings/Features.php', 'Zotlabs\\Module\\Settings\\Features' => $baseDir . '/Zotlabs/Module/Settings/Features.php',
'Zotlabs\\Module\\Settings\\Manage' => $baseDir . '/Zotlabs/Module/Settings/Manage.php', 'Zotlabs\\Module\\Settings\\Manage' => $baseDir . '/Zotlabs/Module/Settings/Manage.php',
'Zotlabs\\Module\\Settings\\Multifactor' => $baseDir . '/Zotlabs/Module/Settings/Multifactor.php',
'Zotlabs\\Module\\Settings\\Network' => $baseDir . '/Zotlabs/Module/Settings/Network.php', 'Zotlabs\\Module\\Settings\\Network' => $baseDir . '/Zotlabs/Module/Settings/Network.php',
'Zotlabs\\Module\\Settings\\Photos' => $baseDir . '/Zotlabs/Module/Settings/Photos.php', 'Zotlabs\\Module\\Settings\\Photos' => $baseDir . '/Zotlabs/Module/Settings/Photos.php',
'Zotlabs\\Module\\Settings\\Privacy' => $baseDir . '/Zotlabs/Module/Settings/Privacy.php', 'Zotlabs\\Module\\Settings\\Privacy' => $baseDir . '/Zotlabs/Module/Settings/Privacy.php',
@@ -1415,6 +1438,7 @@ return array(
'Zotlabs\\Module\\Toggle_safesearch' => $baseDir . '/Zotlabs/Module/Toggle_safesearch.php', 'Zotlabs\\Module\\Toggle_safesearch' => $baseDir . '/Zotlabs/Module/Toggle_safesearch.php',
'Zotlabs\\Module\\Token' => $baseDir . '/Zotlabs/Module/Token.php', 'Zotlabs\\Module\\Token' => $baseDir . '/Zotlabs/Module/Token.php',
'Zotlabs\\Module\\Tokens' => $baseDir . '/Zotlabs/Module/Tokens.php', 'Zotlabs\\Module\\Tokens' => $baseDir . '/Zotlabs/Module/Tokens.php',
'Zotlabs\\Module\\Totp_check' => $baseDir . '/Zotlabs/Module/Totp_check.php',
'Zotlabs\\Module\\Uexport' => $baseDir . '/Zotlabs/Module/Uexport.php', 'Zotlabs\\Module\\Uexport' => $baseDir . '/Zotlabs/Module/Uexport.php',
'Zotlabs\\Module\\Update' => $baseDir . '/Zotlabs/Module/Update.php', 'Zotlabs\\Module\\Update' => $baseDir . '/Zotlabs/Module/Update.php',
'Zotlabs\\Module\\Userinfo' => $baseDir . '/Zotlabs/Module/Userinfo.php', 'Zotlabs\\Module\\Userinfo' => $baseDir . '/Zotlabs/Module/Userinfo.php',
@@ -1787,6 +1811,31 @@ return array(
'Zotlabs\\Zot6\\IHandler' => $baseDir . '/Zotlabs/Zot6/IHandler.php', 'Zotlabs\\Zot6\\IHandler' => $baseDir . '/Zotlabs/Zot6/IHandler.php',
'Zotlabs\\Zot6\\Receiver' => $baseDir . '/Zotlabs/Zot6/Receiver.php', 'Zotlabs\\Zot6\\Receiver' => $baseDir . '/Zotlabs/Zot6/Receiver.php',
'Zotlabs\\Zot6\\Zot6Handler' => $baseDir . '/Zotlabs/Zot6/Zot6Handler.php', 'Zotlabs\\Zot6\\Zot6Handler' => $baseDir . '/Zotlabs/Zot6/Zot6Handler.php',
'chillerlan\\QRCode\\Data\\AlphaNum' => $vendorDir . '/chillerlan/php-qrcode/src/Data/AlphaNum.php',
'chillerlan\\QRCode\\Data\\Byte' => $vendorDir . '/chillerlan/php-qrcode/src/Data/Byte.php',
'chillerlan\\QRCode\\Data\\Kanji' => $vendorDir . '/chillerlan/php-qrcode/src/Data/Kanji.php',
'chillerlan\\QRCode\\Data\\MaskPatternTester' => $vendorDir . '/chillerlan/php-qrcode/src/Data/MaskPatternTester.php',
'chillerlan\\QRCode\\Data\\Number' => $vendorDir . '/chillerlan/php-qrcode/src/Data/Number.php',
'chillerlan\\QRCode\\Data\\QRCodeDataException' => $vendorDir . '/chillerlan/php-qrcode/src/Data/QRCodeDataException.php',
'chillerlan\\QRCode\\Data\\QRDataAbstract' => $vendorDir . '/chillerlan/php-qrcode/src/Data/QRDataAbstract.php',
'chillerlan\\QRCode\\Data\\QRDataInterface' => $vendorDir . '/chillerlan/php-qrcode/src/Data/QRDataInterface.php',
'chillerlan\\QRCode\\Data\\QRMatrix' => $vendorDir . '/chillerlan/php-qrcode/src/Data/QRMatrix.php',
'chillerlan\\QRCode\\Helpers\\BitBuffer' => $vendorDir . '/chillerlan/php-qrcode/src/Helpers/BitBuffer.php',
'chillerlan\\QRCode\\Helpers\\Polynomial' => $vendorDir . '/chillerlan/php-qrcode/src/Helpers/Polynomial.php',
'chillerlan\\QRCode\\Output\\QRCodeOutputException' => $vendorDir . '/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php',
'chillerlan\\QRCode\\Output\\QRFpdf' => $vendorDir . '/chillerlan/php-qrcode/src/Output/QRFpdf.php',
'chillerlan\\QRCode\\Output\\QRImage' => $vendorDir . '/chillerlan/php-qrcode/src/Output/QRImage.php',
'chillerlan\\QRCode\\Output\\QRImagick' => $vendorDir . '/chillerlan/php-qrcode/src/Output/QRImagick.php',
'chillerlan\\QRCode\\Output\\QRMarkup' => $vendorDir . '/chillerlan/php-qrcode/src/Output/QRMarkup.php',
'chillerlan\\QRCode\\Output\\QROutputAbstract' => $vendorDir . '/chillerlan/php-qrcode/src/Output/QROutputAbstract.php',
'chillerlan\\QRCode\\Output\\QROutputInterface' => $vendorDir . '/chillerlan/php-qrcode/src/Output/QROutputInterface.php',
'chillerlan\\QRCode\\Output\\QRString' => $vendorDir . '/chillerlan/php-qrcode/src/Output/QRString.php',
'chillerlan\\QRCode\\QRCode' => $vendorDir . '/chillerlan/php-qrcode/src/QRCode.php',
'chillerlan\\QRCode\\QRCodeException' => $vendorDir . '/chillerlan/php-qrcode/src/QRCodeException.php',
'chillerlan\\QRCode\\QROptions' => $vendorDir . '/chillerlan/php-qrcode/src/QROptions.php',
'chillerlan\\QRCode\\QROptionsTrait' => $vendorDir . '/chillerlan/php-qrcode/src/QROptionsTrait.php',
'chillerlan\\Settings\\SettingsContainerAbstract' => $vendorDir . '/chillerlan/php-settings-container/src/SettingsContainerAbstract.php',
'chillerlan\\Settings\\SettingsContainerInterface' => $vendorDir . '/chillerlan/php-settings-container/src/SettingsContainerInterface.php',
'phpseclib\\Crypt\\AES' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/AES.php', 'phpseclib\\Crypt\\AES' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/AES.php',
'phpseclib\\Crypt\\Base' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Base.php', 'phpseclib\\Crypt\\Base' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Base.php',
'phpseclib\\Crypt\\Blowfish' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php', 'phpseclib\\Crypt\\Blowfish' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php',

View File

@@ -8,6 +8,8 @@ $baseDir = dirname($vendorDir);
return array( return array(
'voku\\' => array($vendorDir . '/voku/portable-ascii/src/voku', $vendorDir . '/voku/stop-words/src/voku'), 'voku\\' => array($vendorDir . '/voku/portable-ascii/src/voku', $vendorDir . '/voku/stop-words/src/voku'),
'phpseclib\\' => array($vendorDir . '/phpseclib/phpseclib/phpseclib'), 'phpseclib\\' => array($vendorDir . '/phpseclib/phpseclib/phpseclib'),
'chillerlan\\Settings\\' => array($vendorDir . '/chillerlan/php-settings-container/src'),
'chillerlan\\QRCode\\' => array($vendorDir . '/chillerlan/php-qrcode/src'),
'Zotlabs\\' => array($baseDir . '/Zotlabs'), 'Zotlabs\\' => array($baseDir . '/Zotlabs'),
'Symfony\\Polyfill\\Php81\\' => array($vendorDir . '/symfony/polyfill-php81'), 'Symfony\\Polyfill\\Php81\\' => array($vendorDir . '/symfony/polyfill-php81'),
'SimplePie\\' => array($vendorDir . '/simplepie/simplepie/src'), 'SimplePie\\' => array($vendorDir . '/simplepie/simplepie/src'),
@@ -23,6 +25,8 @@ return array(
'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'), 'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'),
'Ramsey\\Collection\\' => array($vendorDir . '/ramsey/collection/src'), 'Ramsey\\Collection\\' => array($vendorDir . '/ramsey/collection/src'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/src'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/src'),
'ParagonIE\\ConstantTime\\' => array($vendorDir . '/paragonie/constant_time_encoding/src'),
'OTPHP\\' => array($vendorDir . '/spomky-labs/otphp/src'),
'Michelf\\' => array($vendorDir . '/michelf/php-markdown/Michelf'), 'Michelf\\' => array($vendorDir . '/michelf/php-markdown/Michelf'),
'League\\HTMLToMarkdown\\' => array($vendorDir . '/league/html-to-markdown/src'), 'League\\HTMLToMarkdown\\' => array($vendorDir . '/league/html-to-markdown/src'),
'ID3Parser\\' => array($vendorDir . '/lukasreschke/id3parser/src'), 'ID3Parser\\' => array($vendorDir . '/lukasreschke/id3parser/src'),

View File

@@ -38,15 +38,15 @@ class ComposerAutoloaderInit7b34d7e50a62201ec5d5e526a5b8b35d
$loader->register(true); $loader->register(true);
$filesToLoad = \Composer\Autoload\ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d::$files; $filesToLoad = \Composer\Autoload\ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d::$files;
$requireFile = static function ($fileIdentifier, $file) { $requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file; require $file;
} }
}; }, null, null);
foreach ($filesToLoad as $fileIdentifier => $file) { foreach ($filesToLoad as $fileIdentifier => $file) {
($requireFile)($fileIdentifier, $file); $requireFile($fileIdentifier, $file);
} }
return $loader; return $loader;

View File

@@ -29,6 +29,11 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
array ( array (
'phpseclib\\' => 10, 'phpseclib\\' => 10,
), ),
'c' =>
array (
'chillerlan\\Settings\\' => 20,
'chillerlan\\QRCode\\' => 18,
),
'Z' => 'Z' =>
array ( array (
'Zotlabs\\' => 8, 'Zotlabs\\' => 8,
@@ -55,6 +60,11 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'P' => 'P' =>
array ( array (
'Psr\\Log\\' => 8, 'Psr\\Log\\' => 8,
'ParagonIE\\ConstantTime\\' => 23,
),
'O' =>
array (
'OTPHP\\' => 6,
), ),
'M' => 'M' =>
array ( array (
@@ -92,6 +102,14 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
array ( array (
0 => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib', 0 => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib',
), ),
'chillerlan\\Settings\\' =>
array (
0 => __DIR__ . '/..' . '/chillerlan/php-settings-container/src',
),
'chillerlan\\QRCode\\' =>
array (
0 => __DIR__ . '/..' . '/chillerlan/php-qrcode/src',
),
'Zotlabs\\' => 'Zotlabs\\' =>
array ( array (
0 => __DIR__ . '/../..' . '/Zotlabs', 0 => __DIR__ . '/../..' . '/Zotlabs',
@@ -152,6 +170,14 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
array ( array (
0 => __DIR__ . '/..' . '/psr/log/src', 0 => __DIR__ . '/..' . '/psr/log/src',
), ),
'ParagonIE\\ConstantTime\\' =>
array (
0 => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src',
),
'OTPHP\\' =>
array (
0 => __DIR__ . '/..' . '/spomky-labs/otphp/src',
),
'Michelf\\' => 'Michelf\\' =>
array ( array (
0 => __DIR__ . '/..' . '/michelf/php-markdown/Michelf', 0 => __DIR__ . '/..' . '/michelf/php-markdown/Michelf',
@@ -590,6 +616,27 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'OAuth2\\TokenType\\Bearer' => __DIR__ . '/..' . '/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Bearer.php', 'OAuth2\\TokenType\\Bearer' => __DIR__ . '/..' . '/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Bearer.php',
'OAuth2\\TokenType\\Mac' => __DIR__ . '/..' . '/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Mac.php', 'OAuth2\\TokenType\\Mac' => __DIR__ . '/..' . '/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Mac.php',
'OAuth2\\TokenType\\TokenTypeInterface' => __DIR__ . '/..' . '/bshaffer/oauth2-server-php/src/OAuth2/TokenType/TokenTypeInterface.php', 'OAuth2\\TokenType\\TokenTypeInterface' => __DIR__ . '/..' . '/bshaffer/oauth2-server-php/src/OAuth2/TokenType/TokenTypeInterface.php',
'OTPHP\\Factory' => __DIR__ . '/..' . '/spomky-labs/otphp/src/Factory.php',
'OTPHP\\FactoryInterface' => __DIR__ . '/..' . '/spomky-labs/otphp/src/FactoryInterface.php',
'OTPHP\\HOTP' => __DIR__ . '/..' . '/spomky-labs/otphp/src/HOTP.php',
'OTPHP\\HOTPInterface' => __DIR__ . '/..' . '/spomky-labs/otphp/src/HOTPInterface.php',
'OTPHP\\OTP' => __DIR__ . '/..' . '/spomky-labs/otphp/src/OTP.php',
'OTPHP\\OTPInterface' => __DIR__ . '/..' . '/spomky-labs/otphp/src/OTPInterface.php',
'OTPHP\\ParameterTrait' => __DIR__ . '/..' . '/spomky-labs/otphp/src/ParameterTrait.php',
'OTPHP\\TOTP' => __DIR__ . '/..' . '/spomky-labs/otphp/src/TOTP.php',
'OTPHP\\TOTPInterface' => __DIR__ . '/..' . '/spomky-labs/otphp/src/TOTPInterface.php',
'OTPHP\\Url' => __DIR__ . '/..' . '/spomky-labs/otphp/src/Url.php',
'ParagonIE\\ConstantTime\\Base32' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/Base32.php',
'ParagonIE\\ConstantTime\\Base32Hex' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/Base32Hex.php',
'ParagonIE\\ConstantTime\\Base64' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/Base64.php',
'ParagonIE\\ConstantTime\\Base64DotSlash' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/Base64DotSlash.php',
'ParagonIE\\ConstantTime\\Base64DotSlashOrdered' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php',
'ParagonIE\\ConstantTime\\Base64UrlSafe' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/Base64UrlSafe.php',
'ParagonIE\\ConstantTime\\Binary' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/Binary.php',
'ParagonIE\\ConstantTime\\EncoderInterface' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/EncoderInterface.php',
'ParagonIE\\ConstantTime\\Encoding' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/Encoding.php',
'ParagonIE\\ConstantTime\\Hex' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/Hex.php',
'ParagonIE\\ConstantTime\\RFC4648' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/RFC4648.php',
'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/src/AbstractLogger.php', 'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/src/AbstractLogger.php',
'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/src/InvalidArgumentException.php', 'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/src/InvalidArgumentException.php',
'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/src/LogLevel.php', 'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/src/LogLevel.php',
@@ -1409,6 +1456,7 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'Zotlabs\\Lib\\Libzotdir' => __DIR__ . '/../..' . '/Zotlabs/Lib/Libzotdir.php', 'Zotlabs\\Lib\\Libzotdir' => __DIR__ . '/../..' . '/Zotlabs/Lib/Libzotdir.php',
'Zotlabs\\Lib\\MarkdownSoap' => __DIR__ . '/../..' . '/Zotlabs/Lib/MarkdownSoap.php', 'Zotlabs\\Lib\\MarkdownSoap' => __DIR__ . '/../..' . '/Zotlabs/Lib/MarkdownSoap.php',
'Zotlabs\\Lib\\MessageFilter' => __DIR__ . '/../..' . '/Zotlabs/Lib/MessageFilter.php', 'Zotlabs\\Lib\\MessageFilter' => __DIR__ . '/../..' . '/Zotlabs/Lib/MessageFilter.php',
'Zotlabs\\Lib\\ObjCache' => __DIR__ . '/../..' . '/Zotlabs/Lib/ObjCache.php',
'Zotlabs\\Lib\\PConfig' => __DIR__ . '/../..' . '/Zotlabs/Lib/PConfig.php', 'Zotlabs\\Lib\\PConfig' => __DIR__ . '/../..' . '/Zotlabs/Lib/PConfig.php',
'Zotlabs\\Lib\\Permcat' => __DIR__ . '/../..' . '/Zotlabs/Lib/Permcat.php', 'Zotlabs\\Lib\\Permcat' => __DIR__ . '/../..' . '/Zotlabs/Lib/Permcat.php',
'Zotlabs\\Lib\\PermissionDescription' => __DIR__ . '/../..' . '/Zotlabs/Lib/PermissionDescription.php', 'Zotlabs\\Lib\\PermissionDescription' => __DIR__ . '/../..' . '/Zotlabs/Lib/PermissionDescription.php',
@@ -1599,6 +1647,7 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'Zotlabs\\Module\\Settings\\Featured' => __DIR__ . '/../..' . '/Zotlabs/Module/Settings/Featured.php', 'Zotlabs\\Module\\Settings\\Featured' => __DIR__ . '/../..' . '/Zotlabs/Module/Settings/Featured.php',
'Zotlabs\\Module\\Settings\\Features' => __DIR__ . '/../..' . '/Zotlabs/Module/Settings/Features.php', 'Zotlabs\\Module\\Settings\\Features' => __DIR__ . '/../..' . '/Zotlabs/Module/Settings/Features.php',
'Zotlabs\\Module\\Settings\\Manage' => __DIR__ . '/../..' . '/Zotlabs/Module/Settings/Manage.php', 'Zotlabs\\Module\\Settings\\Manage' => __DIR__ . '/../..' . '/Zotlabs/Module/Settings/Manage.php',
'Zotlabs\\Module\\Settings\\Multifactor' => __DIR__ . '/../..' . '/Zotlabs/Module/Settings/Multifactor.php',
'Zotlabs\\Module\\Settings\\Network' => __DIR__ . '/../..' . '/Zotlabs/Module/Settings/Network.php', 'Zotlabs\\Module\\Settings\\Network' => __DIR__ . '/../..' . '/Zotlabs/Module/Settings/Network.php',
'Zotlabs\\Module\\Settings\\Photos' => __DIR__ . '/../..' . '/Zotlabs/Module/Settings/Photos.php', 'Zotlabs\\Module\\Settings\\Photos' => __DIR__ . '/../..' . '/Zotlabs/Module/Settings/Photos.php',
'Zotlabs\\Module\\Settings\\Privacy' => __DIR__ . '/../..' . '/Zotlabs/Module/Settings/Privacy.php', 'Zotlabs\\Module\\Settings\\Privacy' => __DIR__ . '/../..' . '/Zotlabs/Module/Settings/Privacy.php',
@@ -1626,6 +1675,7 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'Zotlabs\\Module\\Toggle_safesearch' => __DIR__ . '/../..' . '/Zotlabs/Module/Toggle_safesearch.php', 'Zotlabs\\Module\\Toggle_safesearch' => __DIR__ . '/../..' . '/Zotlabs/Module/Toggle_safesearch.php',
'Zotlabs\\Module\\Token' => __DIR__ . '/../..' . '/Zotlabs/Module/Token.php', 'Zotlabs\\Module\\Token' => __DIR__ . '/../..' . '/Zotlabs/Module/Token.php',
'Zotlabs\\Module\\Tokens' => __DIR__ . '/../..' . '/Zotlabs/Module/Tokens.php', 'Zotlabs\\Module\\Tokens' => __DIR__ . '/../..' . '/Zotlabs/Module/Tokens.php',
'Zotlabs\\Module\\Totp_check' => __DIR__ . '/../..' . '/Zotlabs/Module/Totp_check.php',
'Zotlabs\\Module\\Uexport' => __DIR__ . '/../..' . '/Zotlabs/Module/Uexport.php', 'Zotlabs\\Module\\Uexport' => __DIR__ . '/../..' . '/Zotlabs/Module/Uexport.php',
'Zotlabs\\Module\\Update' => __DIR__ . '/../..' . '/Zotlabs/Module/Update.php', 'Zotlabs\\Module\\Update' => __DIR__ . '/../..' . '/Zotlabs/Module/Update.php',
'Zotlabs\\Module\\Userinfo' => __DIR__ . '/../..' . '/Zotlabs/Module/Userinfo.php', 'Zotlabs\\Module\\Userinfo' => __DIR__ . '/../..' . '/Zotlabs/Module/Userinfo.php',
@@ -1998,6 +2048,31 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'Zotlabs\\Zot6\\IHandler' => __DIR__ . '/../..' . '/Zotlabs/Zot6/IHandler.php', 'Zotlabs\\Zot6\\IHandler' => __DIR__ . '/../..' . '/Zotlabs/Zot6/IHandler.php',
'Zotlabs\\Zot6\\Receiver' => __DIR__ . '/../..' . '/Zotlabs/Zot6/Receiver.php', 'Zotlabs\\Zot6\\Receiver' => __DIR__ . '/../..' . '/Zotlabs/Zot6/Receiver.php',
'Zotlabs\\Zot6\\Zot6Handler' => __DIR__ . '/../..' . '/Zotlabs/Zot6/Zot6Handler.php', 'Zotlabs\\Zot6\\Zot6Handler' => __DIR__ . '/../..' . '/Zotlabs/Zot6/Zot6Handler.php',
'chillerlan\\QRCode\\Data\\AlphaNum' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Data/AlphaNum.php',
'chillerlan\\QRCode\\Data\\Byte' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Data/Byte.php',
'chillerlan\\QRCode\\Data\\Kanji' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Data/Kanji.php',
'chillerlan\\QRCode\\Data\\MaskPatternTester' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Data/MaskPatternTester.php',
'chillerlan\\QRCode\\Data\\Number' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Data/Number.php',
'chillerlan\\QRCode\\Data\\QRCodeDataException' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Data/QRCodeDataException.php',
'chillerlan\\QRCode\\Data\\QRDataAbstract' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Data/QRDataAbstract.php',
'chillerlan\\QRCode\\Data\\QRDataInterface' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Data/QRDataInterface.php',
'chillerlan\\QRCode\\Data\\QRMatrix' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Data/QRMatrix.php',
'chillerlan\\QRCode\\Helpers\\BitBuffer' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Helpers/BitBuffer.php',
'chillerlan\\QRCode\\Helpers\\Polynomial' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Helpers/Polynomial.php',
'chillerlan\\QRCode\\Output\\QRCodeOutputException' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php',
'chillerlan\\QRCode\\Output\\QRFpdf' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Output/QRFpdf.php',
'chillerlan\\QRCode\\Output\\QRImage' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Output/QRImage.php',
'chillerlan\\QRCode\\Output\\QRImagick' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Output/QRImagick.php',
'chillerlan\\QRCode\\Output\\QRMarkup' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Output/QRMarkup.php',
'chillerlan\\QRCode\\Output\\QROutputAbstract' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Output/QROutputAbstract.php',
'chillerlan\\QRCode\\Output\\QROutputInterface' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Output/QROutputInterface.php',
'chillerlan\\QRCode\\Output\\QRString' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/Output/QRString.php',
'chillerlan\\QRCode\\QRCode' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/QRCode.php',
'chillerlan\\QRCode\\QRCodeException' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/QRCodeException.php',
'chillerlan\\QRCode\\QROptions' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/QROptions.php',
'chillerlan\\QRCode\\QROptionsTrait' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/QROptionsTrait.php',
'chillerlan\\Settings\\SettingsContainerAbstract' => __DIR__ . '/..' . '/chillerlan/php-settings-container/src/SettingsContainerAbstract.php',
'chillerlan\\Settings\\SettingsContainerInterface' => __DIR__ . '/..' . '/chillerlan/php-settings-container/src/SettingsContainerInterface.php',
'phpseclib\\Crypt\\AES' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/AES.php', 'phpseclib\\Crypt\\AES' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/AES.php',
'phpseclib\\Crypt\\Base' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Base.php', 'phpseclib\\Crypt\\Base' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Base.php',
'phpseclib\\Crypt\\Blowfish' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php', 'phpseclib\\Crypt\\Blowfish' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php',

View File

@@ -197,6 +197,154 @@
}, },
"install-path": "../bshaffer/oauth2-server-php" "install-path": "../bshaffer/oauth2-server-php"
}, },
{
"name": "chillerlan/php-qrcode",
"version": "4.3.4",
"version_normalized": "4.3.4.0",
"source": {
"type": "git",
"url": "https://github.com/chillerlan/php-qrcode.git",
"reference": "2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d",
"reference": "2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d",
"shasum": ""
},
"require": {
"chillerlan/php-settings-container": "^2.1.4",
"ext-mbstring": "*",
"php": "^7.4 || ^8.0"
},
"require-dev": {
"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": "2022-07-25T09:12:45+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"chillerlan\\QRCode\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kazuhiko Arase",
"homepage": "https://github.com/kazuhikoarase"
},
{
"name": "Smiley",
"email": "smiley@chillerlan.net",
"homepage": "https://github.com/codemasher"
},
{
"name": "Contributors",
"homepage": "https://github.com/chillerlan/php-qrcode/graphs/contributors"
}
],
"description": "A QR code generator. PHP 7.4+",
"homepage": "https://github.com/chillerlan/php-qrcode",
"keywords": [
"phpqrcode",
"qr",
"qr code",
"qrcode",
"qrcode-generator"
],
"support": {
"issues": "https://github.com/chillerlan/php-qrcode/issues",
"source": "https://github.com/chillerlan/php-qrcode/tree/4.3.4"
},
"funding": [
{
"url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4",
"type": "custom"
},
{
"url": "https://ko-fi.com/codemasher",
"type": "ko_fi"
}
],
"install-path": "../chillerlan/php-qrcode"
},
{
"name": "chillerlan/php-settings-container",
"version": "2.1.4",
"version_normalized": "2.1.4.0",
"source": {
"type": "git",
"url": "https://github.com/chillerlan/php-settings-container.git",
"reference": "1beb7df3c14346d4344b0b2e12f6f9a74feabd4a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/1beb7df3c14346d4344b0b2e12f6f9a74feabd4a",
"reference": "1beb7df3c14346d4344b0b2e12f6f9a74feabd4a",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": "^7.4 || ^8.0"
},
"require-dev": {
"phan/phan": "^5.3",
"phpunit/phpunit": "^9.5"
},
"time": "2022-07-05T22:32:14+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"chillerlan\\Settings\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Smiley",
"email": "smiley@chillerlan.net",
"homepage": "https://github.com/codemasher"
}
],
"description": "A container class for immutable settings objects. Not a DI container. PHP 7.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",
"type": "custom"
},
{
"url": "https://ko-fi.com/codemasher",
"type": "ko_fi"
}
],
"install-path": "../chillerlan/php-settings-container"
},
{ {
"name": "commerceguys/intl", "name": "commerceguys/intl",
"version": "v1.1.1", "version": "v1.1.1",
@@ -618,6 +766,76 @@
}, },
"install-path": "../michelf/php-markdown" "install-path": "../michelf/php-markdown"
}, },
{
"name": "paragonie/constant_time_encoding",
"version": "v2.6.3",
"version_normalized": "2.6.3.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/constant_time_encoding.git",
"reference": "58c3f47f650c94ec05a151692652a868995d2938"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938",
"reference": "58c3f47f650c94ec05a151692652a868995d2938",
"shasum": ""
},
"require": {
"php": "^7|^8"
},
"require-dev": {
"phpunit/phpunit": "^6|^7|^8|^9",
"vimeo/psalm": "^1|^2|^3|^4"
},
"time": "2022-06-14T06:56:20+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"ParagonIE\\ConstantTime\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com",
"role": "Maintainer"
},
{
"name": "Steve 'Sc00bz' Thomas",
"email": "steve@tobtu.com",
"homepage": "https://www.tobtu.com",
"role": "Original Developer"
}
],
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
"keywords": [
"base16",
"base32",
"base32_decode",
"base32_encode",
"base64",
"base64_decode",
"base64_encode",
"bin2hex",
"encoding",
"hex",
"hex2bin",
"rfc4648"
],
"support": {
"email": "info@paragonie.com",
"issues": "https://github.com/paragonie/constant_time_encoding/issues",
"source": "https://github.com/paragonie/constant_time_encoding"
},
"install-path": "../paragonie/constant_time_encoding"
},
{ {
"name": "pear/text_languagedetect", "name": "pear/text_languagedetect",
"version": "v1.0.1", "version": "v1.0.1",
@@ -1634,6 +1852,89 @@
}, },
"install-path": "../smarty/smarty" "install-path": "../smarty/smarty"
}, },
{
"name": "spomky-labs/otphp",
"version": "11.1.0",
"version_normalized": "11.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/Spomky-Labs/otphp.git",
"reference": "4849ac1aa560bfc56c0d1534b0d72532da4665ab"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/4849ac1aa560bfc56c0d1534b0d72532da4665ab",
"reference": "4849ac1aa560bfc56c0d1534b0d72532da4665ab",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"paragonie/constant_time_encoding": "^2.0",
"php": "^8.1"
},
"require-dev": {
"ekino/phpstan-banned-code": "^1.0",
"infection/infection": "^0.26",
"php-parallel-lint/php-parallel-lint": "^1.3",
"phpstan/phpstan": "^1.0",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": "^9.5.26",
"qossmic/deptrac-shim": "^1.0",
"rector/rector": "^0.14",
"symfony/phpunit-bridge": "^6.1",
"symplify/easy-coding-standard": "^11.0"
},
"time": "2022-11-11T12:57:17+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"OTPHP\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky"
},
{
"name": "All contributors",
"homepage": "https://github.com/Spomky-Labs/otphp/contributors"
}
],
"description": "A PHP library for generating one time passwords according to RFC 4226 (HOTP Algorithm) and the RFC 6238 (TOTP Algorithm) and compatible with Google Authenticator",
"homepage": "https://github.com/Spomky-Labs/otphp",
"keywords": [
"FreeOTP",
"RFC 4226",
"RFC 6238",
"google authenticator",
"hotp",
"otp",
"totp"
],
"support": {
"issues": "https://github.com/Spomky-Labs/otphp/issues",
"source": "https://github.com/Spomky-Labs/otphp/tree/11.1.0"
},
"funding": [
{
"url": "https://github.com/Spomky",
"type": "github"
},
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"install-path": "../spomky-labs/otphp"
},
{ {
"name": "symfony/polyfill-php81", "name": "symfony/polyfill-php81",
"version": "v1.26.0", "version": "v1.26.0",

View File

@@ -3,7 +3,7 @@
'name' => 'zotlabs/hubzilla', 'name' => 'zotlabs/hubzilla',
'pretty_version' => 'dev-master', 'pretty_version' => 'dev-master',
'version' => 'dev-master', 'version' => 'dev-master',
'reference' => 'c3d3dc9d92d14dbcfbaf39c4cc9ad4c120812a3d', 'reference' => 'd43a56614cd93982d19f4f82aae6e62f9ca533a9',
'type' => 'application', 'type' => 'application',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@@ -37,6 +37,24 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'chillerlan/php-qrcode' => array(
'pretty_version' => '4.3.4',
'version' => '4.3.4.0',
'reference' => '2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d',
'type' => 'library',
'install_path' => __DIR__ . '/../chillerlan/php-qrcode',
'aliases' => array(),
'dev_requirement' => false,
),
'chillerlan/php-settings-container' => array(
'pretty_version' => '2.1.4',
'version' => '2.1.4.0',
'reference' => '1beb7df3c14346d4344b0b2e12f6f9a74feabd4a',
'type' => 'library',
'install_path' => __DIR__ . '/../chillerlan/php-settings-container',
'aliases' => array(),
'dev_requirement' => false,
),
'commerceguys/intl' => array( 'commerceguys/intl' => array(
'pretty_version' => 'v1.1.1', 'pretty_version' => 'v1.1.1',
'version' => '1.1.1.0', 'version' => '1.1.1.0',
@@ -100,6 +118,15 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'paragonie/constant_time_encoding' => array(
'pretty_version' => 'v2.6.3',
'version' => '2.6.3.0',
'reference' => '58c3f47f650c94ec05a151692652a868995d2938',
'type' => 'library',
'install_path' => __DIR__ . '/../paragonie/constant_time_encoding',
'aliases' => array(),
'dev_requirement' => false,
),
'pear/text_languagedetect' => array( 'pear/text_languagedetect' => array(
'pretty_version' => 'v1.0.1', 'pretty_version' => 'v1.0.1',
'version' => '1.0.1.0', 'version' => '1.0.1.0',
@@ -223,6 +250,15 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'spomky-labs/otphp' => array(
'pretty_version' => '11.1.0',
'version' => '11.1.0.0',
'reference' => '4849ac1aa560bfc56c0d1534b0d72532da4665ab',
'type' => 'library',
'install_path' => __DIR__ . '/../spomky-labs/otphp',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-php81' => array( 'symfony/polyfill-php81' => array(
'pretty_version' => 'v1.26.0', 'pretty_version' => 'v1.26.0',
'version' => '1.26.0.0', 'version' => '1.26.0.0',
@@ -268,7 +304,7 @@
'zotlabs/hubzilla' => array( 'zotlabs/hubzilla' => array(
'pretty_version' => 'dev-master', 'pretty_version' => 'dev-master',
'version' => 'dev-master', 'version' => 'dev-master',
'reference' => 'c3d3dc9d92d14dbcfbaf39c4cc9ad4c120812a3d', 'reference' => 'd43a56614cd93982d19f4f82aae6e62f9ca533a9',
'type' => 'application', 'type' => 'application',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),

View File

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

View File

@@ -0,0 +1,48 @@
The MIT License (MIT)
Copyright (c) 2016 - 2022 Paragon Initiative Enterprises
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
This library was based on the work of Steve "Sc00bz" Thomas.
------------------------------------------------------------------------------
The MIT License (MIT)
Copyright (c) 2014 Steve Thomas
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,84 @@
# Constant-Time Encoding
[![Build Status](https://github.com/paragonie/constant_time_encoding/actions/workflows/ci.yml/badge.svg)](https://github.com/paragonie/constant_time_encoding/actions)
[![Latest Stable Version](https://poser.pugx.org/paragonie/constant_time_encoding/v/stable)](https://packagist.org/packages/paragonie/constant_time_encoding)
[![Latest Unstable Version](https://poser.pugx.org/paragonie/constant_time_encoding/v/unstable)](https://packagist.org/packages/paragonie/constant_time_encoding)
[![License](https://poser.pugx.org/paragonie/constant_time_encoding/license)](https://packagist.org/packages/paragonie/constant_time_encoding)
[![Downloads](https://img.shields.io/packagist/dt/paragonie/constant_time_encoding.svg)](https://packagist.org/packages/paragonie/constant_time_encoding)
Based on the [constant-time base64 implementation made by Steve "Sc00bz" Thomas](https://github.com/Sc00bz/ConstTimeEncoding),
this library aims to offer character encoding functions that do not leak
information about what you are encoding/decoding via processor cache
misses. Further reading on [cache-timing attacks](http://blog.ircmaxell.com/2014/11/its-all-about-time.html).
Our fork offers the following enchancements:
* `mbstring.func_overload` resistance
* Unit tests
* Composer- and Packagist-ready
* Base16 encoding
* Base32 encoding
* Uses `pack()` and `unpack()` instead of `chr()` and `ord()`
## PHP Version Requirements
Version 2 of this library should work on **PHP 7** or newer. For PHP 5
support, see [the v1.x branch](https://github.com/paragonie/constant_time_encoding/tree/v1.x).
If you are adding this as a dependency to a project intended to work on both PHP 5 and PHP 7, please set the required version to `^1|^2` instead of just `^1` or `^2`.
## How to Install
```sh
composer require paragonie/constant_time_encoding
```
## How to Use
```php
use ParagonIE\ConstantTime\Encoding;
// possibly (if applicable):
// require 'vendor/autoload.php';
$data = random_bytes(32);
echo Encoding::base64Encode($data), "\n";
echo Encoding::base32EncodeUpper($data), "\n";
echo Encoding::base32Encode($data), "\n";
echo Encoding::hexEncode($data), "\n";
echo Encoding::hexEncodeUpper($data), "\n";
```
Example output:
```
1VilPkeVqirlPifk5scbzcTTbMT2clp+Zkyv9VFFasE=
2VMKKPSHSWVCVZJ6E7SONRY3ZXCNG3GE6ZZFU7TGJSX7KUKFNLAQ====
2vmkkpshswvcvzj6e7sonry3zxcng3ge6zzfu7tgjsx7kukfnlaq====
d558a53e4795aa2ae53e27e4e6c71bcdc4d36cc4f6725a7e664caff551456ac1
D558A53E4795AA2AE53E27E4E6C71BDCC4D36CC4F6725A7E664CAFF551456AC1
```
If you only need a particular variant, you can just reference the
required class like so:
```php
use ParagonIE\ConstantTime\Base64;
use ParagonIE\ConstantTime\Base32;
$data = random_bytes(32);
echo Base64::encode($data), "\n";
echo Base32::encode($data), "\n";
```
Example output:
```
1VilPkeVqirlPifk5scbzcTTbMT2clp+Zkyv9VFFasE=
2vmkkpshswvcvzj6e7sonry3zxcng3ge6zzfu7tgjsx7kukfnlaq====
```
## Support Contracts
If your company uses this library in their products or services, you may be
interested in [purchasing a support contract from Paragon Initiative Enterprises](https://paragonie.com/enterprise).

View File

@@ -0,0 +1,56 @@
{
"name": "paragonie/constant_time_encoding",
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
"keywords": [
"base64",
"encoding",
"rfc4648",
"base32",
"base16",
"hex",
"bin2hex",
"hex2bin",
"base64_encode",
"base64_decode",
"base32_encode",
"base32_decode"
],
"license": "MIT",
"type": "library",
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com",
"role": "Maintainer"
},
{
"name": "Steve 'Sc00bz' Thomas",
"email": "steve@tobtu.com",
"homepage": "https://www.tobtu.com",
"role": "Original Developer"
}
],
"support": {
"issues": "https://github.com/paragonie/constant_time_encoding/issues",
"email": "info@paragonie.com",
"source": "https://github.com/paragonie/constant_time_encoding"
},
"require": {
"php": "^7|^8"
},
"require-dev": {
"phpunit/phpunit": "^6|^7|^8|^9",
"vimeo/psalm": "^1|^2|^3|^4"
},
"autoload": {
"psr-4": {
"ParagonIE\\ConstantTime\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"ParagonIE\\ConstantTime\\Tests\\": "tests/"
}
}
}

View File

@@ -0,0 +1,519 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
use InvalidArgumentException;
use RangeException;
use TypeError;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class Base32
* [A-Z][2-7]
*
* @package ParagonIE\ConstantTime
*/
abstract class Base32 implements EncoderInterface
{
/**
* Decode a Base32-encoded string into raw binary
*
* @param string $encodedString
* @param bool $strictPadding
* @return string
*/
public static function decode(string $encodedString, bool $strictPadding = false): string
{
return static::doDecode($encodedString, false, $strictPadding);
}
/**
* Decode an uppercase Base32-encoded string into raw binary
*
* @param string $src
* @param bool $strictPadding
* @return string
*/
public static function decodeUpper(string $src, bool $strictPadding = false): string
{
return static::doDecode($src, true, $strictPadding);
}
/**
* Encode into Base32 (RFC 4648)
*
* @param string $binString
* @return string
* @throws TypeError
*/
public static function encode(string $binString): string
{
return static::doEncode($binString, false, true);
}
/**
* Encode into Base32 (RFC 4648)
*
* @param string $src
* @return string
* @throws TypeError
*/
public static function encodeUnpadded(string $src): string
{
return static::doEncode($src, false, false);
}
/**
* Encode into uppercase Base32 (RFC 4648)
*
* @param string $src
* @return string
* @throws TypeError
*/
public static function encodeUpper(string $src): string
{
return static::doEncode($src, true, true);
}
/**
* Encode into uppercase Base32 (RFC 4648)
*
* @param string $src
* @return string
* @throws TypeError
*/
public static function encodeUpperUnpadded(string $src): string
{
return static::doEncode($src, true, false);
}
/**
* Uses bitwise operators instead of table-lookups to turn 5-bit integers
* into 8-bit integers.
*
* @param int $src
* @return int
*/
protected static function decode5Bits(int $src): int
{
$ret = -1;
// if ($src > 96 && $src < 123) $ret += $src - 97 + 1; // -64
$ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 96);
// if ($src > 0x31 && $src < 0x38) $ret += $src - 24 + 1; // -23
$ret += (((0x31 - $src) & ($src - 0x38)) >> 8) & ($src - 23);
return $ret;
}
/**
* Uses bitwise operators instead of table-lookups to turn 5-bit integers
* into 8-bit integers.
*
* Uppercase variant.
*
* @param int $src
* @return int
*/
protected static function decode5BitsUpper(int $src): int
{
$ret = -1;
// if ($src > 64 && $src < 91) $ret += $src - 65 + 1; // -64
$ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64);
// if ($src > 0x31 && $src < 0x38) $ret += $src - 24 + 1; // -23
$ret += (((0x31 - $src) & ($src - 0x38)) >> 8) & ($src - 23);
return $ret;
}
/**
* Uses bitwise operators instead of table-lookups to turn 8-bit integers
* into 5-bit integers.
*
* @param int $src
* @return string
*/
protected static function encode5Bits(int $src): string
{
$diff = 0x61;
// if ($src > 25) $ret -= 72;
$diff -= ((25 - $src) >> 8) & 73;
return \pack('C', $src + $diff);
}
/**
* Uses bitwise operators instead of table-lookups to turn 8-bit integers
* into 5-bit integers.
*
* Uppercase variant.
*
* @param int $src
* @return string
*/
protected static function encode5BitsUpper(int $src): string
{
$diff = 0x41;
// if ($src > 25) $ret -= 40;
$diff -= ((25 - $src) >> 8) & 41;
return \pack('C', $src + $diff);
}
/**
* @param string $encodedString
* @param bool $upper
* @return string
*/
public static function decodeNoPadding(string $encodedString, bool $upper = false): string
{
$srcLen = Binary::safeStrlen($encodedString);
if ($srcLen === 0) {
return '';
}
if (($srcLen & 7) === 0) {
for ($j = 0; $j < 7 && $j < $srcLen; ++$j) {
if ($encodedString[$srcLen - $j - 1] === '=') {
throw new InvalidArgumentException(
"decodeNoPadding() doesn't tolerate padding"
);
}
}
}
return static::doDecode(
$encodedString,
$upper,
true
);
}
/**
* Base32 decoding
*
* @param string $src
* @param bool $upper
* @param bool $strictPadding
* @return string
*
* @throws TypeError
* @psalm-suppress RedundantCondition
*/
protected static function doDecode(
string $src,
bool $upper = false,
bool $strictPadding = false
): string {
// We do this to reduce code duplication:
$method = $upper
? 'decode5BitsUpper'
: 'decode5Bits';
// Remove padding
$srcLen = Binary::safeStrlen($src);
if ($srcLen === 0) {
return '';
}
if ($strictPadding) {
if (($srcLen & 7) === 0) {
for ($j = 0; $j < 7; ++$j) {
if ($src[$srcLen - 1] === '=') {
$srcLen--;
} else {
break;
}
}
}
if (($srcLen & 7) === 1) {
throw new RangeException(
'Incorrect padding'
);
}
} else {
$src = \rtrim($src, '=');
$srcLen = Binary::safeStrlen($src);
}
$err = 0;
$dest = '';
// Main loop (no padding):
for ($i = 0; $i + 8 <= $srcLen; $i += 8) {
/** @var array<int, int> $chunk */
$chunk = \unpack('C*', Binary::safeSubstr($src, $i, 8));
/** @var int $c0 */
$c0 = static::$method($chunk[1]);
/** @var int $c1 */
$c1 = static::$method($chunk[2]);
/** @var int $c2 */
$c2 = static::$method($chunk[3]);
/** @var int $c3 */
$c3 = static::$method($chunk[4]);
/** @var int $c4 */
$c4 = static::$method($chunk[5]);
/** @var int $c5 */
$c5 = static::$method($chunk[6]);
/** @var int $c6 */
$c6 = static::$method($chunk[7]);
/** @var int $c7 */
$c7 = static::$method($chunk[8]);
$dest .= \pack(
'CCCCC',
(($c0 << 3) | ($c1 >> 2) ) & 0xff,
(($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
(($c3 << 4) | ($c4 >> 1) ) & 0xff,
(($c4 << 7) | ($c5 << 2) | ($c6 >> 3)) & 0xff,
(($c6 << 5) | ($c7 ) ) & 0xff
);
$err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5 | $c6 | $c7) >> 8;
}
// The last chunk, which may have padding:
if ($i < $srcLen) {
/** @var array<int, int> $chunk */
$chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
/** @var int $c0 */
$c0 = static::$method($chunk[1]);
if ($i + 6 < $srcLen) {
/** @var int $c1 */
$c1 = static::$method($chunk[2]);
/** @var int $c2 */
$c2 = static::$method($chunk[3]);
/** @var int $c3 */
$c3 = static::$method($chunk[4]);
/** @var int $c4 */
$c4 = static::$method($chunk[5]);
/** @var int $c5 */
$c5 = static::$method($chunk[6]);
/** @var int $c6 */
$c6 = static::$method($chunk[7]);
$dest .= \pack(
'CCCC',
(($c0 << 3) | ($c1 >> 2) ) & 0xff,
(($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
(($c3 << 4) | ($c4 >> 1) ) & 0xff,
(($c4 << 7) | ($c5 << 2) | ($c6 >> 3)) & 0xff
);
$err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5 | $c6) >> 8;
if ($strictPadding) {
$err |= ($c6 << 5) & 0xff;
}
} elseif ($i + 5 < $srcLen) {
/** @var int $c1 */
$c1 = static::$method($chunk[2]);
/** @var int $c2 */
$c2 = static::$method($chunk[3]);
/** @var int $c3 */
$c3 = static::$method($chunk[4]);
/** @var int $c4 */
$c4 = static::$method($chunk[5]);
/** @var int $c5 */
$c5 = static::$method($chunk[6]);
$dest .= \pack(
'CCCC',
(($c0 << 3) | ($c1 >> 2) ) & 0xff,
(($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
(($c3 << 4) | ($c4 >> 1) ) & 0xff,
(($c4 << 7) | ($c5 << 2) ) & 0xff
);
$err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5) >> 8;
} elseif ($i + 4 < $srcLen) {
/** @var int $c1 */
$c1 = static::$method($chunk[2]);
/** @var int $c2 */
$c2 = static::$method($chunk[3]);
/** @var int $c3 */
$c3 = static::$method($chunk[4]);
/** @var int $c4 */
$c4 = static::$method($chunk[5]);
$dest .= \pack(
'CCC',
(($c0 << 3) | ($c1 >> 2) ) & 0xff,
(($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
(($c3 << 4) | ($c4 >> 1) ) & 0xff
);
$err |= ($c0 | $c1 | $c2 | $c3 | $c4) >> 8;
if ($strictPadding) {
$err |= ($c4 << 7) & 0xff;
}
} elseif ($i + 3 < $srcLen) {
/** @var int $c1 */
$c1 = static::$method($chunk[2]);
/** @var int $c2 */
$c2 = static::$method($chunk[3]);
/** @var int $c3 */
$c3 = static::$method($chunk[4]);
$dest .= \pack(
'CC',
(($c0 << 3) | ($c1 >> 2) ) & 0xff,
(($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff
);
$err |= ($c0 | $c1 | $c2 | $c3) >> 8;
if ($strictPadding) {
$err |= ($c3 << 4) & 0xff;
}
} elseif ($i + 2 < $srcLen) {
/** @var int $c1 */
$c1 = static::$method($chunk[2]);
/** @var int $c2 */
$c2 = static::$method($chunk[3]);
$dest .= \pack(
'CC',
(($c0 << 3) | ($c1 >> 2) ) & 0xff,
(($c1 << 6) | ($c2 << 1) ) & 0xff
);
$err |= ($c0 | $c1 | $c2) >> 8;
if ($strictPadding) {
$err |= ($c2 << 6) & 0xff;
}
} elseif ($i + 1 < $srcLen) {
/** @var int $c1 */
$c1 = static::$method($chunk[2]);
$dest .= \pack(
'C',
(($c0 << 3) | ($c1 >> 2) ) & 0xff
);
$err |= ($c0 | $c1) >> 8;
if ($strictPadding) {
$err |= ($c1 << 6) & 0xff;
}
} else {
$dest .= \pack(
'C',
(($c0 << 3) ) & 0xff
);
$err |= ($c0) >> 8;
}
}
$check = ($err === 0);
if (!$check) {
throw new RangeException(
'Base32::doDecode() only expects characters in the correct base32 alphabet'
);
}
return $dest;
}
/**
* Base32 Encoding
*
* @param string $src
* @param bool $upper
* @param bool $pad
* @return string
* @throws TypeError
*/
protected static function doEncode(string $src, bool $upper = false, $pad = true): string
{
// We do this to reduce code duplication:
$method = $upper
? 'encode5BitsUpper'
: 'encode5Bits';
$dest = '';
$srcLen = Binary::safeStrlen($src);
// Main loop (no padding):
for ($i = 0; $i + 5 <= $srcLen; $i += 5) {
/** @var array<int, int> $chunk */
$chunk = \unpack('C*', Binary::safeSubstr($src, $i, 5));
$b0 = $chunk[1];
$b1 = $chunk[2];
$b2 = $chunk[3];
$b3 = $chunk[4];
$b4 = $chunk[5];
$dest .=
static::$method( ($b0 >> 3) & 31) .
static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
static::$method((($b1 >> 1) ) & 31) .
static::$method((($b1 << 4) | ($b2 >> 4)) & 31) .
static::$method((($b2 << 1) | ($b3 >> 7)) & 31) .
static::$method((($b3 >> 2) ) & 31) .
static::$method((($b3 << 3) | ($b4 >> 5)) & 31) .
static::$method( $b4 & 31);
}
// The last chunk, which may have padding:
if ($i < $srcLen) {
/** @var array<int, int> $chunk */
$chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
$b0 = $chunk[1];
if ($i + 3 < $srcLen) {
$b1 = $chunk[2];
$b2 = $chunk[3];
$b3 = $chunk[4];
$dest .=
static::$method( ($b0 >> 3) & 31) .
static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
static::$method((($b1 >> 1) ) & 31) .
static::$method((($b1 << 4) | ($b2 >> 4)) & 31) .
static::$method((($b2 << 1) | ($b3 >> 7)) & 31) .
static::$method((($b3 >> 2) ) & 31) .
static::$method((($b3 << 3) ) & 31);
if ($pad) {
$dest .= '=';
}
} elseif ($i + 2 < $srcLen) {
$b1 = $chunk[2];
$b2 = $chunk[3];
$dest .=
static::$method( ($b0 >> 3) & 31) .
static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
static::$method((($b1 >> 1) ) & 31) .
static::$method((($b1 << 4) | ($b2 >> 4)) & 31) .
static::$method((($b2 << 1) ) & 31);
if ($pad) {
$dest .= '===';
}
} elseif ($i + 1 < $srcLen) {
$b1 = $chunk[2];
$dest .=
static::$method( ($b0 >> 3) & 31) .
static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
static::$method((($b1 >> 1) ) & 31) .
static::$method((($b1 << 4) ) & 31);
if ($pad) {
$dest .= '====';
}
} else {
$dest .=
static::$method( ($b0 >> 3) & 31) .
static::$method( ($b0 << 2) & 31);
if ($pad) {
$dest .= '======';
}
}
}
return $dest;
}
}

View File

@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class Base32Hex
* [0-9][A-V]
*
* @package ParagonIE\ConstantTime
*/
abstract class Base32Hex extends Base32
{
/**
* Uses bitwise operators instead of table-lookups to turn 5-bit integers
* into 8-bit integers.
*
* @param int $src
* @return int
*/
protected static function decode5Bits(int $src): int
{
$ret = -1;
// if ($src > 0x30 && $src < 0x3a) ret += $src - 0x2e + 1; // -47
$ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src - 47);
// if ($src > 0x60 && $src < 0x77) ret += $src - 0x61 + 10 + 1; // -86
$ret += (((0x60 - $src) & ($src - 0x77)) >> 8) & ($src - 86);
return $ret;
}
/**
* Uses bitwise operators instead of table-lookups to turn 5-bit integers
* into 8-bit integers.
*
* @param int $src
* @return int
*/
protected static function decode5BitsUpper(int $src): int
{
$ret = -1;
// if ($src > 0x30 && $src < 0x3a) ret += $src - 0x2e + 1; // -47
$ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src - 47);
// if ($src > 0x40 && $src < 0x57) ret += $src - 0x41 + 10 + 1; // -54
$ret += (((0x40 - $src) & ($src - 0x57)) >> 8) & ($src - 54);
return $ret;
}
/**
* Uses bitwise operators instead of table-lookups to turn 8-bit integers
* into 5-bit integers.
*
* @param int $src
* @return string
*/
protected static function encode5Bits(int $src): string
{
$src += 0x30;
// if ($src > 0x39) $src += 0x61 - 0x3a; // 39
$src += ((0x39 - $src) >> 8) & 39;
return \pack('C', $src);
}
/**
* Uses bitwise operators instead of table-lookups to turn 8-bit integers
* into 5-bit integers.
*
* Uppercase variant.
*
* @param int $src
* @return string
*/
protected static function encode5BitsUpper(int $src): string
{
$src += 0x30;
// if ($src > 0x39) $src += 0x41 - 0x3a; // 7
$src += ((0x39 - $src) >> 8) & 7;
return \pack('C', $src);
}
}

View File

@@ -0,0 +1,314 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
use InvalidArgumentException;
use RangeException;
use TypeError;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class Base64
* [A-Z][a-z][0-9]+/
*
* @package ParagonIE\ConstantTime
*/
abstract class Base64 implements EncoderInterface
{
/**
* Encode into Base64
*
* Base64 character set "[A-Z][a-z][0-9]+/"
*
* @param string $binString
* @return string
*
* @throws TypeError
*/
public static function encode(string $binString): string
{
return static::doEncode($binString, true);
}
/**
* Encode into Base64, no = padding
*
* Base64 character set "[A-Z][a-z][0-9]+/"
*
* @param string $src
* @return string
*
* @throws TypeError
*/
public static function encodeUnpadded(string $src): string
{
return static::doEncode($src, false);
}
/**
* @param string $src
* @param bool $pad Include = padding?
* @return string
*
* @throws TypeError
*/
protected static function doEncode(string $src, bool $pad = true): string
{
$dest = '';
$srcLen = Binary::safeStrlen($src);
// Main loop (no padding):
for ($i = 0; $i + 3 <= $srcLen; $i += 3) {
/** @var array<int, int> $chunk */
$chunk = \unpack('C*', Binary::safeSubstr($src, $i, 3));
$b0 = $chunk[1];
$b1 = $chunk[2];
$b2 = $chunk[3];
$dest .=
static::encode6Bits( $b0 >> 2 ) .
static::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) .
static::encode6Bits((($b1 << 2) | ($b2 >> 6)) & 63) .
static::encode6Bits( $b2 & 63);
}
// The last chunk, which may have padding:
if ($i < $srcLen) {
/** @var array<int, int> $chunk */
$chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
$b0 = $chunk[1];
if ($i + 1 < $srcLen) {
$b1 = $chunk[2];
$dest .=
static::encode6Bits($b0 >> 2) .
static::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) .
static::encode6Bits(($b1 << 2) & 63);
if ($pad) {
$dest .= '=';
}
} else {
$dest .=
static::encode6Bits( $b0 >> 2) .
static::encode6Bits(($b0 << 4) & 63);
if ($pad) {
$dest .= '==';
}
}
}
return $dest;
}
/**
* decode from base64 into binary
*
* Base64 character set "./[A-Z][a-z][0-9]"
*
* @param string $encodedString
* @param bool $strictPadding
* @return string
*
* @throws RangeException
* @throws TypeError
* @psalm-suppress RedundantCondition
*/
public static function decode(string $encodedString, bool $strictPadding = false): string
{
// Remove padding
$srcLen = Binary::safeStrlen($encodedString);
if ($srcLen === 0) {
return '';
}
if ($strictPadding) {
if (($srcLen & 3) === 0) {
if ($encodedString[$srcLen - 1] === '=') {
$srcLen--;
if ($encodedString[$srcLen - 1] === '=') {
$srcLen--;
}
}
}
if (($srcLen & 3) === 1) {
throw new RangeException(
'Incorrect padding'
);
}
if ($encodedString[$srcLen - 1] === '=') {
throw new RangeException(
'Incorrect padding'
);
}
} else {
$encodedString = \rtrim($encodedString, '=');
$srcLen = Binary::safeStrlen($encodedString);
}
$err = 0;
$dest = '';
// Main loop (no padding):
for ($i = 0; $i + 4 <= $srcLen; $i += 4) {
/** @var array<int, int> $chunk */
$chunk = \unpack('C*', Binary::safeSubstr($encodedString, $i, 4));
$c0 = static::decode6Bits($chunk[1]);
$c1 = static::decode6Bits($chunk[2]);
$c2 = static::decode6Bits($chunk[3]);
$c3 = static::decode6Bits($chunk[4]);
$dest .= \pack(
'CCC',
((($c0 << 2) | ($c1 >> 4)) & 0xff),
((($c1 << 4) | ($c2 >> 2)) & 0xff),
((($c2 << 6) | $c3 ) & 0xff)
);
$err |= ($c0 | $c1 | $c2 | $c3) >> 8;
}
// The last chunk, which may have padding:
if ($i < $srcLen) {
/** @var array<int, int> $chunk */
$chunk = \unpack('C*', Binary::safeSubstr($encodedString, $i, $srcLen - $i));
$c0 = static::decode6Bits($chunk[1]);
if ($i + 2 < $srcLen) {
$c1 = static::decode6Bits($chunk[2]);
$c2 = static::decode6Bits($chunk[3]);
$dest .= \pack(
'CC',
((($c0 << 2) | ($c1 >> 4)) & 0xff),
((($c1 << 4) | ($c2 >> 2)) & 0xff)
);
$err |= ($c0 | $c1 | $c2) >> 8;
if ($strictPadding) {
$err |= ($c2 << 6) & 0xff;
}
} elseif ($i + 1 < $srcLen) {
$c1 = static::decode6Bits($chunk[2]);
$dest .= \pack(
'C',
((($c0 << 2) | ($c1 >> 4)) & 0xff)
);
$err |= ($c0 | $c1) >> 8;
if ($strictPadding) {
$err |= ($c1 << 4) & 0xff;
}
} elseif ($strictPadding) {
$err |= 1;
}
}
$check = ($err === 0);
if (!$check) {
throw new RangeException(
'Base64::decode() only expects characters in the correct base64 alphabet'
);
}
return $dest;
}
/**
* @param string $encodedString
* @return string
*/
public static function decodeNoPadding(string $encodedString): string
{
$srcLen = Binary::safeStrlen($encodedString);
if ($srcLen === 0) {
return '';
}
if (($srcLen & 3) === 0) {
if ($encodedString[$srcLen - 1] === '=') {
throw new InvalidArgumentException(
"decodeNoPadding() doesn't tolerate padding"
);
}
if (($srcLen & 3) > 1) {
if ($encodedString[$srcLen - 2] === '=') {
throw new InvalidArgumentException(
"decodeNoPadding() doesn't tolerate padding"
);
}
}
}
return static::decode(
$encodedString,
true
);
}
/**
* Uses bitwise operators instead of table-lookups to turn 6-bit integers
* into 8-bit integers.
*
* Base64 character set:
* [A-Z] [a-z] [0-9] + /
* 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f
*
* @param int $src
* @return int
*/
protected static function decode6Bits(int $src): int
{
$ret = -1;
// if ($src > 0x40 && $src < 0x5b) $ret += $src - 0x41 + 1; // -64
$ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64);
// if ($src > 0x60 && $src < 0x7b) $ret += $src - 0x61 + 26 + 1; // -70
$ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 70);
// if ($src > 0x2f && $src < 0x3a) $ret += $src - 0x30 + 52 + 1; // 5
$ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 5);
// if ($src == 0x2b) $ret += 62 + 1;
$ret += (((0x2a - $src) & ($src - 0x2c)) >> 8) & 63;
// if ($src == 0x2f) ret += 63 + 1;
$ret += (((0x2e - $src) & ($src - 0x30)) >> 8) & 64;
return $ret;
}
/**
* Uses bitwise operators instead of table-lookups to turn 8-bit integers
* into 6-bit integers.
*
* @param int $src
* @return string
*/
protected static function encode6Bits(int $src): string
{
$diff = 0x41;
// if ($src > 25) $diff += 0x61 - 0x41 - 26; // 6
$diff += ((25 - $src) >> 8) & 6;
// if ($src > 51) $diff += 0x30 - 0x61 - 26; // -75
$diff -= ((51 - $src) >> 8) & 75;
// if ($src > 61) $diff += 0x2b - 0x30 - 10; // -15
$diff -= ((61 - $src) >> 8) & 15;
// if ($src > 62) $diff += 0x2f - 0x2b - 1; // 3
$diff += ((62 - $src) >> 8) & 3;
return \pack('C', $src + $diff);
}
}

View File

@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class Base64DotSlash
* ./[A-Z][a-z][0-9]
*
* @package ParagonIE\ConstantTime
*/
abstract class Base64DotSlash extends Base64
{
/**
* Uses bitwise operators instead of table-lookups to turn 6-bit integers
* into 8-bit integers.
*
* Base64 character set:
* ./ [A-Z] [a-z] [0-9]
* 0x2e-0x2f, 0x41-0x5a, 0x61-0x7a, 0x30-0x39
*
* @param int $src
* @return int
*/
protected static function decode6Bits(int $src): int
{
$ret = -1;
// if ($src > 0x2d && $src < 0x30) ret += $src - 0x2e + 1; // -45
$ret += (((0x2d - $src) & ($src - 0x30)) >> 8) & ($src - 45);
// if ($src > 0x40 && $src < 0x5b) ret += $src - 0x41 + 2 + 1; // -62
$ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 62);
// if ($src > 0x60 && $src < 0x7b) ret += $src - 0x61 + 28 + 1; // -68
$ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 68);
// if ($src > 0x2f && $src < 0x3a) ret += $src - 0x30 + 54 + 1; // 7
$ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 7);
return $ret;
}
/**
* Uses bitwise operators instead of table-lookups to turn 8-bit integers
* into 6-bit integers.
*
* @param int $src
* @return string
*/
protected static function encode6Bits(int $src): string
{
$src += 0x2e;
// if ($src > 0x2f) $src += 0x41 - 0x30; // 17
$src += ((0x2f - $src) >> 8) & 17;
// if ($src > 0x5a) $src += 0x61 - 0x5b; // 6
$src += ((0x5a - $src) >> 8) & 6;
// if ($src > 0x7a) $src += 0x30 - 0x7b; // -75
$src -= ((0x7a - $src) >> 8) & 75;
return \pack('C', $src);
}
}

View File

@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class Base64DotSlashOrdered
* ./[0-9][A-Z][a-z]
*
* @package ParagonIE\ConstantTime
*/
abstract class Base64DotSlashOrdered extends Base64
{
/**
* Uses bitwise operators instead of table-lookups to turn 6-bit integers
* into 8-bit integers.
*
* Base64 character set:
* [.-9] [A-Z] [a-z]
* 0x2e-0x39, 0x41-0x5a, 0x61-0x7a
*
* @param int $src
* @return int
*/
protected static function decode6Bits(int $src): int
{
$ret = -1;
// if ($src > 0x2d && $src < 0x3a) ret += $src - 0x2e + 1; // -45
$ret += (((0x2d - $src) & ($src - 0x3a)) >> 8) & ($src - 45);
// if ($src > 0x40 && $src < 0x5b) ret += $src - 0x41 + 12 + 1; // -52
$ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 52);
// if ($src > 0x60 && $src < 0x7b) ret += $src - 0x61 + 38 + 1; // -58
$ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 58);
return $ret;
}
/**
* Uses bitwise operators instead of table-lookups to turn 8-bit integers
* into 6-bit integers.
*
* @param int $src
* @return string
*/
protected static function encode6Bits(int $src): string
{
$src += 0x2e;
// if ($src > 0x39) $src += 0x41 - 0x3a; // 7
$src += ((0x39 - $src) >> 8) & 7;
// if ($src > 0x5a) $src += 0x61 - 0x5b; // 6
$src += ((0x5a - $src) >> 8) & 6;
return \pack('C', $src);
}
}

View File

@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class Base64UrlSafe
* [A-Z][a-z][0-9]\-_
*
* @package ParagonIE\ConstantTime
*/
abstract class Base64UrlSafe extends Base64
{
/**
* Uses bitwise operators instead of table-lookups to turn 6-bit integers
* into 8-bit integers.
*
* Base64 character set:
* [A-Z] [a-z] [0-9] - _
* 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2d, 0x5f
*
* @param int $src
* @return int
*/
protected static function decode6Bits(int $src): int
{
$ret = -1;
// if ($src > 0x40 && $src < 0x5b) $ret += $src - 0x41 + 1; // -64
$ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64);
// if ($src > 0x60 && $src < 0x7b) $ret += $src - 0x61 + 26 + 1; // -70
$ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 70);
// if ($src > 0x2f && $src < 0x3a) $ret += $src - 0x30 + 52 + 1; // 5
$ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 5);
// if ($src == 0x2c) $ret += 62 + 1;
$ret += (((0x2c - $src) & ($src - 0x2e)) >> 8) & 63;
// if ($src == 0x5f) ret += 63 + 1;
$ret += (((0x5e - $src) & ($src - 0x60)) >> 8) & 64;
return $ret;
}
/**
* Uses bitwise operators instead of table-lookups to turn 8-bit integers
* into 6-bit integers.
*
* @param int $src
* @return string
*/
protected static function encode6Bits(int $src): string
{
$diff = 0x41;
// if ($src > 25) $diff += 0x61 - 0x41 - 26; // 6
$diff += ((25 - $src) >> 8) & 6;
// if ($src > 51) $diff += 0x30 - 0x61 - 26; // -75
$diff -= ((51 - $src) >> 8) & 75;
// if ($src > 61) $diff += 0x2d - 0x30 - 10; // -13
$diff -= ((61 - $src) >> 8) & 13;
// if ($src > 62) $diff += 0x5f - 0x2b - 1; // 3
$diff += ((62 - $src) >> 8) & 49;
return \pack('C', $src + $diff);
}
}

View File

@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
use TypeError;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class Binary
*
* Binary string operators that don't choke on
* mbstring.func_overload
*
* @package ParagonIE\ConstantTime
*/
abstract class Binary
{
/**
* Safe string length
*
* @ref mbstring.func_overload
*
* @param string $str
* @return int
*/
public static function safeStrlen(string $str): int
{
if (\function_exists('mb_strlen')) {
// mb_strlen in PHP 7.x can return false.
/** @psalm-suppress RedundantCast */
return (int) \mb_strlen($str, '8bit');
} else {
return \strlen($str);
}
}
/**
* Safe substring
*
* @ref mbstring.func_overload
*
* @staticvar boolean $exists
* @param string $str
* @param int $start
* @param ?int $length
* @return string
*
* @throws TypeError
*/
public static function safeSubstr(
string $str,
int $start = 0,
$length = null
): string {
if ($length === 0) {
return '';
}
if (\function_exists('mb_substr')) {
return \mb_substr($str, $start, $length, '8bit');
}
// Unlike mb_substr(), substr() doesn't accept NULL for length
if ($length !== null) {
return \substr($str, $start, $length);
} else {
return \substr($str, $start);
}
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Interface EncoderInterface
* @package ParagonIE\ConstantTime
*/
interface EncoderInterface
{
/**
* Convert a binary string into a hexadecimal string without cache-timing
* leaks
*
* @param string $binString (raw binary)
* @return string
*/
public static function encode(string $binString): string;
/**
* Convert a binary string into a hexadecimal string without cache-timing
* leaks
*
* @param string $encodedString
* @param bool $strictPadding Error on invalid padding
* @return string (raw binary)
*/
public static function decode(string $encodedString, bool $strictPadding = false): string;
}

View File

@@ -0,0 +1,262 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
use TypeError;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class Encoding
* @package ParagonIE\ConstantTime
*/
abstract class Encoding
{
/**
* RFC 4648 Base32 encoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base32Encode(string $str): string
{
return Base32::encode($str);
}
/**
* RFC 4648 Base32 encoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base32EncodeUpper(string $str): string
{
return Base32::encodeUpper($str);
}
/**
* RFC 4648 Base32 decoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base32Decode(string $str): string
{
return Base32::decode($str);
}
/**
* RFC 4648 Base32 decoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base32DecodeUpper(string $str): string
{
return Base32::decodeUpper($str);
}
/**
* RFC 4648 Base32 encoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base32HexEncode(string $str): string
{
return Base32Hex::encode($str);
}
/**
* RFC 4648 Base32Hex encoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base32HexEncodeUpper(string $str): string
{
return Base32Hex::encodeUpper($str);
}
/**
* RFC 4648 Base32Hex decoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base32HexDecode(string $str): string
{
return Base32Hex::decode($str);
}
/**
* RFC 4648 Base32Hex decoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base32HexDecodeUpper(string $str): string
{
return Base32Hex::decodeUpper($str);
}
/**
* RFC 4648 Base64 encoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base64Encode(string $str): string
{
return Base64::encode($str);
}
/**
* RFC 4648 Base64 decoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base64Decode(string $str): string
{
return Base64::decode($str);
}
/**
* Encode into Base64
*
* Base64 character set "./[A-Z][a-z][0-9]"
* @param string $str
* @return string
* @throws TypeError
*/
public static function base64EncodeDotSlash(string $str): string
{
return Base64DotSlash::encode($str);
}
/**
* Decode from base64 to raw binary
*
* Base64 character set "./[A-Z][a-z][0-9]"
*
* @param string $str
* @return string
* @throws \RangeException
* @throws TypeError
*/
public static function base64DecodeDotSlash(string $str): string
{
return Base64DotSlash::decode($str);
}
/**
* Encode into Base64
*
* Base64 character set "[.-9][A-Z][a-z]" or "./[0-9][A-Z][a-z]"
* @param string $str
* @return string
* @throws TypeError
*/
public static function base64EncodeDotSlashOrdered(string $str): string
{
return Base64DotSlashOrdered::encode($str);
}
/**
* Decode from base64 to raw binary
*
* Base64 character set "[.-9][A-Z][a-z]" or "./[0-9][A-Z][a-z]"
*
* @param string $str
* @return string
* @throws \RangeException
* @throws TypeError
*/
public static function base64DecodeDotSlashOrdered(string $str): string
{
return Base64DotSlashOrdered::decode($str);
}
/**
* Convert a binary string into a hexadecimal string without cache-timing
* leaks
*
* @param string $bin_string (raw binary)
* @return string
* @throws TypeError
*/
public static function hexEncode(string $bin_string): string
{
return Hex::encode($bin_string);
}
/**
* Convert a hexadecimal string into a binary string without cache-timing
* leaks
*
* @param string $hex_string
* @return string (raw binary)
* @throws \RangeException
*/
public static function hexDecode(string $hex_string): string
{
return Hex::decode($hex_string);
}
/**
* Convert a binary string into a hexadecimal string without cache-timing
* leaks
*
* @param string $bin_string (raw binary)
* @return string
* @throws TypeError
*/
public static function hexEncodeUpper(string $bin_string): string
{
return Hex::encodeUpper($bin_string);
}
/**
* Convert a binary string into a hexadecimal string without cache-timing
* leaks
*
* @param string $bin_string (raw binary)
* @return string
*/
public static function hexDecodeUpper(string $bin_string): string
{
return Hex::decode($bin_string);
}
}

View File

@@ -0,0 +1,146 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
use RangeException;
use TypeError;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class Hex
* @package ParagonIE\ConstantTime
*/
abstract class Hex implements EncoderInterface
{
/**
* Convert a binary string into a hexadecimal string without cache-timing
* leaks
*
* @param string $binString (raw binary)
* @return string
* @throws TypeError
*/
public static function encode(string $binString): string
{
$hex = '';
$len = Binary::safeStrlen($binString);
for ($i = 0; $i < $len; ++$i) {
/** @var array<int, int> $chunk */
$chunk = \unpack('C', $binString[$i]);
$c = $chunk[1] & 0xf;
$b = $chunk[1] >> 4;
$hex .= \pack(
'CC',
(87 + $b + ((($b - 10) >> 8) & ~38)),
(87 + $c + ((($c - 10) >> 8) & ~38))
);
}
return $hex;
}
/**
* Convert a binary string into a hexadecimal string without cache-timing
* leaks, returning uppercase letters (as per RFC 4648)
*
* @param string $binString (raw binary)
* @return string
* @throws TypeError
*/
public static function encodeUpper(string $binString): string
{
$hex = '';
$len = Binary::safeStrlen($binString);
for ($i = 0; $i < $len; ++$i) {
/** @var array<int, int> $chunk */
$chunk = \unpack('C', $binString[$i]);
$c = $chunk[1] & 0xf;
$b = $chunk[1] >> 4;
$hex .= \pack(
'CC',
(55 + $b + ((($b - 10) >> 8) & ~6)),
(55 + $c + ((($c - 10) >> 8) & ~6))
);
}
return $hex;
}
/**
* Convert a hexadecimal string into a binary string without cache-timing
* leaks
*
* @param string $encodedString
* @param bool $strictPadding
* @return string (raw binary)
* @throws RangeException
*/
public static function decode(
string $encodedString,
bool $strictPadding = false
): string {
$hex_pos = 0;
$bin = '';
$c_acc = 0;
$hex_len = Binary::safeStrlen($encodedString);
$state = 0;
if (($hex_len & 1) !== 0) {
if ($strictPadding) {
throw new RangeException(
'Expected an even number of hexadecimal characters'
);
} else {
$encodedString = '0' . $encodedString;
++$hex_len;
}
}
/** @var array<int, int> $chunk */
$chunk = \unpack('C*', $encodedString);
while ($hex_pos < $hex_len) {
++$hex_pos;
$c = $chunk[$hex_pos];
$c_num = $c ^ 48;
$c_num0 = ($c_num - 10) >> 8;
$c_alpha = ($c & ~32) - 55;
$c_alpha0 = (($c_alpha - 10) ^ ($c_alpha - 16)) >> 8;
if (($c_num0 | $c_alpha0) === 0) {
throw new RangeException(
'Expected hexadecimal character'
);
}
$c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0);
if ($state === 0) {
$c_acc = $c_val * 16;
} else {
$bin .= \pack('C', $c_acc | $c_val);
}
$state ^= 1;
}
return $bin;
}
}

View File

@@ -0,0 +1,186 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
use TypeError;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class RFC4648
*
* This class conforms strictly to the RFC
*
* @package ParagonIE\ConstantTime
*/
abstract class RFC4648
{
/**
* RFC 4648 Base64 encoding
*
* "foo" -> "Zm9v"
*
* @param string $str
* @return string
*
* @throws TypeError
*/
public static function base64Encode(string $str): string
{
return Base64::encode($str);
}
/**
* RFC 4648 Base64 decoding
*
* "Zm9v" -> "foo"
*
* @param string $str
* @return string
*
* @throws TypeError
*/
public static function base64Decode(string $str): string
{
return Base64::decode($str, true);
}
/**
* RFC 4648 Base64 (URL Safe) encoding
*
* "foo" -> "Zm9v"
*
* @param string $str
* @return string
*
* @throws TypeError
*/
public static function base64UrlSafeEncode(string $str): string
{
return Base64UrlSafe::encode($str);
}
/**
* RFC 4648 Base64 (URL Safe) decoding
*
* "Zm9v" -> "foo"
*
* @param string $str
* @return string
*
* @throws TypeError
*/
public static function base64UrlSafeDecode(string $str): string
{
return Base64UrlSafe::decode($str, true);
}
/**
* RFC 4648 Base32 encoding
*
* "foo" -> "MZXW6==="
*
* @param string $str
* @return string
*
* @throws TypeError
*/
public static function base32Encode(string $str): string
{
return Base32::encodeUpper($str);
}
/**
* RFC 4648 Base32 encoding
*
* "MZXW6===" -> "foo"
*
* @param string $str
* @return string
*
* @throws TypeError
*/
public static function base32Decode(string $str): string
{
return Base32::decodeUpper($str, true);
}
/**
* RFC 4648 Base32-Hex encoding
*
* "foo" -> "CPNMU==="
*
* @param string $str
* @return string
*
* @throws TypeError
*/
public static function base32HexEncode(string $str): string
{
return Base32::encodeUpper($str);
}
/**
* RFC 4648 Base32-Hex decoding
*
* "CPNMU===" -> "foo"
*
* @param string $str
* @return string
*
* @throws TypeError
*/
public static function base32HexDecode(string $str): string
{
return Base32::decodeUpper($str, true);
}
/**
* RFC 4648 Base16 decoding
*
* "foo" -> "666F6F"
*
* @param string $str
* @return string
*
* @throws TypeError
*/
public static function base16Encode(string $str): string
{
return Hex::encodeUpper($str);
}
/**
* RFC 4648 Base16 decoding
*
* "666F6F" -> "foo"
*
* @param string $str
* @return string
*/
public static function base16Decode(string $str): string
{
return Hex::decode($str, true);
}
}

20
vendor/spomky-labs/otphp/LICENSE vendored Normal file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014-2016 Florent Morselli
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

42
vendor/spomky-labs/otphp/README.md vendored Normal file
View File

@@ -0,0 +1,42 @@
TOTP / HOTP library in PHP
==========================
![Build Status](https://github.com/spomky-labs/otphp/workflows/Integrate/badge.svg)
[![Latest Stable Version](https://poser.pugx.org/spomky-labs/otphp/v/stable.png)](https://packagist.org/packages/spomky-labs/otphp)
[![Total Downloads](https://poser.pugx.org/spomky-labs/otphp/downloads.png)](https://packagist.org/packages/spomky-labs/otphp)
[![Latest Unstable Version](https://poser.pugx.org/spomky-labs/otphp/v/unstable.png)](https://packagist.org/packages/spomky-labs/otphp)
[![License](https://poser.pugx.org/spomky-labs/otphp/license.png)](https://packagist.org/packages/spomky-labs/otphp)
A php library for generating one-time passwords according to [RFC 4226](https://datatracker.ietf.org/doc/html/rfc4226) (HOTP Algorithm) and [RFC 6238](https://datatracker.ietf.org/doc/html/rfc6238) (TOTP Algorithm)
This library is compatible with Google Authenticator apps available for Android and iPhone.
It is also compatible with other applications such as [FreeOTP](https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp) for example.
# Documentation
The documentation of this project is available in the [*doc* folder](doc/index.md).
# Support
I bring solutions to your problems and answer your questions.
If you really love that project, and the work I have done or if you want I prioritize your issues, then you can help me out for a couple of :beers: or more!
[Become a sponsor](https://github.com/sponsors/Spomky)
Or
[![Become a Patreon](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/FlorentMorselli)
## Contributing
Requests for new features, bug fixed and all other ideas to make this project useful are welcome.
Please report all issues in [the repository bug tracker](hhttps://github.com/Spomky-Labs/otphp/issues).
Also make sure to [follow these best practices](.github/CONTRIBUTING.md).
## Licence
This software is release under the [MIT licence](LICENSE).

87
vendor/spomky-labs/otphp/SECURITY.md vendored Normal file
View File

@@ -0,0 +1,87 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- |----------------------------------------|
| 11.0.x | :white_check_mark: |
| 10.0.x | :white_check_mark: (security fix only) |
| < 10.0 | :x: |
## Reporting a Vulnerability
Please email `security@spomky-labs.com`.
If deemed necessary, you can encrypt your message using one of the following GPG key
```
-----BEGIN PGP PUBLIC KEY BLOCK-----
xjMEXTsJVxYJKwYBBAHaRw8BAQdAZCS93eHRx97V+LQbAWuAaeKIdUZ9YIkn
QH5pQ7dDU0TNMWNvbnRhY3RAc3BvbWt5LWxhYnMuY29tIDxjb250YWN0QHNw
b21reS1sYWJzLmNvbT7CdwQQFgoAHwUCXTsJVwYLCQcIAwIEFQgKAgMWAgEC
GQECGwMCHgEACgkQG6hbCDSDj+1/tgEAoy11uHvDV7kkG/iN2/0ylV72hU8y
c/xoqGd7qFaKD6ABANcthlg63OrQVTf0dUPOT9Y2BJpOOA88JJWgILtuUPIO
zjgEXTsJVxIKKwYBBAGXVQEFAQEHQKiX7nldkmICePhzwReZnBPmjpsmNt7V
Y8xHdICKsr8cAwEIB8JhBBgWCAAJBQJdOwlXAhsMAAoJEBuoWwg0g4/t0KgA
/31ucb/bL/MGpWFrpSjTs6uQhZWlBmcFoeMhwCYepIpZAQDd65UBqFDKXJWv
Xy3zoMQQzD9Z6fUATnFrWkzjHwhvDQ==
=j4dw
-----END PGP PUBLIC KEY BLOCK-----
```
```
-----BEGIN PGP PUBLIC KEY BLOCK-----
xsFNBGILZFoBEADo9pzAMRVxL5typ22Ywifdyi3CMHgg7zptfb8otrQci8IX
m7B8/NTA0I9EkenzSW/Mf4k2iPNCwXc+qVEHPvPNvr3WazcdiDQJjXqMtkxG
l2dvdQHdBxN46v+mvWDVGf9anYQxIAmZrj7CDLOfD/cG/8STL4hSbFjRBOKs
xAP8wgRA/amcrf9WcCDxURGIq8mDPcECR8fca+iukTmMe2NDEc56pJi0KVoF
pFhOMMfjgP/XvtGjjSNZNGRgHSLTQs8UiK+5BjPh+iWFIPV5+ZPLpbSOcoma
GyeX5i1DmAh7cWx/FphvFzOun6to3ERuy82+zW54iA9zS8+kIfV4Wjr2qE7l
Ctc9l8RIv/6dMXoW2Y42CTuywlAMnlP7XaaUgE++CXTIuO7+6Gp0E5NlmqB5
lb+CZLV/LS27gUcajs23ve5B3UId2bGUflvTtY/J0VPzrJMoEErVnkCsnD7W
Oiwe8GiSNMJmTGu/A45xf5nuYNcuU7blA5XXwPoHZuALj1zv6eCWVxWz02l9
Fc/T+gNkOEErlXOcldyXxQ5Qb99TU5NgdqzbibyR9QAqdfwtgg19oFbiSP7t
8b5P2qAIW2GaOCkX007cBCzTXNrcQNruTwUD59LZQLhdGz5WJo/gefC/3ZvR
vKoJKCRlk7s43aUjeZzE+Engpr5e1wl63WjAzQARAQABzTNzZWN1cml0eUBz
cG9ta3ktbGFicy5jb20gPHNlY3VyaXR5QHNwb21reS1sYWJzLmNvbT7CwY0E
EAEIACAFAmILZFoGCwkHCAMCBBUICgIEFgIBAAIZAQIbAwIeAQAhCRBy14gx
FHv4aBYhBKgF8zJv89FYVv0RFHLXiDEUe/hoA+YP/ijaePtilKURzNVrPWfc
gDw/ZNCR+dVAgwGo9VcbOvkyZmyqD6yBjuDWvG96KQs0LRrqWKonAvnewNtp
wQruuvrlcCuNE6TTfvx0wh2+lwKD7MH5dKutHUCowVNAsZ5uZxHVF9RGLBh+
JRofklupcGqUx+Jtx4uq2gAGOqV4/QdvneMjkLwqVu8FGIM59LfdNfp/iA3p
wX2DvfxBO58Gu6hilmf7R+b9nX0U7xYJM6QJb7H89cV3/AoTh2kf1wtFY+Py
Di6VZTMUBYOoz2iSnvCE8KlBWDu98/A2EJ7kDGQdmnuIgsURsyap3yKioaUr
LGTaG0OiC/gkXkKisH6eff6Gw06qelBarf5N/GgoeAN/amE8twy3a+Hx1pyw
ZzkjPsL7uWg3Koy5mPuCtWfPtIBcJaTLS5d8ESlJ8/CfaVaDludzYQZo70Xn
m4KzjPnptm3djpZNwoFEUxrHVREOEe69/MnEL2PNcEMQkapg16PnH4phajnC
7bYOPDteMJlHjNmQzz9d25ZwzVBHDDT50mHDijR2D/OgKx3NQr88fiFAWhKG
lEu1ZuOkKIKV5VIFbocTWSoV7bkzIfrll49xWou+4VOxgRuqjquFC4RV8fea
lLbHOcJlOR00aFDmoOWQ3/QNvajaWJFzDdocGbgbnEBMDFRoUkuhqOBcnzA+
apW/zsFNBGILZFoBEADSwiM49wObRpxOyas91M6WvJ4Gt3iXqj+L8dmcw0FW
UdDpwOxy8tuZx+OfXEBBH3eJHOobC66vN+E9WYobVkJ5zfbGxfQruTuvUZNl
X9Lo0UwoP+AP21AKUUvsf48iZGWzmlkxgPnhAQS4ECkkWCKPf7nFTk+V+jIN
nf6ZDZLXaRUnG0nLvzs0raG1eTVrGvPSCC8u3R2zIh9SvoeEgTnT/Re0mhCu
ah3fwG+4vXc6VIjR1ZtpM9+Y8sl+PFZ/Oiisc+46oU5qXVVLtHfLdxYZ4vl2
IflHDKKmrfbfGY1hJl/foBLglT3Cd8GTu3FjiAJX9PpkiWbsflc0OUBQf9aC
73W5FLS4P4clm4nNzVGkNucWHvk+urM6nEUf02bhsfF0TPeos3QcJorfKNUS
TvuGYccENuK5cVOzEcU+VhN08GT0pr0CpqJnsw+zV8vD4k3aPmMFmSVog+bY
NhfB7AgwbOjd6MhQJcP7YjYTHaa6YsnKMSg4RhkDjvMa3421hfaWsVvlIb0f
AZJ8BnXgfE0uI8CKA9dc6I2Posl33zC8HI2sS1MEJ90Am68P+uJt61LdJeD5
VXSrCkzBhUBds0hbGR6+DF20UD496m7Lw3VBoWOl2bMeLdERDarFMDYsPH47
rie9wlrnPNR57HUqK4bpkFwqTStRkRFUhFv7LLWZ1QARAQABwsF2BBgBCAAJ
BQJiC2RaAhsMACEJEHLXiDEUe/hoFiEEqAXzMm/z0VhW/REUcteIMRR7+GhI
lQ/9GbSwIdGue6Gw0msYAEoER9HhpYB//9/GG7/c4ZW60nLSSYuhNWIo0Akl
10CzeApezf/O9/1EExqZ9ygj4wtUphcQOdRJVhXPt+gskw7/NHoXUJ+Z1rbb
EWbKle9YufZ4PAKYhlxdqTlWyQvPVxrRvbuhYeQG4S412VzKjH0/x1Fh2CfV
hFuyOaRjg89T6rihXL1rCSJ/PDQeQtvtXeJ30yFj+aapCj+VqUl+2D+N0bzS
LL18kEPQnJw4BOHOXrw349dAKmHN/QkRH8DINlXLyaOlABglnSViDQL3Q1t3
sBuIeClsl3brQNJRp/RKOdTBMNAX+BhAjqodbwwT+UkJl9xJKw0Cla4wtbs2
T0yoK/Z1iFfvPdufkK4q6ocAHJUp3+XckFIZxsHQvhQPbm9XoOt1RTO29MOw
EYo8UjFQCnXJVsj1/6XMgIUe5tPYvS/ZZZNJFF4j+OE8xRKLKqg/DFcpEipC
LCmzzr/hhWx0XP4CIK2tYsAMk3ieCZuk1Wa+NGLL4WfALWsNHq3wg5Wzv+yJ
dp14fv711BVYlriI+VKggGFgBdz0dWkgrBk4+thLatJFcjFYr8BLkbtPraa3
sFI/cGxvOXSIy4GEALdfnozyU3RJtMNtVi3IzGeIFAOb457y/IrMqpWLp1FX
BUqlX5YJHneD9Q8Sfz/HKDQDCqg=
=o+4z
-----END PGP PUBLIC KEY BLOCK-----
```

57
vendor/spomky-labs/otphp/composer.json vendored Normal file
View File

@@ -0,0 +1,57 @@
{
"name": "spomky-labs/otphp",
"type": "library",
"description": "A PHP library for generating one time passwords according to RFC 4226 (HOTP Algorithm) and the RFC 6238 (TOTP Algorithm) and compatible with Google Authenticator",
"license": "MIT",
"keywords": ["otp", "hotp", "totp", "RFC 4226", "RFC 6238", "Google Authenticator", "FreeOTP"],
"homepage": "https://github.com/Spomky-Labs/otphp",
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky"
},
{
"name": "All contributors",
"homepage": "https://github.com/Spomky-Labs/otphp/contributors"
}
],
"require": {
"php": "^8.1",
"ext-mbstring": "*",
"paragonie/constant_time_encoding": "^2.0"
},
"require-dev": {
"ekino/phpstan-banned-code": "^1.0",
"infection/infection": "^0.26",
"php-parallel-lint/php-parallel-lint": "^1.3",
"phpstan/phpstan": "^1.0",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": "^9.5.26",
"qossmic/deptrac-shim": "^1.0",
"rector/rector": "^0.14",
"symfony/phpunit-bridge": "^6.1",
"symplify/easy-coding-standard": "^11.0"
},
"autoload": {
"psr-4": { "OTPHP\\": "src/" }
},
"autoload-dev": {
"psr-4": { "OTPHP\\Test\\": "tests/" }
},
"config": {
"allow-plugins": {
"phpstan/extension-installer": true,
"infection/extension-installer": true,
"composer/package-versions-deprecated": true,
"symfony/flex": true,
"symfony/runtime": true
},
"optimize-autoloader": true,
"preferred-install": {
"*": "dist"
},
"sort-packages": true
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace OTPHP;
use function count;
use InvalidArgumentException;
use Throwable;
/**
* This class is used to load OTP object from a provisioning Uri.
*
* @see \OTPHP\Test\FactoryTest
*/
final class Factory implements FactoryInterface
{
public static function loadFromProvisioningUri(string $uri): OTPInterface
{
try {
$parsed_url = Url::fromString($uri);
$parsed_url->getScheme() === 'otpauth' || throw new InvalidArgumentException('Invalid scheme.');
} catch (Throwable $throwable) {
throw new InvalidArgumentException('Not a valid OTP provisioning URI', $throwable->getCode(), $throwable);
}
$otp = self::createOTP($parsed_url);
self::populateOTP($otp, $parsed_url);
return $otp;
}
private static function populateParameters(OTPInterface $otp, Url $data): void
{
foreach ($data->getQuery() as $key => $value) {
$otp->setParameter($key, $value);
}
}
private static function populateOTP(OTPInterface $otp, Url $data): void
{
self::populateParameters($otp, $data);
$result = explode(':', rawurldecode(mb_substr($data->getPath(), 1)));
if (count($result) < 2) {
$otp->setIssuerIncludedAsParameter(false);
return;
}
if ($otp->getIssuer() !== null) {
$result[0] === $otp->getIssuer() || throw new InvalidArgumentException(
'Invalid OTP: invalid issuer in parameter'
);
$otp->setIssuerIncludedAsParameter(true);
}
$otp->setIssuer($result[0]);
}
private static function createOTP(Url $parsed_url): OTPInterface
{
switch ($parsed_url->getHost()) {
case 'totp':
$totp = TOTP::createFromSecret($parsed_url->getSecret());
$totp->setLabel(self::getLabel($parsed_url->getPath()));
return $totp;
case 'hotp':
$hotp = HOTP::createFromSecret($parsed_url->getSecret());
$hotp->setLabel(self::getLabel($parsed_url->getPath()));
return $hotp;
default:
throw new InvalidArgumentException(sprintf('Unsupported "%s" OTP type', $parsed_url->getHost()));
}
}
private static function getLabel(string $data): string
{
$result = explode(':', rawurldecode(mb_substr($data, 1)));
return count($result) === 2 ? $result[1] : $result[0];
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace OTPHP;
interface FactoryInterface
{
/**
* This method is the unique public method of the class. It can load a provisioning Uri and convert it into an OTP
* object.
*/
public static function loadFromProvisioningUri(string $uri): OTPInterface;
}

124
vendor/spomky-labs/otphp/src/HOTP.php vendored Normal file
View File

@@ -0,0 +1,124 @@
<?php
declare(strict_types=1);
namespace OTPHP;
use InvalidArgumentException;
use function is_int;
/**
* @see \OTPHP\Test\HOTPTest
*/
final class HOTP extends OTP implements HOTPInterface
{
private const DEFAULT_WINDOW = 0;
public static function create(
null|string $secret = null,
int $counter = self::DEFAULT_COUNTER,
string $digest = self::DEFAULT_DIGEST,
int $digits = self::DEFAULT_DIGITS
): self {
$htop = $secret !== null
? self::createFromSecret($secret)
: self::generate()
;
$htop->setCounter($counter);
$htop->setDigest($digest);
$htop->setDigits($digits);
return $htop;
}
public static function createFromSecret(string $secret): self
{
$htop = new self($secret);
$htop->setCounter(self::DEFAULT_COUNTER);
$htop->setDigest(self::DEFAULT_DIGEST);
$htop->setDigits(self::DEFAULT_DIGITS);
return $htop;
}
public static function generate(): self
{
return self::createFromSecret(self::generateSecret());
}
public function getCounter(): int
{
$value = $this->getParameter('counter');
is_int($value) || throw new InvalidArgumentException('Invalid "counter" parameter.');
return $value;
}
public function getProvisioningUri(): string
{
return $this->generateURI('hotp', [
'counter' => $this->getCounter(),
]);
}
/**
* If the counter is not provided, the OTP is verified at the actual counter.
*/
public function verify(string $otp, null|int $counter = null, null|int $window = null): bool
{
$counter >= 0 || throw new InvalidArgumentException('The counter must be at least 0.');
if ($counter === null) {
$counter = $this->getCounter();
} elseif ($counter < $this->getCounter()) {
return false;
}
return $this->verifyOtpWithWindow($otp, $counter, $window);
}
public function setCounter(int $counter): void
{
$this->setParameter('counter', $counter);
}
/**
* @return array<string, callable>
*/
protected function getParameterMap(): array
{
return [...parent::getParameterMap(), ...[
'counter' => static function (mixed $value): int {
$value = (int) $value;
$value >= 0 || throw new InvalidArgumentException('Counter must be at least 0.');
return $value;
},
]];
}
private function updateCounter(int $counter): void
{
$this->setCounter($counter);
}
private function getWindow(null|int $window): int
{
return abs($window ?? self::DEFAULT_WINDOW);
}
private function verifyOtpWithWindow(string $otp, int $counter, null|int $window): bool
{
$window = $this->getWindow($window);
for ($i = $counter; $i <= $counter + $window; ++$i) {
if ($this->compareOTP($this->at($i), $otp)) {
$this->updateCounter($i + 1);
return true;
}
}
return false;
}
}

Some files were not shown because too many files have changed in this diff Show More