mirror of
https://github.com/KuJoe/kontrolvm.git
synced 2025-04-20 20:13:39 +00:00
Compare commits
83 commits
v0.1-alpha
...
main
Author | SHA1 | Date | |
---|---|---|---|
![]() |
013372dbb8 | ||
![]() |
1af69f2a60 | ||
![]() |
25d5fffd5f | ||
![]() |
adcdbdc242 | ||
![]() |
162e252134 | ||
![]() |
5b0fedeaa0 | ||
![]() |
26226f0741 | ||
![]() |
f1e88d8a51 | ||
![]() |
077e1d6aff | ||
![]() |
c64f203d42 | ||
![]() |
f6ce5252f2 | ||
![]() |
5963ecb5f4 | ||
![]() |
42fd519d86 | ||
![]() |
eed93200ef | ||
![]() |
e529ad6f48 | ||
![]() |
e78f3d4cc0 | ||
![]() |
248ea0972f | ||
![]() |
af35c6f5d4 | ||
![]() |
7dcd2b80b4 | ||
![]() |
b26535a115 | ||
![]() |
fd38eef7d6 | ||
![]() |
72961db019 | ||
![]() |
a2b1db9d00 | ||
![]() |
91acba65a9 | ||
![]() |
0b79e82f7d | ||
![]() |
d3090c7307 | ||
![]() |
590dfd5651 | ||
![]() |
9c7dd70c6d | ||
![]() |
262830f7a6 | ||
![]() |
4204d7a6d1 | ||
![]() |
ef7e779266 | ||
![]() |
0f0705f555 | ||
![]() |
f42c6797e8 | ||
![]() |
ef5e9e0836 | ||
![]() |
7699899409 | ||
![]() |
d18397942e | ||
![]() |
a060a70470 | ||
![]() |
7a26ab29a9 | ||
![]() |
f9ac906c6f | ||
![]() |
dbf26a255d | ||
![]() |
bcd201030b | ||
![]() |
81d9e9770e | ||
![]() |
d5885ef825 | ||
![]() |
a077f03422 | ||
![]() |
8ae741061b | ||
![]() |
2d8438bf40 | ||
![]() |
0c507312fe | ||
![]() |
a78a809ca3 | ||
![]() |
fa863312d0 | ||
![]() |
13d30d4e17 | ||
![]() |
6e0f260c3e | ||
![]() |
80cfba9996 | ||
![]() |
dccf804aab | ||
![]() |
992e8570aa | ||
![]() |
2317a7e3c4 | ||
![]() |
b1b1fb3122 | ||
![]() |
fcdcac0746 | ||
![]() |
6dee7764fe | ||
![]() |
87d0346989 | ||
![]() |
0f3ab7f70e | ||
![]() |
ddb4b654ba | ||
![]() |
cfaf13107d | ||
![]() |
595885e51a | ||
![]() |
3a0f3af884 | ||
![]() |
66cbe9796c | ||
![]() |
9a032d03a1 | ||
![]() |
d8dc55b17c | ||
![]() |
78468dec82 | ||
![]() |
89d6864bf7 | ||
![]() |
d291e46110 | ||
![]() |
9d769d66cd | ||
![]() |
1dce761e56 | ||
![]() |
38c2aa7827 | ||
![]() |
c4a3025d85 | ||
![]() |
1cafeb8157 | ||
![]() |
59b4a2457a | ||
![]() |
ebe46383e9 | ||
![]() |
266412500e | ||
![]() |
977cca6031 | ||
![]() |
5d34869052 | ||
![]() |
080d61fc58 | ||
![]() |
6b89292c1d | ||
![]() |
6710a72d88 |
222 changed files with 25904 additions and 1733 deletions
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Client (please complete the following information):**
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
- Mobile/Desktop/Tablet
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[REQUEST]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
10
.github/ISSUE_TEMPLATE/help.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/help.md
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
name: Help
|
||||
about: Need help?
|
||||
title: "[HELP]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please provide as much information as possible with what you need assistance with.
|
9
.github/dependabot.yml
vendored
Normal file
9
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "composer"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 10
|
||||
allow:
|
||||
- dependency-type: "all"
|
57
.github/workflows/phpmd.yml
vendored
Normal file
57
.github/workflows/phpmd.yml
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
# PHPMD is a spin-off project of PHP Depend and
|
||||
# aims to be a PHP equivalent of the well known Java tool PMD.
|
||||
# What PHPMD does is: It takes a given PHP source code base
|
||||
# and look for several potential problems within that source.
|
||||
# These problems can be things like:
|
||||
# Possible bugs
|
||||
# Suboptimal code
|
||||
# Overcomplicated expressions
|
||||
# Unused parameters, methods, properties
|
||||
# More details at https://phpmd.org/
|
||||
|
||||
name: PHPMD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "main" ]
|
||||
schedule:
|
||||
- cron: '33 19 * * 0'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
PHPMD:
|
||||
name: Run PHPMD scanning
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read # for checkout to fetch code
|
||||
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
|
||||
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@aa1fe473f9c687b6fb896056d771232c0bc41161
|
||||
with:
|
||||
coverage: none
|
||||
tools: phpmd
|
||||
|
||||
- name: Run PHPMD
|
||||
run: phpmd . sarif codesize --exclude vendor/phpseclib/*,vendor/paragonie/*,vendor/composer/*,vendor/bacon/*,vendor/dasprid/*,vendor/pragmarx/*,vendor/phpmailer/* --reportfile phpmd-results.sarif
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload analysis results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: phpmd-results.sarif
|
||||
wait-for-processing: true
|
38
.github/workflows/psalm.yml
vendored
Normal file
38
.github/workflows/psalm.yml
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
name: Psalm Security Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "main" ]
|
||||
schedule:
|
||||
- cron: '30 3 * * 4'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
php-security:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read # for actions/checkout to fetch code
|
||||
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
|
||||
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Psalm Security Scan
|
||||
uses: psalm/psalm-github-security-scan@f3e6fd9432bc3e44aec078572677ce9d2ef9c287
|
||||
|
||||
- name: Upload Security Analysis results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: results.sarif
|
28
README.md
28
README.md
|
@ -27,9 +27,9 @@ All of the current assets and UI are placeholders, I'll find somebody to design
|
|||
- ✅Delete user accounts
|
||||
- ✅Update user details (username, email, status, password)
|
||||
- ✅Cloudflare Turnstile invisible CAPTCHA
|
||||
- 🚧Multifactor Authentication
|
||||
- 🚧User permissions/levels
|
||||
- 🚧Password reset
|
||||
- ✅Multifactor Authentication
|
||||
- ✅User permissions/levels
|
||||
- ✅Password reset
|
||||
- 🚧Notifications
|
||||
|
||||
### Node Management
|
||||
|
@ -40,7 +40,7 @@ All of the current assets and UI are placeholders, I'll find somebody to design
|
|||
- ✅Get node statistics (CPU, RAM, disk usage, etc.)
|
||||
- ✅Update node statistics
|
||||
- ✅Enable/disable nodes
|
||||
- 🚧Import existing Linux KVM nodes
|
||||
- ✅Import existing Linux KVM nodes
|
||||
- 🚧Setup multiple virtual networks (i.e. VLANs, public, private, etc...)
|
||||
|
||||
### VM Management
|
||||
|
@ -57,10 +57,10 @@ All of the current assets and UI are placeholders, I'll find somebody to design
|
|||
- ✅Reset VNC passwords
|
||||
- ✅Change disk and network drivers for VMs
|
||||
- ✅Set boot order for VMs
|
||||
- 🚧Adjusting VM resources (CPU, RAM, disk space, etc...)
|
||||
- 🚧Adding and removing disks
|
||||
- 🚧Adding and removing NICs
|
||||
- 🚧Backup/Restore/Snapshot VMs
|
||||
- ✅Adjusting VM resources (CPU, RAM, etc...)
|
||||
- ✅Adding and removing disks
|
||||
- ✅Adding and removing NICs
|
||||
- ✅Backup/Restore/Snapshot VMs
|
||||
- 🚧Migrate VMs between nodes/clusters
|
||||
- 🚧IP Management (DHCP, vSwitch, etc...)
|
||||
- 🚧Display VM resource usage + history
|
||||
|
@ -72,21 +72,21 @@ All of the current assets and UI are placeholders, I'll find somebody to design
|
|||
- ✅Add clusters
|
||||
- ✅Delete clusters
|
||||
- ✅Enable/disable clusters
|
||||
- 🚧Load balancing
|
||||
- ✅Load balancing
|
||||
- 🚧Resource based deployment
|
||||
|
||||
### Other Features
|
||||
|
||||
- ✅Add ISOs
|
||||
- ✅Get total resources (CPU, disk, RAM, VMs, nodes)
|
||||
- 🚧Manage IP addresses (add, delete, reserve, unreserve)
|
||||
- 🚧Logging and alerting
|
||||
- 🚧Automated backups of SQLite3 database
|
||||
- 🚧Automatic updates of KontrolVM + Nodes
|
||||
- ✅Manage IP addresses (add, delete, reserve, unreserve)
|
||||
- ✅Logging and alerting
|
||||
- ✅Automated backups of SQLite3 database
|
||||
- ✅Automatic updates of KontrolVM + Nodes
|
||||
|
||||
# Requirements
|
||||
|
||||
- PHP 8.x (tested on PHP 8.1 and 8.2)
|
||||
- PHP 8.1+ (tested on PHP 8.1 and 8.2)
|
||||
- SQLite3
|
||||
|
||||
# KontrolVM Installation
|
||||
|
|
223
account.php
223
account.php
|
@ -1,7 +1,16 @@
|
|||
<?php
|
||||
/** KontrolVM By KuJoe (https://github.com/KuJoe/kontrolvm) **/
|
||||
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
use BaconQrCode\Renderer\GDLibRenderer;
|
||||
use BaconQrCode\Writer;
|
||||
|
||||
session_start();
|
||||
if(isset($_SESSION['mfa_required']) && $_SESSION['mfa_required']) {
|
||||
header("Location: logout.php");
|
||||
exit;
|
||||
}
|
||||
if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
|
@ -9,14 +18,15 @@ if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
|||
define('AmAllowed', TRUE);
|
||||
require_once('config.php');
|
||||
require_once('functions.php');
|
||||
$loggedin_id = $_SESSION['staff_id'];
|
||||
$loggedin_id = (int)$_SESSION['staff_id'];
|
||||
$myrole = (int)$_SESSION["staff_role"];
|
||||
$chkActive = checkActive($loggedin_id);
|
||||
$chkLocked = checkLockedOut($loggedin_id);
|
||||
$chkRole = getStaffRole($loggedin_id);
|
||||
if($chkLocked == true OR $chkActive == false) {
|
||||
header("Location: logout.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
if(isset($_GET['id'])) {
|
||||
$staff_id = $_GET['id'];
|
||||
} elseif(isset($_POST['id'])) {
|
||||
|
@ -27,60 +37,105 @@ if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
|||
error_log("Error: Account ID missing.");
|
||||
exit;
|
||||
}
|
||||
if($staff_id != $loggedin_id) {
|
||||
$allowedRoles = ['1', '9'];
|
||||
if(!in_array($chkRole, $allowedRoles)) {
|
||||
header("Location: home.php?s=99");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
$staff = getStaffDetails($staff_id);
|
||||
if(isset($_GET['s'])) {
|
||||
if ($_GET['s'] == '1') {
|
||||
if($_GET['s'] == '1') {
|
||||
$success = "Account updated successfully.";
|
||||
} elseif ($_GET['s'] == '2') {
|
||||
$success = ".";
|
||||
} elseif($_GET['s'] == '2') {
|
||||
$success = "MFA enabled successfully.";
|
||||
} elseif($_GET['s'] == '3') {
|
||||
$success = "MFA disabled successfully.";
|
||||
}
|
||||
}
|
||||
}
|
||||
$csrfToken = getCSRFToken();
|
||||
$google2fa = new Google2FA();
|
||||
$mfasecret = $google2fa->generateSecretKey();
|
||||
$g2faUrl = $google2fa->getQRCodeUrl(
|
||||
'KontrolVM',
|
||||
$_SESSION["username"],
|
||||
$mfasecret
|
||||
);
|
||||
$writer = new Writer(new GDLibRenderer(250));
|
||||
$qrcode_image = base64_encode($writer->writeString($g2faUrl));
|
||||
|
||||
if($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$token = $_POST["csrf_token"];
|
||||
if(validateCSRFToken($token)) {
|
||||
if (isset($_POST['save_account'])) {
|
||||
if(isset($_POST['save_account'])) {
|
||||
$username = $_POST['username'];
|
||||
$email = $_POST['email'];
|
||||
if($chkRole == '2') {
|
||||
$role = '2';
|
||||
} else {
|
||||
$role = $_POST['role'];
|
||||
}
|
||||
if($role == '9' AND $chkRole != '9') {
|
||||
header("Location: home.php?s=99");
|
||||
exit;
|
||||
}
|
||||
if($staff_id == '1') {
|
||||
$status = '1';
|
||||
} else {
|
||||
if($_POST["status"]) {
|
||||
if(isset($_POST["status"])) {
|
||||
$status = '1';
|
||||
} else {
|
||||
$status = '0';
|
||||
}
|
||||
}
|
||||
if($_POST["confirm-password"]) {
|
||||
if(isset($_POST["password-change"])) {
|
||||
$password1 = $_POST['new-password'];
|
||||
$password2 = $_POST['confirm-password'];
|
||||
} else {
|
||||
$password1 = NULL;
|
||||
$password2 = NULL;
|
||||
}
|
||||
$result = updateStaff($staff_id,$username,$email,$status,$password1,$password2);
|
||||
$result = updateStaff($loggedin_id,$staff_id,$username,$email,$status,$role,$password1,$password2);
|
||||
if($result === true) {
|
||||
if($_SESSION["staff_id"] == $_POST["id"]) {
|
||||
if($loggedin_id == $_POST["id"]) {
|
||||
$_SESSION["username"] = $username;
|
||||
}
|
||||
header("Location: account.php?id=$staff_id&s=1");
|
||||
header("Location: account.php?id=". (int)$staff_id. "&s=1");
|
||||
} else {
|
||||
$error = "Account update failed: ".$result;
|
||||
$error = $result;
|
||||
}
|
||||
}
|
||||
if (isset($_POST['delete_account'])) {
|
||||
if(isset($_POST["mfa-verify"]) AND isset($_POST["enable_mfa"])) {
|
||||
$mfacode = $_POST["mfa-verify"];
|
||||
$mfasecret = $_POST["mfa_secret"];
|
||||
$result = enableMFA($loggedin_id,$staff_id,$mfasecret,$mfacode);
|
||||
if($result === true) {
|
||||
header("Location: account.php?id=". (int)$staff_id. "&s=2");
|
||||
} else {
|
||||
$error = "MFA failed to enable.";
|
||||
}
|
||||
}
|
||||
if(!isset($_POST["mfastate"]) AND isset($_POST["disable_mfa"])) {
|
||||
$result = disableMFA($loggedin_id,$staff_id);
|
||||
if($result === true) {
|
||||
header("Location: account.php?id=". (int)$staff_id. "&s=3");
|
||||
} else {
|
||||
$error = "MFA failed to disable.";
|
||||
}
|
||||
}
|
||||
if(isset($_POST['delete_account'])) {
|
||||
if($_SESSION["staff_id"] == $_POST["id"]) {
|
||||
$error = "User delete failed: You can't deleted yourself.";
|
||||
} else {
|
||||
if (isset($_POST['confirm'])) {
|
||||
if(isset($_POST['confirm'])) {
|
||||
$confirm = $_POST['confirm'];
|
||||
$result = deleteUser($_POST["id"],$confirm);
|
||||
$result = deleteUser($loggedin_id,$_POST["id"],$confirm);
|
||||
if($result === true) {
|
||||
header("Location: users.php?s=2");
|
||||
} else {
|
||||
$error = "User delete failed: ".$result;
|
||||
$error = $result;
|
||||
}
|
||||
} else {
|
||||
$error = "User delete failed: Please make sure you checked the confirmation box.";
|
||||
|
@ -92,7 +147,7 @@ if($_SERVER["REQUEST_METHOD"] == "POST") {
|
|||
}
|
||||
}
|
||||
|
||||
if ($staff) {
|
||||
if($staff) {
|
||||
if($staff_id == '1') {
|
||||
$state = " checked disabled";
|
||||
} else {
|
||||
|
@ -102,6 +157,11 @@ if ($staff) {
|
|||
$state = "";
|
||||
}
|
||||
}
|
||||
$role = $staff['staff_role'];
|
||||
if($role == '9' AND $chkRole != '9') {
|
||||
header("Location: home.php?s=99");
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
@ -115,25 +175,24 @@ if ($staff) {
|
|||
<label class="logo"><a href="index.php"><img src="assets/logo.png" alt="KontrolVM Logo"></a></label>
|
||||
<ul>
|
||||
<li><a href="index.php">Dashboard</a></li>
|
||||
<li><a href="nodes.php">Nodes</a></li>
|
||||
<li><a href="vms.php">VMs</a></li>
|
||||
<li><a href="users.php">Users</a></li>
|
||||
<?php if(in_array($myrole, ['2', '9'])) { ?> <li><a href="clusters.php">Infrastructure</a></li> <?php } ?>
|
||||
<?php if(in_array($myrole, ['1', '9'])) { ?> <li><a class="active" href="users.php">Users</a></li> <?php } ?>
|
||||
<li><a href="settings.php">Settings</a></li>
|
||||
<li style="font-weight: bold;"><a class="active" href="account.php"><?php echo $_SESSION["username"]; ?></a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo htmlspecialchars($_SESSION["username"]); ?></a></li>
|
||||
<li><a href="logout.php"><i class="fa fa-sign-out" aria-hidden="true"></i></a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="container">
|
||||
<h1>Manage Account</h1>
|
||||
<?php if (isset($success)) { ?>
|
||||
<?php if(isset($success)) { ?>
|
||||
<div class="success-message"><?php echo $success; ?></div>
|
||||
<?php } ?>
|
||||
<?php if (isset($error)) { ?>
|
||||
<?php if(isset($error)) { ?>
|
||||
<div class="error-message"><?php echo $error; ?></div>
|
||||
<?php } ?>
|
||||
<form id="save_account" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo $csrfToken; ?>">
|
||||
<input type="hidden" name="id" value="<?php echo $staff_id; ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken); ?>">
|
||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($staff_id); ?>">
|
||||
<h2>Profile</h2>
|
||||
<div class="form-group">
|
||||
<label for="username">Username:</label>
|
||||
|
@ -147,12 +206,33 @@ if ($staff) {
|
|||
<label for="status">Active: </label>
|
||||
<input type="checkbox" id="status" name="status"<?php echo $state; ?>>
|
||||
</div>
|
||||
|
||||
<h2>Change Password</h2>
|
||||
<div class="form-group">
|
||||
<label for="password-change">Change Password: </label>
|
||||
<input type="checkbox" id="password-change" name="password-change">
|
||||
<label for="status">User Role: </label>
|
||||
<?php if($chkRole == '2' OR $staff_id == '1') { ?>
|
||||
<select id="role" name="role" style="text-align:center;" disabled>
|
||||
<?php } else { ?>
|
||||
<select id="role" name="role" style="text-align:center;">
|
||||
<?php } ?>
|
||||
<?php if($role == '1') { ?>
|
||||
<option value="1" selected>IAM Support</option>
|
||||
<option value="2">Infra Support</option>
|
||||
<option value="9">Full Admin</option>
|
||||
<?php } elseif($role == '2') { ?>
|
||||
<option value="1">IAM Support</option>
|
||||
<option value="2" selected>Infra Support</option>
|
||||
<option value="9">Full Admin</option>
|
||||
<?php } elseif($role == '9') { ?>
|
||||
<option value="1">IAM Support</option>
|
||||
<option value="2">Infra Support</option>
|
||||
<option value="9" selected>Full Admin</option>
|
||||
<?php } else { ?>
|
||||
<option value="1">IAM Support</option>
|
||||
<option value="2">Infra Support</option>
|
||||
<option value="9">Full Admin</option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
</div>
|
||||
<h2>Change Password <input type="checkbox" id="password-change" name="password-change"></h2>
|
||||
<div class="form-group password-options">
|
||||
<label for="new-password">New Password:</label>
|
||||
<input type="password" id="new-password" name="new-password">
|
||||
|
@ -161,10 +241,41 @@ if ($staff) {
|
|||
<label for="confirm-password">Confirm New Password:</label>
|
||||
<input type="password" id="confirm-password" name="confirm-password">
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<button type="submit" class="stylish-button" name="save_account">Save Changes</button>
|
||||
</form>
|
||||
<?php if ($staff_id > "1") { ?>
|
||||
<br />
|
||||
<hr />
|
||||
<?php if(empty($staff["staff_mfa"])) { ?>
|
||||
<form id="enable_mfa" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken); ?>">
|
||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($staff_id); ?>">
|
||||
<input type="hidden" name="mfa_secret" value="<?php echo htmlspecialchars($mfasecret); ?>">
|
||||
<br />
|
||||
<h2>Enable MFA <input type="checkbox" id="mfa-change" name="mfa-change"> </h2>
|
||||
<div class="form-group mfa-options">
|
||||
<p>
|
||||
<img src="data:image/png;base64, <?php echo $qrcode_image; ?> "/><br />
|
||||
<span style="padding-left:22px;"><small>Secret Key: <?php echo htmlspecialchars($mfasecret); ?></small>
|
||||
</p>
|
||||
<br />
|
||||
<label for="mfa-verify">Verify MFA:</label>
|
||||
<input type="text" id="mfa-verify" name="mfa-verify">
|
||||
<br /><br />
|
||||
<button type="submit" class="stylish-button" name="enable_mfa">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php } else { ?>
|
||||
<form id="disable_mfa" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken); ?>">
|
||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($staff_id); ?>">
|
||||
<br />
|
||||
<h2>Disable MFA <input type="checkbox" id="mfastate" name="mfastate" checked></h2>
|
||||
<br /><br />
|
||||
<button type="submit" class="stylish-button" name="disable_mfa">Save Changes</button>
|
||||
</form>
|
||||
<?php } ?>
|
||||
<?php if($staff_id > "1") { ?>
|
||||
<br />
|
||||
<hr />
|
||||
<br />
|
||||
|
@ -180,8 +291,8 @@ if ($staff) {
|
|||
Confirm
|
||||
</td>
|
||||
<td>
|
||||
<input type="hidden" name="csrf_token" value="<?php echo $csrfToken; ?>">
|
||||
<input type="hidden" name="id" value="<?php echo $staff_id; ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken); ?>">
|
||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($staff_id); ?>">
|
||||
<button type="submit" name="delete_account" id="delete_account" class="stylish-button" style="background-color:red;">DELETE</button>
|
||||
</form>
|
||||
</td>
|
||||
|
@ -197,34 +308,30 @@ if ($staff) {
|
|||
?>
|
||||
<?php include('footer.php'); ?>
|
||||
<script>
|
||||
// JavaScript code to handle theme switching
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const body = document.body;
|
||||
|
||||
// Load the user's preferred theme from localStorage
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
body.classList.add('dark-mode');
|
||||
themeToggle.checked = true;
|
||||
}
|
||||
|
||||
themeToggle.addEventListener('change', () => {
|
||||
if (themeToggle.checked) {
|
||||
body.classList.add('dark-mode');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
body.classList.remove('dark-mode');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
});
|
||||
// Ensure this code runs after the DOM is fully loaded
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Show/hide password options based on checkbox state
|
||||
const mfaEnabled = document.getElementById('password-change');
|
||||
const mfaOptions = document.querySelectorAll('.password-options');
|
||||
const pwChange = document.getElementById('password-change');
|
||||
const pwOptions = document.querySelectorAll('.password-options');
|
||||
|
||||
mfaEnabled.addEventListener('change', function() {
|
||||
if (this.checked) {
|
||||
pwChange.addEventListener('change', function() {
|
||||
if(this.checked) {
|
||||
pwOptions.forEach(option => option.style.display = 'block');
|
||||
} else {
|
||||
pwOptions.forEach(option => option.style.display = 'none');
|
||||
}
|
||||
});
|
||||
|
||||
// Trigger the change event initially to set the correct state on page load
|
||||
pwChange.dispatchEvent(new Event('change'));
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Show/hide MFA options based on checkbox state
|
||||
const mfaChange = document.getElementById('mfa-change');
|
||||
const mfaOptions = document.querySelectorAll('.mfa-options');
|
||||
|
||||
mfaChange.addEventListener('change', function() {
|
||||
if(this.checked) {
|
||||
mfaOptions.forEach(option => option.style.display = 'block');
|
||||
} else {
|
||||
mfaOptions.forEach(option => option.style.display = 'none');
|
||||
|
@ -232,7 +339,7 @@ if ($staff) {
|
|||
});
|
||||
|
||||
// Trigger the change event initially to set the correct state on page load
|
||||
mfaEnabled.dispatchEvent(new Event('change'));
|
||||
mfaChange.dispatchEvent(new Event('change'));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
|
BIN
assets/logo.png
BIN
assets/logo.png
Binary file not shown.
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 10 KiB |
201
assets/style.css
201
assets/style.css
|
@ -108,6 +108,11 @@ label.logo {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
label.logo img {
|
||||
height: 64px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
float: right;
|
||||
margin-right: 20px;
|
||||
|
@ -248,10 +253,6 @@ input:checked + .slider:before {
|
|||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.darkmode {
|
||||
margin: 2px 0 2px -1px;
|
||||
}
|
||||
|
||||
form {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
|
@ -285,9 +286,9 @@ input[type="text"]:disabled {
|
|||
}
|
||||
|
||||
.success-message {
|
||||
color: #155724; /* Dark green text color */
|
||||
background-color: #d4edda; /* Light green background */
|
||||
border: 1px solid #c3e6cb; /* Green border */
|
||||
color: #155724;
|
||||
background-color: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
padding: 10px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 4px;
|
||||
|
@ -308,6 +309,7 @@ input[type="text"]:disabled {
|
|||
background-color: #007bff; /* Blue background color */
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
margin:5px;
|
||||
border: none;
|
||||
border-radius: 5px; /* Slightly rounded corners */
|
||||
cursor: pointer;
|
||||
|
@ -432,35 +434,35 @@ input[type="text"]:disabled {
|
|||
}
|
||||
|
||||
.submenu {
|
||||
list-style: none;
|
||||
margin: 20px auto;
|
||||
padding: 0;
|
||||
background-color: #f0f0f5;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
border: 1px solid #e0e0e5;
|
||||
list-style: none;
|
||||
padding: 10px 0; /* Adjust or remove top padding */
|
||||
background-color: #f0f0f5;
|
||||
border-bottom: 1px solid #e0e0e5; /* Add a bottom border for separation */
|
||||
width: 100%;
|
||||
position: relative; /* Or static */
|
||||
text-align: center; /* Center the submenu items */
|
||||
}
|
||||
|
||||
.submenu li {
|
||||
display: inline-block;
|
||||
margin-right: 15px;
|
||||
display: inline-block;
|
||||
margin: 0 10px; /* Adjust spacing between items */
|
||||
}
|
||||
|
||||
.submenu a {
|
||||
text-decoration: none;
|
||||
color: #333; /* Dark gray text */
|
||||
font-weight: 500;
|
||||
padding: 8px 12px; /* Add some padding */
|
||||
border-radius: 3px; /* Slightly rounded links */
|
||||
transition: background-color 0.3s ease; /* Smooth transition for hover effect */
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
padding: 8px 12px;
|
||||
border-radius: 3px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.submenu a.active {
|
||||
font-weight: bold;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.submenu a:hover {
|
||||
background-color: #e0e0e5;
|
||||
background-color: #e0e0e5;
|
||||
}
|
||||
|
||||
.actionButtons {
|
||||
|
@ -547,96 +549,113 @@ button.btnaction:active{
|
|||
box-shadow:0 0 0 0;
|
||||
}
|
||||
|
||||
/* Dark Mode Styles */
|
||||
body.dark-mode {
|
||||
background-color: #333;
|
||||
color: #eee;
|
||||
font-family: "Montserrat", sans-serif;
|
||||
margin: 0;
|
||||
/* Disk Management Styles */
|
||||
.disk-management {
|
||||
display: flex;
|
||||
min-height: 100vh; /* Ensure the body takes at least the full viewport height */
|
||||
flex-direction: column; /* Arrange content vertically */
|
||||
flex-wrap: wrap; /* Allow wrapping on smaller screens */
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
body.dark-mode .container {
|
||||
background-color: #222;
|
||||
color: #eee;
|
||||
min-width: 30%;
|
||||
max-width: 1500px;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
.disk-list,.add-disk {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
body.dark-mode .card {
|
||||
background-color: #29292c;
|
||||
min-width: 300px; /* Ensure minimum width for each section */
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
body.dark-mode .grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr); /* 5 equal-width columns */
|
||||
grid-gap: 20px;
|
||||
}
|
||||
|
||||
body.dark-mode .card .value {
|
||||
font-size: 2.5rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
background-color: #29292c;
|
||||
}
|
||||
|
||||
|
||||
body.dark-mode .card h2 {
|
||||
.disk-list h3,.add-disk h3 {
|
||||
margin-top: 0;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
body.dark-mode th {
|
||||
background-color: #222;
|
||||
color: #eee;
|
||||
#disk-list-items {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body.dark-mode nav {
|
||||
background: #1f2029;
|
||||
height: 80px;
|
||||
/* Modal Styles */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
body.dark-mode .footer {
|
||||
background-color: #222;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
body.dark-mode .social-links a {
|
||||
text-decoration: none;
|
||||
color: #eee;
|
||||
display: flex; /* Align icon and text horizontally */
|
||||
align-items: center; /* Center icon and text vertically */
|
||||
}
|
||||
|
||||
body.dark-mode .modal-content {
|
||||
background-color: #232323;
|
||||
margin: 15% auto;
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
margin: 15% auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #888;
|
||||
max-width: 300px;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
body.dark-mode .submenu {
|
||||
background-color: #1e1e1e;
|
||||
.close {
|
||||
color: #aaa;
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
body.dark-mode .submenu a {
|
||||
color: #fff;
|
||||
.close:hover,.close:focus {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
body.dark-mode .submenu a:hover {
|
||||
background-color: #0e0e0e;
|
||||
.pagination {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.pagination a {
|
||||
padding: 5px 10px;
|
||||
margin: 2px;
|
||||
border: 1px solid #ddd;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
}
|
||||
.pagination a.active {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
.admin-update-banner {
|
||||
background-color: #ffeb3b; /* Yellow background for attention */
|
||||
color: #333; /* Dark text for readability */
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
position: fixed; /* Fixed position to stay at the top */
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 1000; /* Ensure it's on top of other elements */
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); /* Subtle shadow */
|
||||
display: flex; /* Use flexbox for layout */
|
||||
justify-content: center; /* Center content horizontally */
|
||||
align-items: center; /* Center content vertically */
|
||||
}
|
||||
.admin-update-banner a {
|
||||
color: #007bff; /* Link color */
|
||||
text-decoration: none;
|
||||
margin-left: 10px; /* Space between text and link */
|
||||
}
|
||||
.admin-update-banner a:hover {
|
||||
text-decoration: underline; /* Underline on hover */
|
||||
}
|
||||
.admin-update-banner .close-button {
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
font-size: 1.2em;
|
||||
color: #555;
|
||||
}
|
||||
.admin-update-banner .close-button:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Responsive Styles */
|
||||
|
|
20
assets/switchtheme.js
Normal file
20
assets/switchtheme.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
// JavaScript code to handle theme switching (from previous responses)
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const body = document.body;
|
||||
|
||||
// Load the user's preferred theme from localStorage
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if(savedTheme === 'dark') {
|
||||
body.classList.add('dark-mode');
|
||||
themeToggle.checked = true;
|
||||
}
|
||||
|
||||
themeToggle.addEventListener('change', () => {
|
||||
if(themeToggle.checked) {
|
||||
body.classList.add('dark-mode');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
body.classList.remove('dark-mode');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
});
|
108
auth.php
108
auth.php
|
@ -7,9 +7,9 @@ require_once('config.php');
|
|||
require_once('functions.php');
|
||||
$remote_addr = getRealUserIp();
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
if($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$errorMessage = "";
|
||||
if (isset($_POST['username']) && isset($_POST['password'])) {
|
||||
if(isset($_POST['username']) && isset($_POST['password'])) {
|
||||
if(isset($secretkey)) {
|
||||
$cf_url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
|
||||
$token = $_POST['cf-turnstile-response'];
|
||||
|
@ -24,14 +24,14 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
|||
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
$response = curl_exec($curl);
|
||||
if (curl_errno($curl)) {
|
||||
if(curl_errno($curl)) {
|
||||
$error_message = curl_error($curl);
|
||||
error_log("Error with CAPTCHA: " . $error_message);
|
||||
curl_close($curl);
|
||||
header("Location: index.php?e=1");
|
||||
}else{
|
||||
$response = json_decode($response,true);
|
||||
if ($response['error-codes'] && count($response['error-codes']) > 0){
|
||||
if($response['error-codes'] && count($response['error-codes']) > 0){
|
||||
error_log("Cloudflare Turnstile check failed.");
|
||||
curl_close($curl);
|
||||
header("Location: index.php?e=1");
|
||||
|
@ -51,13 +51,14 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
|||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if($user) {
|
||||
$staff_id = $user['staff_id'];
|
||||
$staff_role = $user['staff_role'];
|
||||
$chkActive = checkActive($staff_id);
|
||||
$chkLocked = checkLockedOut($staff_id);
|
||||
if($chkLocked == false AND $chkActive == true) {
|
||||
if(password_verify($password, $user['staff_password'])) {
|
||||
$_SESSION["loggedin"] = true;
|
||||
$_SESSION["username"] = $username;
|
||||
$_SESSION['staff_id'] = $staff_id;
|
||||
$_SESSION['staff_role'] = $staff_role;
|
||||
$staff_lastlogin = time();
|
||||
$stmt = $conn->prepare("UPDATE staff SET staff_ip =:staff_ip, staff_lastlogin =:staff_lastlogin, staff_failed_logins =:staff_failed_logins WHERE staff_id =:staff_id");
|
||||
$stmt->bindValue(':staff_ip', "$remote_addr", SQLITE3_TEXT);
|
||||
|
@ -65,7 +66,14 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
|||
$stmt->bindValue(':staff_failed_logins', '0', SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':staff_id', $staff_id, SQLITE3_INTEGER);
|
||||
$stmt->execute();
|
||||
header("refresh:3;url=home.php");
|
||||
if(isset($user['staff_mfa'])) {
|
||||
$_SESSION['mfa_required'] = true;
|
||||
header("Location: ". $_SERVER["PHP_SELF"]);
|
||||
exit;
|
||||
} else {
|
||||
$_SESSION["loggedin"] = true;
|
||||
header("Location: home.php");
|
||||
}
|
||||
} else {
|
||||
$sql = "UPDATE staff SET staff_failed_logins = staff_failed_logins + 1 WHERE staff_id =:staff_id";
|
||||
$stmt = $conn->prepare($sql);
|
||||
|
@ -98,12 +106,20 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
|||
echo "Error: " . $e->getMessage();
|
||||
}
|
||||
$stmt = null;
|
||||
}
|
||||
} else {
|
||||
header("refresh:3;url=index.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
} elseif(isset($_POST["otp"])) {
|
||||
$staffid = (int)$_POST["staff_id"];
|
||||
$otp = (int)$_POST["otp"];
|
||||
if(verifyMFA($staffid,$otp)) {
|
||||
unset($_SESSION['mfa_required']);
|
||||
$_SESSION["loggedin"] = true;
|
||||
header("Location: home.php");
|
||||
} else {
|
||||
session_destroy();
|
||||
header("Location: index.php?e=3");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
} elseif($_SESSION['mfa_required']) {
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
@ -121,7 +137,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
|||
background-color: #f4f4f7;
|
||||
}
|
||||
|
||||
.logout-container {
|
||||
.container {
|
||||
background-color: #fff;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
|
@ -130,35 +146,61 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
|||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 15px;
|
||||
color: #666;
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.loader {
|
||||
border: 8px solid #f3f3f3;
|
||||
border-radius: 50%;
|
||||
border-top: 8px solid #3498db;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 2s linear infinite;
|
||||
margin: 20px auto;
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
input[type="text"],
|
||||
input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="logout-container">
|
||||
<h1>Authenticating account...</h1>
|
||||
<p>Please wait.</p>
|
||||
<div class="loader"></div>
|
||||
<div class="container">
|
||||
<img src="assets/logo.png" alt="KontrolVM Logo" style="display:block;margin:0 auto;" />
|
||||
<br />
|
||||
<br />
|
||||
<h1>Two-Factor Authentication</h1>
|
||||
<p>Please enter your OTP:
|
||||
<form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
|
||||
<input type="hidden" name="staff_id" value="<?php echo htmlspecialchars($_SESSION['staff_id']);?>">
|
||||
<input type="text" name="otp" required>
|
||||
<br />
|
||||
<br />
|
||||
<button type="submit">Verify</button>
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
<?php
|
||||
} else {
|
||||
header("refresh:3;url=index.php");
|
||||
exit;
|
||||
}
|
||||
?>
|
270
cluster.php
Normal file
270
cluster.php
Normal file
|
@ -0,0 +1,270 @@
|
|||
<?php
|
||||
/** KontrolVM By KuJoe (https://github.com/KuJoe/kontrolvm) **/
|
||||
|
||||
session_start();
|
||||
define('AmAllowed', TRUE);
|
||||
if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
} else {
|
||||
if(isset($_GET['id'])) {
|
||||
if(isset($_GET['s']) AND $_GET['s'] == '1') {
|
||||
$success = "Cluster updated successfully.";
|
||||
}
|
||||
if(isset($_GET['s']) AND $_GET['s'] == '2') {
|
||||
$success = "Cluster enabled successfully.";
|
||||
}
|
||||
if(isset($_GET['s']) AND $_GET['s'] == '3') {
|
||||
$success = "Cluster disabled successfully.";
|
||||
}
|
||||
$cluster_id = $_GET['id'];
|
||||
require_once('functions.php');
|
||||
$cluster = getClusterDetails($cluster_id);
|
||||
} elseif(isset($_POST['id'])) {
|
||||
$cluster_id = $_POST['id'];
|
||||
require_once('functions.php');
|
||||
$cluster = getClusterDetails($cluster_id);
|
||||
} else {
|
||||
header("Location: clusters.php?s=3");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
$loggedin_id = (int)$_SESSION['staff_id'];
|
||||
$myrole = (int)$_SESSION["staff_role"];
|
||||
$chkActive = checkActive($loggedin_id);
|
||||
$chkLocked = checkLockedOut($loggedin_id);
|
||||
if($chkLocked == true OR $chkActive == false) {
|
||||
header("Location: logout.php");
|
||||
exit;
|
||||
}
|
||||
$chkRole = getStaffRole($loggedin_id);
|
||||
$allowedRoles = ['2', '9'];
|
||||
if(!in_array($chkRole, $allowedRoles)) {
|
||||
header("Location: home.php?s=99");
|
||||
exit;
|
||||
}
|
||||
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
$csrfToken = getCSRFToken();
|
||||
|
||||
if($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$token = $_POST["csrf_token"];
|
||||
if(validateCSRFToken($token)) {
|
||||
if(isset($_POST['editcluster'])) {
|
||||
$cluster_data = [':friendlyname' => $_POST["friendlyname"],':deployment' => $_POST["deployment"]];
|
||||
$result = editcluster($loggedin_id,$cluster_id, $cluster_data);
|
||||
if($result === true) {
|
||||
header("Location: cluster.php?id=". (int)$cluster_id. "&s=1");
|
||||
} else {
|
||||
$error = "Cluster update failed.";
|
||||
}
|
||||
}
|
||||
if(isset($_POST['delete_cluster'])) {
|
||||
$confirm = $_POST['confirm'];
|
||||
$result = deleteCluster($loggedin_id,$cluster_id,$confirm);
|
||||
if($result === true) {
|
||||
header("Location: clusters.php?s=2");
|
||||
} else {
|
||||
$error = "Cluster deletion failed.";
|
||||
}
|
||||
}
|
||||
if(isset($_POST['enable_cluster'])) {
|
||||
$result = enableCluster($loggedin_id,$cluster_id);
|
||||
if($result === true) {
|
||||
header("Location: cluster.php?id=". (int)$cluster_id. "&s=2");
|
||||
} else {
|
||||
$error = "Enabling cluster failed.";
|
||||
}
|
||||
}
|
||||
if(isset($_POST['disable_cluster'])) {
|
||||
$result = disableCluster($loggedin_id,$cluster_id);
|
||||
if($result === true) {
|
||||
header("Location: cluster.php?id=". (int)$cluster_id. "&s=3");
|
||||
} else {
|
||||
$error = "Disabling cluster failed.";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$error = "Invalid CSRF token.";
|
||||
}
|
||||
}
|
||||
|
||||
if($cluster) {
|
||||
if($cluster['status'] == "1") {
|
||||
$state = " checked";
|
||||
} else {
|
||||
$state = "";
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<?php include('header.php'); ?>
|
||||
<body>
|
||||
<nav>
|
||||
<input type="checkbox" id="check">
|
||||
<label for="check" class="checkbtn">
|
||||
<i class="fas fa-bars"></i>
|
||||
</label>
|
||||
<label class="logo"><a href="index.php"><img src="assets/logo.png" alt="KontrolVM Logo"></a></label>
|
||||
<ul>
|
||||
<li><a href="index.php">Dashboard</a></li>
|
||||
<?php if(in_array($myrole, ['2', '9'])) { ?> <li><a class="active" href="clusters.php">Infrastructure</a></li> <?php } ?>
|
||||
<?php if(in_array($myrole, ['1', '9'])) { ?> <li><a href="users.php">Users</a></li> <?php } ?>
|
||||
<li><a href="settings.php">Settings</a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo htmlspecialchars($_SESSION["username"]); ?></a></li>
|
||||
<li><a href="logout.php"><i class="fa fa-sign-out" aria-hidden="true"></i></a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<ul class="submenu">
|
||||
<li><a class="active" href="clusters.php">Clusters</a></li>
|
||||
<li><a href="nodes.php">Nodes</a></li>
|
||||
<li><a href="vms.php">VMs</a></li>
|
||||
</ul>
|
||||
<div class="container">
|
||||
<h1>Cluster Details</h1>
|
||||
<?php if(isset($success)) { ?>
|
||||
<div class="success-message"><?php echo $success; ?></div>
|
||||
<?php } ?>
|
||||
<?php if(isset($error)) { ?>
|
||||
<div class="error-message"><?php echo $error; ?></div>
|
||||
<?php } ?>
|
||||
<div class="table-container" style="max-width:1500px;">
|
||||
<div class="cluster-details">
|
||||
<form id="editcluster" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken); ?>">
|
||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($cluster_id); ?>">
|
||||
<table>
|
||||
<tr>
|
||||
<td style="background-color:#999;">Cluster Name:</td>
|
||||
<td><input type="text" id="friendlyname" name="friendlyname" value="<?php echo htmlspecialchars($cluster['friendlyname']); ?>" style="text-align:center;width:80%;"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="background-color:#999;">Status:</td>
|
||||
<td>
|
||||
<label class="checkbox-container">
|
||||
<input type="checkbox" name="status"<?php echo $state; ?> disabled>
|
||||
<span class="checkmark"></span>
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="background-color:#999;">Deployment:</td>
|
||||
<td><select id="deployment" name="deployment" style="text-align:center;width:80%;">
|
||||
<?php if($cluster['deployment'] == '1') { ?>
|
||||
<option value="1" selected>Load-balanced</option>
|
||||
<option value="2">Resource based</option>
|
||||
<?php } else { ?>
|
||||
<option value="1">Load-balanced</option>
|
||||
<option value="2" selected>Resource based</option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="background-color:#999;">Last Updated:</td>
|
||||
<td><?php echo date('m/j/Y @ g:i:s A', $cluster['last_updated']); ?></td>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
<br />
|
||||
<center><button type="submit" name="editcluster" id="editcluster" class="stylish-button">EDIT CLUSTER</button><br /></center>
|
||||
</form>
|
||||
<br />
|
||||
<hr />
|
||||
<br />
|
||||
<table>
|
||||
<tr>
|
||||
<td style="background-color:#999;">TOGGLE CLUSTER:</td>
|
||||
<td style="padding:10px;">
|
||||
<form id="togglecluster" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken); ?>">
|
||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($cluster_id); ?>">
|
||||
<?php if($cluster['status'] == "1") { ?>
|
||||
<button type="submit" class="stylish-button" name="disable_cluster">DISABLE</button>
|
||||
<?php } else { ?>
|
||||
<button type="submit" class="stylish-button" name="enable_cluster">ENABLE</button>
|
||||
<?php } ?>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<?php if($cluster['status'] == "0") { ?>
|
||||
<br />
|
||||
<hr />
|
||||
<br />
|
||||
<table>
|
||||
<tr>
|
||||
<td style="background-color:#999;">DELETE CLUSTER:</td>
|
||||
<td style="padding:10px;">
|
||||
<form id="delete_cluster" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
|
||||
<label class="checkbox-container" style="padding:5px;">
|
||||
<input type="checkbox" name="confirm" style="padding:5px;">
|
||||
<span class="checkmark"></span>
|
||||
</label>
|
||||
Confirm
|
||||
</td>
|
||||
<td>
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken); ?>">
|
||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($cluster_id); ?>">
|
||||
<button type="submit" name="delete_cluster" id="delete_cluster" class="stylish-button" style="background-color:red;">DELETE</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php
|
||||
} else {
|
||||
error_log("Error finding cluster.");
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
<?php include('footer.php'); ?>
|
||||
<script>
|
||||
// Unlock field is override is checked.
|
||||
function toggleInput(inputId) {
|
||||
const checkboxId = 'enable' + inputId;
|
||||
const checkbox = document.getElementById(checkboxId);
|
||||
const inputField = document.getElementById(inputId);
|
||||
if(checkbox) { // Check if checkbox exists
|
||||
inputField.readOnly =!checkbox.checked;
|
||||
} else {
|
||||
console.error("Checkbox not found:", checkboxId);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the modal
|
||||
var modal = document.getElementById("addServerModal");
|
||||
|
||||
// Get the button that opens the modal
|
||||
var btn = document.getElementById("addServerBtn");
|
||||
|
||||
// Get the <span> element that closes the modal
|
||||
var span = document.getElementsByClassName("close")[0];
|
||||
|
||||
// When the user clicks the button, open the modal
|
||||
btn.onclick = function() {
|
||||
modal.style.display = "block";
|
||||
}
|
||||
|
||||
// When the user clicks on <span> (x), close the modal
|
||||
span.onclick = function() {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
|
||||
// When the user clicks anywhere outside of the modal, close it
|
||||
window.onclick = function(event) {
|
||||
if(event.target == modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="assets/filtertable.js"></script>
|
||||
</body>
|
||||
</html>
|
129
clusters.php
129
clusters.php
|
@ -2,72 +2,48 @@
|
|||
/** KontrolVM By KuJoe (https://github.com/KuJoe/kontrolvm) **/
|
||||
|
||||
session_start();
|
||||
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
} else {
|
||||
if (isset($_GET['s']) AND $_GET['s'] == '1') {
|
||||
if(isset($_GET['s']) AND $_GET['s'] == '1') {
|
||||
$success = "Cluster added successfully.";
|
||||
}
|
||||
if (isset($_GET['s']) AND $_GET['s'] == '2') {
|
||||
if(isset($_GET['s']) AND $_GET['s'] == '2') {
|
||||
$success = "Cluster deleted successfully.";
|
||||
}
|
||||
if (isset($_GET['s']) AND $_GET['s'] == '3') {
|
||||
$success = "Cluster enabled successfully.";
|
||||
}
|
||||
if (isset($_GET['s']) AND $_GET['s'] == '4') {
|
||||
$success = "Cluster disabled successfully.";
|
||||
if(isset($_GET['s']) AND $_GET['s'] == '3') {
|
||||
$success = "Cluster ID not found.";
|
||||
}
|
||||
define('AmAllowed', TRUE);
|
||||
require_once('config.php');
|
||||
require_once('functions.php');
|
||||
$loggedin_id = $_SESSION['staff_id'];
|
||||
$loggedin_id = (int)$_SESSION['staff_id'];
|
||||
$myrole = (int)$_SESSION["staff_role"];
|
||||
$chkActive = checkActive($loggedin_id);
|
||||
$chkLocked = checkLockedOut($loggedin_id);
|
||||
if($chkLocked == true OR $chkActive == false) {
|
||||
header("Location: logout.php");
|
||||
exit;
|
||||
}
|
||||
$chkRole = getStaffRole($loggedin_id);
|
||||
$allowedRoles = ['2', '9'];
|
||||
if(!in_array($chkRole, $allowedRoles)) {
|
||||
header("Location: home.php?s=99");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
if($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$token = $_POST["csrf_token"];
|
||||
if (validateCSRFToken($token)) {
|
||||
if (isset($_POST['add_cluster'])) {
|
||||
$loc = $_POST["loc"];
|
||||
if(validateCSRFToken($token)) {
|
||||
if(isset($_POST['add_cluster'])) {
|
||||
$friendlyname = $_POST["friendlyname"];
|
||||
$result = addCluster($loc, $friendlyname);
|
||||
$result = addCluster($loggedin_id,$friendlyname);
|
||||
if($result === true) {
|
||||
header("Location: clusters.php?s=1");
|
||||
} else {
|
||||
$error = "Cluster add failed: ".$result;
|
||||
}
|
||||
}
|
||||
if (isset($_POST['delete_cluster'])) {
|
||||
$clusterid = $_POST['clusterid'];
|
||||
$result = deleteCluster($clusterid);
|
||||
if($result === true) {
|
||||
header("Location: clusters.php?s=2");
|
||||
} else {
|
||||
$error = "Cluster deletion failed: ".$result;
|
||||
}
|
||||
}
|
||||
if (isset($_POST['enable_cluster'])) {
|
||||
$clusterid = $_POST['clusterid'];
|
||||
$result = enableCluster($clusterid);
|
||||
if($result === true) {
|
||||
header("Location: clusters.php?s=3");
|
||||
} else {
|
||||
$error = "Enabling cluster failed: ".$result;
|
||||
}
|
||||
}
|
||||
if (isset($_POST['disable_cluster'])) {
|
||||
$clusterid = $_POST['clusterid'];
|
||||
$result = disableCluster($clusterid);
|
||||
if($result === true) {
|
||||
header("Location: clusters.php?s=4");
|
||||
} else {
|
||||
$error = "Disabling cluster failed: ".$result;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$error = "Invalid CSRF token.";
|
||||
|
@ -92,20 +68,17 @@ $clusters = getClusters('all');
|
|||
<label class="logo"><a href="index.php"><img src="assets/logo.png" alt="KontrolVM Logo"></a></label>
|
||||
<ul>
|
||||
<li><a href="index.php">Dashboard</a></li>
|
||||
<li><a href="nodes.php">Nodes</a></li>
|
||||
<li><a href="vms.php">VMs</a></li>
|
||||
<li><a href="users.php">Users</a></li>
|
||||
<li><a class="active" href="settings.php">Settings</a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo $_SESSION["username"]; ?></a></li>
|
||||
<?php if(in_array($myrole, ['2', '9'])) { ?> <li><a class="active" href="clusters.php">Infrastructure</a></li> <?php } ?>
|
||||
<?php if(in_array($myrole, ['1', '9'])) { ?> <li><a href="users.php">Users</a></li> <?php } ?>
|
||||
<li><a href="settings.php">Settings</a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo htmlspecialchars($_SESSION["username"]); ?></a></li>
|
||||
<li><a href="logout.php"><i class="fa fa-sign-out" aria-hidden="true"></i></a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<ul class="submenu">
|
||||
<li><a href="settings.php">General</a></li>
|
||||
<li><a class="active" href="clusters.php">Clusters</a></li>
|
||||
<li><a href="isos.php">ISOs</a></li>
|
||||
<li><a href="ipv4.php">IPv4 Addresses</a></li>
|
||||
<li><a href="ipv6.php">IPv6 Addresses</a></li>
|
||||
<li><a href="nodes.php">Nodes</a></li>
|
||||
<li><a href="vms.php">VMs</a></li>
|
||||
</ul>
|
||||
<div class="container">
|
||||
<p style="float:right;"><button id="addBtn" class="stylish-button"><i class="fa-solid fa-square-plus"></i> ADD CLUSTER</button></p>
|
||||
|
@ -116,19 +89,17 @@ $clusters = getClusters('all');
|
|||
<br />
|
||||
<form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo $csrfToken; ?>">
|
||||
<label for="loc">Cluster Identifier:</label>
|
||||
<input type="text" id="loc" name="loc" placeholder="ie: us1, cluster01, homelab" maxlength="20" required><br><br>
|
||||
<label for="friendlyname">Friendly Name:</label>
|
||||
<input type="text" id="friendlyname" name="friendlyname" maxlength="20" required><br><br>
|
||||
<center><button type="submit" class="stylish-button" name="add_cluster"><i class="fa-solid fa-square-plus"></i> ADD IP</button></center>
|
||||
<center><button type="submit" class="stylish-button" name="add_cluster"><i class="fa-solid fa-square-plus"></i> ADD CLUSTER</button></center>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<h1>Clusters</h1>
|
||||
<?php if (isset($success)) { ?>
|
||||
<?php if(isset($success)) { ?>
|
||||
<div class="success-message"><?php echo $success; ?></div>
|
||||
<?php } ?>
|
||||
<?php if (isset($error)) { ?>
|
||||
<?php if(isset($error)) { ?>
|
||||
<div class="error-message"><?php echo $error; ?></div>
|
||||
<?php } ?>
|
||||
<div class="table-container" style="max-width:1500px;">
|
||||
|
@ -138,39 +109,21 @@ $clusters = getClusters('all');
|
|||
<tr>
|
||||
<th>Cluster Name</th>
|
||||
<th>Status</th>
|
||||
<th>Notes</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
foreach ($clusters as $cluster) {
|
||||
$clusterid = $cluster['clusterid'];
|
||||
$cluster_id = $cluster['cluster_id'];
|
||||
echo '<tr>';
|
||||
echo "<td class='tname'>" . $cluster['friendlyname'] . "</td>";
|
||||
echo "<td class='tname'><a href='cluster.php?id=$cluster_id' />" . $cluster['friendlyname'] . "</a></td>";
|
||||
echo "<td><span class='ticon' style='padding-right:4px;'>Status: </span>";
|
||||
if ($cluster['status'] == "1") {
|
||||
if($cluster['status'] == "1") {
|
||||
echo "<img src='assets/1.png' alt='Enabled'>";
|
||||
} else {
|
||||
echo "<img src='assets/0.png' alt='Disabled'>";
|
||||
}
|
||||
echo "</td>";
|
||||
echo "<td style='font-size:small;'>" . $cluster['notes'] . "</td><td>";
|
||||
echo '<form style="padding:10px;" action="'.htmlspecialchars($_SERVER["PHP_SELF"]).'" method="post">
|
||||
<input type="hidden" name="csrf_token" value="'.$csrfToken.'">
|
||||
<input type="hidden" name="clusterid" value="'.$clusterid.'">
|
||||
<button type="submit" class="stylish-button" name="delete_cluster">Delete</button>
|
||||
</form>';
|
||||
echo '<form style="padding:10px;" action="'.htmlspecialchars($_SERVER["PHP_SELF"]).'" method="post">
|
||||
<input type="hidden" name="csrf_token" value="'.$csrfToken.'">
|
||||
<input type="hidden" name="clusterid" value="'.$clusterid.'">';
|
||||
if ($cluster['status'] == "1") {
|
||||
echo '<button type="submit" class="stylish-button" name="disable_cluster">Disable</button>';
|
||||
} else {
|
||||
echo '<button type="submit" class="stylish-button" name="enable_cluster">Enable</button>';
|
||||
}
|
||||
echo '</form>';
|
||||
echo '</td></tr>';
|
||||
echo "</td></tr>";
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
|
@ -179,26 +132,6 @@ $clusters = getClusters('all');
|
|||
</div>
|
||||
<?php include('footer.php'); ?>
|
||||
<script>
|
||||
// JavaScript code to handle theme switching (from previous responses)
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const body = document.body;
|
||||
|
||||
// Load the user's preferred theme from localStorage
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
body.classList.add('dark-mode');
|
||||
themeToggle.checked = true;
|
||||
}
|
||||
|
||||
themeToggle.addEventListener('change', () => {
|
||||
if (themeToggle.checked) {
|
||||
body.classList.add('dark-mode');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
body.classList.remove('dark-mode');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
});
|
||||
// Get the modal
|
||||
var modal = document.getElementById("addModal");
|
||||
|
||||
|
@ -220,7 +153,7 @@ $clusters = getClusters('all');
|
|||
|
||||
// When the user clicks anywhere outside of the modal, close it
|
||||
window.onclick = function(event) {
|
||||
if (event.target == modal) {
|
||||
if(event.target == modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
{
|
||||
"require": {
|
||||
"phpseclib/phpseclib": "~3.0"
|
||||
"phpseclib/phpseclib": "~3.0",
|
||||
"pragmarx/google2fa": "^8.0",
|
||||
"bacon/bacon-qr-code": "^3.0",
|
||||
"phpmailer/phpmailer": "^6.9"
|
||||
}
|
||||
}
|
||||
|
|
247
composer.lock
generated
247
composer.lock
generated
|
@ -4,8 +4,112 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "dfabe774bbbe86d7ea2d09b36c1daed0",
|
||||
"content-hash": "0a12474dc8b68ee1c425c2cb3dd722d0",
|
||||
"packages": [
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
"version": "v3.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Bacon/BaconQrCode.git",
|
||||
"reference": "f9cc1f52b5a463062251d666761178dbdb6b544f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/f9cc1f52b5a463062251d666761178dbdb6b544f",
|
||||
"reference": "f9cc1f52b5a463062251d666761178dbdb6b544f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"dasprid/enum": "^1.0.3",
|
||||
"ext-iconv": "*",
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phly/keep-a-changelog": "^2.12",
|
||||
"phpunit/phpunit": "^10.5.11 || 11.0.4",
|
||||
"spatie/phpunit-snapshot-assertions": "^5.1.5",
|
||||
"squizlabs/php_codesniffer": "^3.9"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-imagick": "to generate QR code images"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"BaconQrCode\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-2-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ben Scholzen 'DASPRiD'",
|
||||
"email": "mail@dasprids.de",
|
||||
"homepage": "https://dasprids.de/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "BaconQrCode is a QR code generator for PHP.",
|
||||
"homepage": "https://github.com/Bacon/BaconQrCode",
|
||||
"support": {
|
||||
"issues": "https://github.com/Bacon/BaconQrCode/issues",
|
||||
"source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.1"
|
||||
},
|
||||
"time": "2024-10-01T13:55:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dasprid/enum",
|
||||
"version": "1.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/DASPRiD/Enum.git",
|
||||
"reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/DASPRiD/Enum/zipball/8dfd07c6d2cf31c8da90c53b83c026c7696dda90",
|
||||
"reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1 <9.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11",
|
||||
"squizlabs/php_codesniffer": "*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"DASPRiD\\Enum\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-2-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ben Scholzen 'DASPRiD'",
|
||||
"email": "mail@dasprids.de",
|
||||
"homepage": "https://dasprids.de/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "PHP 7.1 enum implementation",
|
||||
"keywords": [
|
||||
"enum",
|
||||
"map"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/DASPRiD/Enum/issues",
|
||||
"source": "https://github.com/DASPRiD/Enum/tree/1.0.6"
|
||||
},
|
||||
"time": "2024-08-09T14:30:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/constant_time_encoding",
|
||||
"version": "v3.0.0",
|
||||
|
@ -123,6 +227,87 @@
|
|||
},
|
||||
"time": "2020-10-15T08:29:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpmailer/phpmailer",
|
||||
"version": "v6.9.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPMailer/PHPMailer.git",
|
||||
"reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2f5c94fe7493efc213f643c23b1b1c249d40f47e",
|
||||
"reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-ctype": "*",
|
||||
"ext-filter": "*",
|
||||
"ext-hash": "*",
|
||||
"php": ">=5.5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
|
||||
"doctrine/annotations": "^1.2.6 || ^1.13.3",
|
||||
"php-parallel-lint/php-console-highlighter": "^1.0.0",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.3.2",
|
||||
"phpcompatibility/php-compatibility": "^9.3.5",
|
||||
"roave/security-advisories": "dev-latest",
|
||||
"squizlabs/php_codesniffer": "^3.7.2",
|
||||
"yoast/phpunit-polyfills": "^1.0.4"
|
||||
},
|
||||
"suggest": {
|
||||
"decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
|
||||
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
|
||||
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
|
||||
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
|
||||
"hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
|
||||
"league/oauth2-google": "Needed for Google XOAUTH2 authentication",
|
||||
"psr/log": "For optional PSR-3 debug logging",
|
||||
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
|
||||
"thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPMailer\\PHPMailer\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-2.1-only"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Marcus Bointon",
|
||||
"email": "phpmailer@synchromedia.co.uk"
|
||||
},
|
||||
{
|
||||
"name": "Jim Jagielski",
|
||||
"email": "jimjag@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Andy Prevost",
|
||||
"email": "codeworxtech@users.sourceforge.net"
|
||||
},
|
||||
{
|
||||
"name": "Brent R. Matzelle"
|
||||
}
|
||||
],
|
||||
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
|
||||
"support": {
|
||||
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
|
||||
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/Synchro",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-24T18:04:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpseclib/phpseclib",
|
||||
"version": "3.0.43",
|
||||
|
@ -232,15 +417,67 @@
|
|||
}
|
||||
],
|
||||
"time": "2024-12-14T21:12:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pragmarx/google2fa",
|
||||
"version": "v8.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/antonioribeiro/google2fa.git",
|
||||
"reference": "6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad",
|
||||
"reference": "6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"paragonie/constant_time_encoding": "^1.0|^2.0|^3.0",
|
||||
"php": "^7.1|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.9",
|
||||
"phpunit/phpunit": "^7.5.15|^8.5|^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PragmaRX\\Google2FA\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Antonio Carlos Ribeiro",
|
||||
"email": "acr@antoniocarlosribeiro.com",
|
||||
"role": "Creator & Designer"
|
||||
}
|
||||
],
|
||||
"description": "A One Time Password Authentication package, compatible with Google Authenticator.",
|
||||
"keywords": [
|
||||
"2fa",
|
||||
"Authentication",
|
||||
"Two Factor Authentication",
|
||||
"google2fa"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/antonioribeiro/google2fa/issues",
|
||||
"source": "https://github.com/antonioribeiro/google2fa/tree/v8.0.3"
|
||||
},
|
||||
"time": "2024-09-05T11:56:40+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"stability-flags": {},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.3.0"
|
||||
"platform": {},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
|
|
@ -20,6 +20,17 @@ $cryptkey = "a-random-string";
|
|||
// Optional settings
|
||||
//
|
||||
|
||||
// Background Node Updates - If you want the node data to automatically update every hour set this to true, if you want to manually refresh the nodes only set this to false.
|
||||
$bgupdate = true;
|
||||
|
||||
// SMTP Settings
|
||||
//$smtp_server = "localhost";
|
||||
//$smtp_port = "587";
|
||||
//$smtp_user = "noreply@example.com";
|
||||
//$smtp_password = "password";
|
||||
//$smtp_tls = true;
|
||||
//$smtp_sender = "noreply@example.com";
|
||||
|
||||
// Cloudflare Turnstile Keys (https://developers.cloudflare.com/turnstile/)
|
||||
//$sitekey = "";
|
||||
//$secretkey = "";
|
24
dbBackup.php
Normal file
24
dbBackup.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
require_once('config.php');
|
||||
$backup_file_path = $db_file_path.".bak";
|
||||
|
||||
try {
|
||||
$conn = new SQLite3($db_file_path, SQLITE3_OPEN_READONLY);
|
||||
$backup_db = new SQLite3($backup_file_path);
|
||||
$result = $conn->backup($backup_db);
|
||||
if($result) {
|
||||
updateLastRunTime('dbBackup.php');
|
||||
return;
|
||||
} else {
|
||||
error_log("Error creating DB backup: ". $conn->lastErrorMsg());
|
||||
return;
|
||||
}
|
||||
$conn->close();
|
||||
$backup_db->close();
|
||||
} catch (Exception $e) {
|
||||
error_log("Error creating DB backup: ". $e->getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
?>
|
BIN
favicon.ico
BIN
favicon.ico
Binary file not shown.
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
@ -2,7 +2,7 @@
|
|||
<div class="container">
|
||||
<p>Powered by <a href="https://github.com/KuJoe/kontrolvm" target="_blank">KontrolVM v<?php echo KONTROLVM_VERSION; ?></a></p>
|
||||
<ul class="social-links">
|
||||
<li><a href="#" target="_blank"><i class="fab fa-github"></i> GitHub</a></li>
|
||||
<!--<li><a href="#" target="_blank"><i class="fab fa-github"></i> GitHub</a></li>
|
||||
<li><a href="#" target="_blank"><i class="fab fa-mastodon"></i> Mastodon</a></li>
|
||||
<li><a href="#" target="_blank"><i class="fab fa-linkedin"></i> LinkedIn</a></li>
|
||||
<li>
|
||||
|
@ -14,7 +14,7 @@
|
|||
</label>
|
||||
</div>
|
||||
</li>
|
||||
<i class="fa-solid fa-moon darkmode"></i>
|
||||
<i class="fa-solid fa-moon darkmode"></i>-->
|
||||
</ul>
|
||||
</div>
|
||||
</footer>
|
2021
functions.php
2021
functions.php
File diff suppressed because it is too large
Load diff
|
@ -5,12 +5,9 @@ define('AmAllowed', TRUE);
|
|||
require_once('functions.php');
|
||||
|
||||
$vmname = $_POST['vmname'];
|
||||
$ipaddr = $_POST['nodeip'];
|
||||
$sshport = $_POST['nodeport'];
|
||||
$sshuser = $_POST['nodeuser'];
|
||||
$sshkey = $_POST['nodepw'];
|
||||
$node_id = $_POST['node_id'];
|
||||
|
||||
$state = getVMState($vmname,$ipaddr,$sshport,$sshuser,$sshkey);
|
||||
$state = getVMState($vmname,$node_id);
|
||||
echo $state;
|
||||
|
||||
?>
|
|
@ -2,6 +2,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KontrolVM</title>
|
||||
<link rel="icon" href="favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="assets/fonts.css">
|
||||
<link rel="stylesheet" href="assets/style.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
|
|
73
home.php
73
home.php
|
@ -6,30 +6,43 @@ header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
|||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
if(isset($_SESSION['mfa_required']) && $_SESSION['mfa_required']) {
|
||||
header("Location: logout.php");
|
||||
exit;
|
||||
}
|
||||
if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
} else {
|
||||
define('AmAllowed', TRUE);
|
||||
require_once('config.php');
|
||||
require_once('functions.php');
|
||||
$loggedin_id = $_SESSION['staff_id'];
|
||||
$loggedin_id = (int)$_SESSION['staff_id'];
|
||||
$myrole = (int)$_SESSION["staff_role"];
|
||||
$chkActive = checkActive($loggedin_id);
|
||||
$chkLocked = checkLockedOut($loggedin_id);
|
||||
if($chkLocked == true OR $chkActive == false) {
|
||||
header("Location: logout.php");
|
||||
exit;
|
||||
}
|
||||
if(isset($_GET['s'])) {
|
||||
if($_GET['s'] == '99') {
|
||||
$error = "Account does not access to fuction.";
|
||||
}
|
||||
}
|
||||
}
|
||||
$token = getCSRFToken();
|
||||
|
||||
$script_name = 'updateNodes.php';
|
||||
$last_run_time = getLastRunTime($script_name);
|
||||
if (!$last_run_time || time() - $last_run_time >= 3600) {
|
||||
$last_run_time = getLastRunTime('refreshNodes.php');
|
||||
$script_name = 'dbBackup.php';
|
||||
$last_backup = getLastRunTime($script_name);
|
||||
if(!$last_backup || time() - $last_backup >= 86400) {
|
||||
include($script_name);
|
||||
updateLastRunTime($script_name);
|
||||
}
|
||||
|
||||
if(checkVersion() === false) {
|
||||
$newVersion = '<div class="admin-update-banner" id="updateBanner">A new version is available! <a href="update.php">Update Now</a><span class="close-button" onclick="closeBanner()">✕</span></div>';
|
||||
} else {
|
||||
$newVersion = NULL;
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
@ -43,17 +56,22 @@ if (!$last_run_time || time() - $last_run_time >= 3600) {
|
|||
<label class="logo"><a href="index.php"><img src="assets/logo.png" alt="KontrolVM Logo"></a></label>
|
||||
<ul>
|
||||
<li><a class="active" href="index.php">Dashboard</a></li>
|
||||
<li><a href="nodes.php">Nodes</a></li>
|
||||
<li><a href="vms.php">VMs</a></li>
|
||||
<li><a href="users.php">Users</a></li>
|
||||
<?php if(in_array($myrole, ['2', '9'])) { ?> <li><a href="clusters.php">Infrastructure</a></li> <?php } ?>
|
||||
<?php if(in_array($myrole, ['1', '9'])) { ?> <li><a href="users.php">Users</a></li> <?php } ?>
|
||||
<li><a href="settings.php">Settings</a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo $_SESSION["username"]; ?></a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo htmlspecialchars($_SESSION["username"]); ?></a></li>
|
||||
<li><a href="logout.php"><i class="fa fa-sign-out" aria-hidden="true"></i></a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="container">
|
||||
<?php echo $newVersion; ?>
|
||||
<h1>Cluster Overview</h1>
|
||||
|
||||
<?php if(isset($success)) { ?>
|
||||
<div class="success-message"><?php echo $success; ?></div>
|
||||
<?php } ?>
|
||||
<?php if(isset($error)) { ?>
|
||||
<div class="error-message"><?php echo $error; ?></div>
|
||||
<?php } ?>
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h2>Nodes</h2>
|
||||
|
@ -76,29 +94,20 @@ if (!$last_run_time || time() - $last_run_time >= 3600) {
|
|||
<div class="value"><?php echo getTotalDisk(); ?>GB</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<p style="text-align:center;font-size:12px;">Last refresh: <?php echo date('m/j/Y @ g:i:s A',$last_run_time); ?></p>
|
||||
</div>
|
||||
<?php include('footer.php'); ?>
|
||||
<script>
|
||||
// JavaScript code to handle theme switching (from previous responses)
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const body = document.body;
|
||||
|
||||
// Load the user's preferred theme from localStorage
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
body.classList.add('dark-mode');
|
||||
themeToggle.checked = true;
|
||||
// Update banner code
|
||||
if (sessionStorage.getItem('updateBannerDismissed')) {
|
||||
document.getElementById('updateBanner').style.display = 'none';
|
||||
}
|
||||
|
||||
function closeBanner() {
|
||||
document.getElementById('updateBanner').style.display = 'none';
|
||||
sessionStorage.setItem('updateBannerDismissed', 'true');
|
||||
}
|
||||
|
||||
themeToggle.addEventListener('change', () => {
|
||||
if (themeToggle.checked) {
|
||||
body.classList.add('dark-mode');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
body.classList.remove('dark-mode');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
</body>
|
||||
|
|
31
index.php
31
index.php
|
@ -2,11 +2,16 @@
|
|||
/** KontrolVM By KuJoe (https://github.com/KuJoe/kontrolvm) **/
|
||||
|
||||
session_start();
|
||||
if (isset($_SESSION["loggedin"]) AND $_SESSION["loggedin"] == true) {
|
||||
if(isset($_SESSION["loggedin"]) AND $_SESSION["loggedin"] == true) {
|
||||
define('AmAllowed', TRUE);
|
||||
header("Location: home.php");
|
||||
}
|
||||
require_once('config.php');
|
||||
$configFile = __DIR__ . '/config.php';
|
||||
if (file_exists($configFile)) {
|
||||
require_once('config.php');
|
||||
} else {
|
||||
header("Location: install.php");
|
||||
}
|
||||
if(isset($_GET['e'])) {
|
||||
if($_GET['e'] == '0') {
|
||||
$error = 'Incorrect username or password.';
|
||||
|
@ -14,6 +19,10 @@ if(isset($_GET['e'])) {
|
|||
$error = "CAPTCHA failed.";
|
||||
} elseif($_GET['e'] == '2') {
|
||||
$error = "User is locked out or not active.";
|
||||
} elseif($_GET['e'] == '3') {
|
||||
$error = "MFA verification failed.";
|
||||
} elseif($_GET['e'] == '4') {
|
||||
$success = "Your password has been reset and a new one has been e-mailed to you.";
|
||||
} else {
|
||||
$error = "Login failed.";
|
||||
}
|
||||
|
@ -77,6 +86,16 @@ if(isset($_GET['e'])) {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
color: #155724;
|
||||
background-color: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
padding: 10px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #dc3545;
|
||||
background-color: #f8d7da;
|
||||
|
@ -93,7 +112,10 @@ if(isset($_GET['e'])) {
|
|||
<img src="assets/logo.png" alt="KontrolVM Logo" style="display:block;margin:0 auto;" />
|
||||
<br />
|
||||
<br />
|
||||
<?php if (isset($error)) { ?>
|
||||
<?php if(isset($success)) { ?>
|
||||
<div class="success-message"><?php echo $success; ?></div>
|
||||
<?php } ?>
|
||||
<?php if(isset($error)) { ?>
|
||||
<div class="error-message"><?php echo $error; ?></div>
|
||||
<?php } ?>
|
||||
<form action="auth.php" method="post">
|
||||
|
@ -111,7 +133,8 @@ if(isset($_GET['e'])) {
|
|||
<?php } ?>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
<br /><br />
|
||||
<p style="text-align:center;"><a href="password_reset.php">Reset Password</a></p>
|
||||
<br/ >
|
||||
<p style="text-align:center;font-size:0.9em;">Powered by <a href="https://github.com/KuJoe/kontrolvm" target="_blank">KontrolVM</a></p>
|
||||
</div>
|
||||
</body>
|
||||
|
|
152
install.php
152
install.php
|
@ -5,14 +5,49 @@ use phpseclib3\Crypt\EC;
|
|||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$filename = 'LOCKED';
|
||||
if (file_exists($filename)) {
|
||||
if(file_exists($filename)) {
|
||||
die("The directory is locked. Please delete the LOCKED file if you are sure you need to run the install.php file (this might overwrite existing data in the database if it exists).");
|
||||
}
|
||||
|
||||
if (file_exists('config.php')) {
|
||||
require_once('config.php');
|
||||
$exampleFile = __DIR__ . '/config.php.example';
|
||||
$configFile = __DIR__ . '/config.php';
|
||||
if (file_exists($configFile)) {
|
||||
require_once('config.php');
|
||||
} else {
|
||||
die("The config.php file does not exist. Please upload it to the same folder as this file.");
|
||||
if (file_exists($exampleFile)) {
|
||||
if (rename($exampleFile, $configFile)) {
|
||||
require_once('config.php');
|
||||
} else {
|
||||
die("Failed to rename config.php.example to config.php.");
|
||||
}
|
||||
} else {
|
||||
die("config.php does not exist and config.php.example does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
function generateRandomString($length = 16) {
|
||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$charactersLength = strlen($characters);
|
||||
$randomString = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$randomString .= $characters[rand(0, $charactersLength - 1)];
|
||||
}
|
||||
return $randomString;
|
||||
}
|
||||
|
||||
function addSetting($name,$value) {
|
||||
$stmt = $conn->prepare("INSERT INTO settings (setting_name, setting_value) VALUES (:setting_name, :setting_value)");
|
||||
$stmt->bindValue(':setting_name', "$name", SQLITE3_TEXT);
|
||||
$stmt->bindValue(':setting_value', "$value", SQLITE3_TEXT);
|
||||
$stmt->execute();
|
||||
return true;
|
||||
}
|
||||
|
||||
$newEncrypt = generateRandomString('32');
|
||||
$fileContent = file_get_contents($configFile);
|
||||
$updatedContent = str_replace($cryptkey, $newEncrypt, $fileContent);
|
||||
if (file_put_contents($configFile, $updatedContent) === false) {
|
||||
die("Error: Could not write to file: $configFile");
|
||||
}
|
||||
|
||||
//Generate the super user for later.
|
||||
|
@ -29,11 +64,11 @@ try {
|
|||
node_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
hostname TEXT NOT NULL UNIQUE,
|
||||
ipaddr TEXT NOT NULL,
|
||||
cluster INTEGER,
|
||||
cpu_cores INTEGER,
|
||||
total_memory INTEGER,
|
||||
disk_space INTEGER,
|
||||
sshport INTEGER,
|
||||
loc TEXT,
|
||||
sshuser TEXT,
|
||||
sshkey TEXT,
|
||||
uptime TEXT,
|
||||
|
@ -55,12 +90,12 @@ try {
|
|||
last_updated DATETIME
|
||||
)",
|
||||
'last_run' => "CREATE TABLE IF NOT EXISTS last_run (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
run_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
script_name TEXT NOT NULL,
|
||||
last_run_time INTEGER
|
||||
)",
|
||||
'ostemplates' => "CREATE TABLE IF NOT EXISTS ostemplates (
|
||||
templateid INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
template_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
filename TEXT NOT NULL,
|
||||
friendlyname TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
|
@ -69,25 +104,25 @@ try {
|
|||
added DATETIME
|
||||
)",
|
||||
'ipv4' => "CREATE TABLE IF NOT EXISTS ipv4 (
|
||||
ipid INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ip_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ipaddress TEXT NOT NULL,
|
||||
gwip TEXT NOT NULL,
|
||||
bw INTEGER,
|
||||
vmid INTEGER,
|
||||
reserved INTEGER,
|
||||
node INTEGER,
|
||||
loc TEXT,
|
||||
cluster INTEGER,
|
||||
notes TEXT,
|
||||
status INTEGER,
|
||||
last_updated DATETIME
|
||||
)",
|
||||
'ipv6' => "CREATE TABLE IF NOT EXISTS ipv6 (
|
||||
ipid INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ip_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ipaddress TEXT NOT NULL,
|
||||
gwip TEXT NOT NULL,
|
||||
bw INTEGER,
|
||||
vmid INTEGER,
|
||||
reserved INTEGER,
|
||||
node INTEGER,
|
||||
loc TEXT,
|
||||
cluster INTEGER,
|
||||
notes TEXT,
|
||||
status INTEGER,
|
||||
last_updated DATETIME
|
||||
|
@ -95,13 +130,15 @@ try {
|
|||
'staff' => "CREATE TABLE staff (
|
||||
staff_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
staff_username TEXT NOT NULL DEFAULT '',
|
||||
staff_email TEXT NOT NULL DEFAULT '',
|
||||
staff_email TEXT,
|
||||
staff_password TEXT NOT NULL DEFAULT 0,
|
||||
staff_pwreset TEXT,
|
||||
staff_active INTEGER NOT NULL DEFAULT 0,
|
||||
staff_rememberme_token TEXT NOT NULL DEFAULT 0,
|
||||
staff_mfa TEXT NOT NULL DEFAULT '',
|
||||
staff_ip TEXT NOT NULL DEFAULT 0,
|
||||
staff_lastlogin TEXT NULL DEFAULT NULL,
|
||||
staff_rememberme_token TEXT,
|
||||
staff_mfa TEXT,
|
||||
staff_role INTEGER,
|
||||
staff_ip TEXT,
|
||||
staff_lastlogin TEXT,
|
||||
staff_failed_logins INTEGER NOT NULL DEFAULT 0,
|
||||
staff_locked DATETIME NOT NULL DEFAULT '1970-01-01 00:00:01'
|
||||
)",
|
||||
|
@ -110,25 +147,16 @@ try {
|
|||
node_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
hostname TEXT NOT NULL,
|
||||
ip_address TEXT,
|
||||
status INTEGER,
|
||||
protected INTEGER,
|
||||
loc TEXT,
|
||||
cluster INTEGER,
|
||||
cpu_cores INTEGER,
|
||||
memory INTEGER,
|
||||
disk1 TEXT,
|
||||
disk_space1 INTEGER,
|
||||
disk2 TEXT,
|
||||
disk_space2 INTEGER,
|
||||
disk3 TEXT,
|
||||
disk_space3 INTEGER,
|
||||
disk4 TEXT,
|
||||
disk_space4 INTEGER,
|
||||
memory INTEGER,
|
||||
ipv4 INTEGER,
|
||||
ipv6 INTEGER,
|
||||
mac_address TEXT,
|
||||
os_template TEXT,
|
||||
os_type TEXT,
|
||||
mac_address TEXT,
|
||||
nic INTEGER,
|
||||
iow INTEGER,
|
||||
notes TEXT,
|
||||
|
@ -143,25 +171,79 @@ try {
|
|||
created_at DATETIME,
|
||||
last_updated DATETIME
|
||||
)",
|
||||
'disks' => "CREATE TABLE IF NOT EXISTS disks (
|
||||
disk_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
disk_name TEXT NOT NULL,
|
||||
disk_size INTEGER,
|
||||
vm_id INTEGER,
|
||||
node_id INTEGER,
|
||||
last_updated DATETIME
|
||||
)",
|
||||
'clusters' => "CREATE TABLE IF NOT EXISTS clusters (
|
||||
clusterid INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
loc TEXT NOT NULL,
|
||||
cluster_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
friendlyname TEXT NOT NULL,
|
||||
notes TEXT,
|
||||
deployment INTEGER,
|
||||
status INTEGER,
|
||||
last_updated DATETIME
|
||||
)",
|
||||
'settings' => "CREATE TABLE IF NOT EXISTS settings (
|
||||
setting_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
setting_name TEXT NOT NULL,
|
||||
setting_value TEXT NOT NULL
|
||||
)",
|
||||
'logs' => "CREATE TABLE IF NOT EXISTS logs (
|
||||
log_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
log_message TEXT NOT NULL,
|
||||
staff_id INTEGER,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)",
|
||||
'backups' => "CREATE TABLE IF NOT EXISTS backups (
|
||||
backup_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
backup_name TEXT NOT NULL,
|
||||
backup_size INTEGER,
|
||||
vm_id INTEGER,
|
||||
node_id INTEGER,
|
||||
status INTEGER,
|
||||
created_at DATETIME
|
||||
)",
|
||||
'nics' => "CREATE TABLE IF NOT EXISTS nics (
|
||||
nic_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
nic_name TEXT NOT NULL,
|
||||
mac_address TEXT,
|
||||
vm_id INTEGER NOT NULL,
|
||||
node_id INTEGER NOT NULL,
|
||||
last_updated DATETIME
|
||||
)",
|
||||
'networks' => "CREATE TABLE IF NOT EXISTS networks (
|
||||
net_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
net_name TEXT NOT NULL,
|
||||
node_id INTEGER NOT NULL,
|
||||
last_updated DATETIME
|
||||
)"
|
||||
];
|
||||
foreach ($tables as $name => $sql) {
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->execute();
|
||||
}
|
||||
$stmt = $conn->prepare("INSERT INTO staff (staff_username, staff_password, staff_active) VALUES (:username, :password, :active)");
|
||||
// Create Super Admin account
|
||||
$stmt = $conn->prepare("INSERT INTO staff (staff_username, staff_password, staff_active, staff_role) VALUES (:username, :password, :active, :role)");
|
||||
$stmt->bindValue(':username', "$username", SQLITE3_TEXT);
|
||||
$stmt->bindValue(':password', "$hashedPassword", SQLITE3_TEXT);
|
||||
$stmt->bindValue(':active', '1', SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':role', '9', SQLITE3_INTEGER);
|
||||
$stmt->execute();
|
||||
|
||||
//// Populate default settings
|
||||
//$setting = [
|
||||
//'bgupdate' => "true",
|
||||
//'' => "",
|
||||
//'' => "",
|
||||
//];
|
||||
//foreach ($setting as $name => $value) {
|
||||
// addSetting($name,$value);
|
||||
//}
|
||||
|
||||
//Create the SSH key
|
||||
$privateKey = EC::createKey('Ed25519');
|
||||
$publicKey = $privateKey->getPublicKey();
|
||||
|
@ -176,7 +258,7 @@ try {
|
|||
$error = $e->getMessage();
|
||||
}
|
||||
$file = fopen('LOCKED', 'w');
|
||||
if ($file == false) {
|
||||
if($file == false) {
|
||||
$lock = "Unable to lock the directory to prevent the install.php script from being run again. Either manually create a file named <strong>LOCKED</strong> in this directory or delete the install.php to be safe.";
|
||||
} else {
|
||||
$lock = "Lock file created to prevent the install.php file from being run again. You can delete the install.php file just to safe.";
|
||||
|
@ -234,7 +316,7 @@ if ($file == false) {
|
|||
<br />
|
||||
<div class="error-message"><?php echo $error; ?></div>
|
||||
<?php } ?>
|
||||
<?php if (isset($success)) { ?>
|
||||
<?php if(isset($success)) { ?>
|
||||
<h2 style="color:green;">Install Success</h2>
|
||||
<br />
|
||||
<div><?php echo $success; ?></div>
|
||||
|
|
119
ipv4.php
119
ipv4.php
|
@ -2,26 +2,27 @@
|
|||
/** KontrolVM By KuJoe (https://github.com/KuJoe/kontrolvm) **/
|
||||
|
||||
session_start();
|
||||
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
} else {
|
||||
if (isset($_GET['s']) AND $_GET['s'] == '1') {
|
||||
if(isset($_GET['s']) AND $_GET['s'] == '1') {
|
||||
$success = "IP added successfully.";
|
||||
}
|
||||
if (isset($_GET['s']) AND $_GET['s'] == '2') {
|
||||
if(isset($_GET['s']) AND $_GET['s'] == '2') {
|
||||
$success = "IP deleted successfully.";
|
||||
}
|
||||
if (isset($_GET['s']) AND $_GET['s'] == '3') {
|
||||
$success = "IP reserved successfully.";
|
||||
if(isset($_GET['s']) AND $_GET['s'] == '3') {
|
||||
$success = "IP enabled successfully.";
|
||||
}
|
||||
if (isset($_GET['s']) AND $_GET['s'] == '4') {
|
||||
$success = "IP unreserved successfully.";
|
||||
if(isset($_GET['s']) AND $_GET['s'] == '4') {
|
||||
$success = "IP disabled successfully.";
|
||||
}
|
||||
define('AmAllowed', TRUE);
|
||||
require_once('config.php');
|
||||
require_once('functions.php');
|
||||
$loggedin_id = $_SESSION['staff_id'];
|
||||
$loggedin_id = (int)$_SESSION['staff_id'];
|
||||
$myrole = (int)$_SESSION["staff_role"];
|
||||
$chkActive = checkActive($loggedin_id);
|
||||
$chkLocked = checkLockedOut($loggedin_id);
|
||||
if($chkLocked == true OR $chkActive == false) {
|
||||
|
@ -29,48 +30,48 @@ if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
|||
exit;
|
||||
}
|
||||
}
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
if($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$token = $_POST["csrf_token"];
|
||||
if (validateCSRFToken($token)) {
|
||||
if (isset($_POST['add_ip'])) {
|
||||
if(validateCSRFToken($token)) {
|
||||
if(isset($_POST['add_ip'])) {
|
||||
$ipaddress = $_POST["ipaddress"];
|
||||
$gwip = $_POST["gwip"];
|
||||
$loc = $_POST["loc"];
|
||||
$result = addIPs($ipaddress, $gwip, $loc);
|
||||
$cluster = $_POST["cluster"];
|
||||
$result = addIPs($loggedin_id,$ipaddress,$gwip,$cluster);
|
||||
if($result === true) {
|
||||
header("Location: ipv4.php?s=1");
|
||||
} else {
|
||||
$error = "IP add failed: ".$result;
|
||||
}
|
||||
}
|
||||
if (isset($_POST['delete_ip'])) {
|
||||
$idToChange = $_POST['ipid'];
|
||||
if(isset($_POST['delete_ip'])) {
|
||||
$idToChange = $_POST['ip_id'];
|
||||
$ipToChange = $_POST['ipaddress'];
|
||||
$result = deleteIP($idToChange,$ipToChange);
|
||||
$result = deleteIP($loggedin_id,$idToChange,$ipToChange);
|
||||
if($result === true) {
|
||||
header("Location: ipv4.php?s=2");
|
||||
} else {
|
||||
$error = "IP deletion failed: ".$result;
|
||||
}
|
||||
}
|
||||
if (isset($_POST['reserve_ip'])) {
|
||||
$idToChange = $_POST['ipid'];
|
||||
if(isset($_POST['enable_ip'])) {
|
||||
$idToChange = $_POST['ip_id'];
|
||||
$ipToChange = $_POST['ipaddress'];
|
||||
$result = reserveIP($idToChange,$ipToChange);
|
||||
$result = enableIP($loggedin_id,$idToChange,$ipToChange);
|
||||
if($result === true) {
|
||||
header("Location: ipv4.php?s=3");
|
||||
} else {
|
||||
$error = "IP reservation failed: ".$result;
|
||||
$error = "IP enable failed: ".$result;
|
||||
}
|
||||
}
|
||||
if (isset($_POST['unreserve_ip'])) {
|
||||
$idToChange = $_POST['ipid'];
|
||||
if(isset($_POST['disable_ip'])) {
|
||||
$idToChange = $_POST['ip_id'];
|
||||
$ipToChange = $_POST['ipaddress'];
|
||||
$result = unreserveIP($idToChange,$ipToChange);
|
||||
$result = disableIP($loggedin_id,$idToChange,$ipToChange);
|
||||
if($result === true) {
|
||||
header("Location: ipv4.php?s=4");
|
||||
} else {
|
||||
$error = "IP unreserve failed: ".$result;
|
||||
$error = "IP disable failed: ".$result;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -98,20 +99,19 @@ $clusters = getClusters('1');
|
|||
<label class="logo"><a href="index.php"><img src="assets/logo.png" alt="KontrolVM Logo"></a></label>
|
||||
<ul>
|
||||
<li><a href="index.php">Dashboard</a></li>
|
||||
<li><a href="nodes.php">Nodes</a></li>
|
||||
<li><a href="vms.php">VMs</a></li>
|
||||
<li><a href="users.php">Users</a></li>
|
||||
<?php if(in_array($myrole, ['2', '9'])) { ?> <li><a href="clusters.php">Infrastructure</a></li> <?php } ?>
|
||||
<?php if(in_array($myrole, ['1', '9'])) { ?> <li><a href="users.php">Users</a></li> <?php } ?>
|
||||
<li><a class="active" href="settings.php">Settings</a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo $_SESSION["username"]; ?></a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo htmlspecialchars($_SESSION["username"]); ?></a></li>
|
||||
<li><a href="logout.php"><i class="fa fa-sign-out" aria-hidden="true"></i></a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<ul class="submenu">
|
||||
<li><a href="settings.php">General</a></li>
|
||||
<li><a href="clusters.php">Clusters</a></li>
|
||||
<li><a href="isos.php">ISOs</a></li>
|
||||
<li><a class="active" href="ipv4.php">IPv4 Addresses</a></li>
|
||||
<li><a href="ipv6.php">IPv6 Addresses</a></li>
|
||||
<li><a href="logs.php">Logs</a></li>
|
||||
</ul>
|
||||
<div class="container">
|
||||
<p style="float:right;"><button id="addBtn" class="stylish-button"><i class="fa-solid fa-square-plus"></i> ADD IP</button></p>
|
||||
|
@ -126,10 +126,10 @@ $clusters = getClusters('1');
|
|||
<input type="text" id="ipaddress" name="ipaddress" required><br><br>
|
||||
<label for="gwip">Gateway:</label>
|
||||
<input type="text" id="gwip" name="gwip" required><br><br>
|
||||
<label for="loc">Cluster:</label>
|
||||
<select name="loc" style="text-align:center;">
|
||||
<label for="cluster">Cluster:</label>
|
||||
<select name="cluster" style="text-align:center;">
|
||||
<?php foreach ($clusters as $cluster):?>
|
||||
<option value="<?php echo htmlspecialchars($cluster['loc']);?>">
|
||||
<option value="<?php echo htmlspecialchars($cluster['cluster_id']);?>">
|
||||
<?php echo htmlspecialchars($cluster['friendlyname']);?>
|
||||
</option>
|
||||
<?php endforeach;?>
|
||||
|
@ -139,10 +139,10 @@ $clusters = getClusters('1');
|
|||
</div>
|
||||
</div>
|
||||
<h1>IPv4 Addresses</h1>
|
||||
<?php if (isset($success)) { ?>
|
||||
<?php if(isset($success)) { ?>
|
||||
<div class="success-message"><?php echo $success; ?></div>
|
||||
<?php } ?>
|
||||
<?php if (isset($error)) { ?>
|
||||
<?php if(isset($error)) { ?>
|
||||
<div class="error-message"><?php echo $error; ?></div>
|
||||
<?php } ?>
|
||||
<div class="table-container" style="max-width:1500px;">
|
||||
|
@ -156,42 +156,45 @@ $clusters = getClusters('1');
|
|||
<th>Cluster</th>
|
||||
<th>Node</th>
|
||||
<th>Available</th>
|
||||
<th>Notes</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
foreach ($ips as $ip) {
|
||||
$ipid = $ip['ipid'];
|
||||
$ip_id = $ip['ip_id'];
|
||||
echo '<tr>';
|
||||
echo "<td class='tname'>" . $ip['ipaddress'] . "</td>";
|
||||
echo "<td style='font-size:small;'>" . $ip['vmid'] . "</td>";
|
||||
if($ip['vmid'] == "0") {
|
||||
echo "<td style='font-size:small;'>" . $ip['vmid'] . "</td>";
|
||||
} else {
|
||||
echo "<td style='font-size:small;'><a href='vm.php?id=" . $ip['vmid'] . "'>" . $ip['vmid'] . "</a></td>";
|
||||
}
|
||||
echo "<td style='font-size:small;'>" . $ip['gwip'] . "</td>";
|
||||
echo "<td style='font-size:small;'>" . getClusterName($ip['loc']) . "</td>";
|
||||
echo "<td style='font-size:small;'>" . getClusterName($ip['cluster']) . "</td>";
|
||||
echo "<td style='font-size:small;'>" . getNodeName($ip['node']) . "</td>";
|
||||
echo "<td><span class='ticon' style='padding-right:4px;'>Available: </span>";
|
||||
if ($ip['reserved'] == "0") {
|
||||
echo "<img src='assets/1.png' alt='Available'>";
|
||||
} else {
|
||||
if($ip['status'] == "0") {
|
||||
echo "<img src='assets/0.png' alt='Unavailable'>";
|
||||
} else {
|
||||
echo "<img src='assets/1.png' alt='Available'>";
|
||||
}
|
||||
echo "</td>";
|
||||
echo "<td style='font-size:small;'>" . $ip['notes'] . "</td><td>";
|
||||
echo "<td>";
|
||||
echo '<form action="'.htmlspecialchars($_SERVER["PHP_SELF"]).'" method="post">
|
||||
<input type="hidden" name="csrf_token" value="'.$csrfToken.'">
|
||||
<input type="hidden" name="ipid" value="'.$ipid.'">
|
||||
<input type="hidden" name="ip_id" value="'.$ip_id.'">
|
||||
<input type="hidden" name="ipaddress" value="'.$ip['ipaddress'].'">
|
||||
<button type="submit" class="stylish-button" name="delete_ip">Delete</button>
|
||||
</form>';
|
||||
echo '<br /><form action="'.htmlspecialchars($_SERVER["PHP_SELF"]).'" method="post">
|
||||
<input type="hidden" name="csrf_token" value="'.$csrfToken.'">
|
||||
<input type="hidden" name="ipid" value="'.$ipid.'">
|
||||
<input type="hidden" name="ip_id" value="'.$ip_id.'">
|
||||
<input type="hidden" name="ipaddress" value="'.$ip['ipaddress'].'">';
|
||||
if ($ip['reserved'] == "1") {
|
||||
echo '<button type="submit" class="stylish-button" name="unreserve_ip">Enable</button>';
|
||||
if($ip['status'] == "0") {
|
||||
echo '<button type="submit" class="stylish-button" name="enable_ip">Enable</button>';
|
||||
} else {
|
||||
echo '<button type="submit" class="stylish-button" name="reserve_ip">Disable</button>';
|
||||
echo '<button type="submit" class="stylish-button" name="disable_ip">Disable</button>';
|
||||
}
|
||||
echo '</form>';
|
||||
echo '</td></tr>';
|
||||
|
@ -203,26 +206,6 @@ $clusters = getClusters('1');
|
|||
</div>
|
||||
<?php include('footer.php'); ?>
|
||||
<script>
|
||||
// JavaScript code to handle theme switching (from previous responses)
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const body = document.body;
|
||||
|
||||
// Load the user's preferred theme from localStorage
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
body.classList.add('dark-mode');
|
||||
themeToggle.checked = true;
|
||||
}
|
||||
|
||||
themeToggle.addEventListener('change', () => {
|
||||
if (themeToggle.checked) {
|
||||
body.classList.add('dark-mode');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
body.classList.remove('dark-mode');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
});
|
||||
// Get the modal
|
||||
var modal = document.getElementById("addModal");
|
||||
|
||||
|
@ -244,7 +227,7 @@ $clusters = getClusters('1');
|
|||
|
||||
// When the user clicks anywhere outside of the modal, close it
|
||||
window.onclick = function(event) {
|
||||
if (event.target == modal) {
|
||||
if(event.target == modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
|
121
ipv6.php
121
ipv6.php
|
@ -2,26 +2,27 @@
|
|||
/** KontrolVM By KuJoe (https://github.com/KuJoe/kontrolvm) **/
|
||||
|
||||
session_start();
|
||||
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
} else {
|
||||
if (isset($_GET['s']) AND $_GET['s'] == '1') {
|
||||
if(isset($_GET['s']) AND $_GET['s'] == '1') {
|
||||
$success = "IP added successfully.";
|
||||
}
|
||||
if (isset($_GET['s']) AND $_GET['s'] == '2') {
|
||||
if(isset($_GET['s']) AND $_GET['s'] == '2') {
|
||||
$success = "IP deleted successfully.";
|
||||
}
|
||||
if (isset($_GET['s']) AND $_GET['s'] == '3') {
|
||||
$success = "IP reserved successfully.";
|
||||
if(isset($_GET['s']) AND $_GET['s'] == '3') {
|
||||
$success = "IP enabled successfully.";
|
||||
}
|
||||
if (isset($_GET['s']) AND $_GET['s'] == '4') {
|
||||
$success = "IP unreserved successfully.";
|
||||
if(isset($_GET['s']) AND $_GET['s'] == '4') {
|
||||
$success = "IP disabled successfully.";
|
||||
}
|
||||
define('AmAllowed', TRUE);
|
||||
require_once('config.php');
|
||||
require_once('functions.php');
|
||||
$loggedin_id = $_SESSION['staff_id'];
|
||||
$loggedin_id = (int)$_SESSION['staff_id'];
|
||||
$myrole = (int)$_SESSION["staff_role"];
|
||||
$chkActive = checkActive($loggedin_id);
|
||||
$chkLocked = checkLockedOut($loggedin_id);
|
||||
if($chkLocked == true OR $chkActive == false) {
|
||||
|
@ -29,15 +30,15 @@ if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
|||
exit;
|
||||
}
|
||||
}
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
if($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$token = $_POST["csrf_token"];
|
||||
if (validateCSRFToken($token)) {
|
||||
if (isset($_POST['add_ip'])) {
|
||||
if(validateCSRFToken($token)) {
|
||||
if(isset($_POST['add_ip'])) {
|
||||
$ipaddress = $_POST["ipaddress"];
|
||||
if (strlen($ipaddress) === 19) {
|
||||
if(strlen($ipaddress) === 19) {
|
||||
$gwip = $_POST["gwip"];
|
||||
$loc = $_POST["loc"];
|
||||
$result = addIPs($ipaddress, $gwip, $loc);
|
||||
$cluster = $_POST["cluster"];
|
||||
$result = addIPs($loggedin_id,$ipaddress,$gwip,$cluster);
|
||||
if($result === true) {
|
||||
header("Location: ipv6.php?s=1");
|
||||
} else {
|
||||
|
@ -47,34 +48,34 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
|||
$error = "IPv6 subnet needs to be 19 characters in this format: <b>XXXX:XXXX:XXXX:XXXX</b>";
|
||||
}
|
||||
}
|
||||
if (isset($_POST['delete_ip'])) {
|
||||
$idToChange = $_POST['ipid'];
|
||||
if(isset($_POST['delete_ip'])) {
|
||||
$idToChange = $_POST['ip_id'];
|
||||
$ipToChange = $_POST['ipaddress'];
|
||||
$result = deleteIP($idToChange,$ipToChange);
|
||||
$result = deleteIP($loggedin_id,$idToChange,$ipToChange);
|
||||
if($result === true) {
|
||||
header("Location: ipv6.php?s=2");
|
||||
} else {
|
||||
$error = "IP deletion failed: ".$result;
|
||||
}
|
||||
}
|
||||
if (isset($_POST['reserve_ip'])) {
|
||||
$idToChange = $_POST['ipid'];
|
||||
if(isset($_POST['enable_ip'])) {
|
||||
$idToChange = $_POST['ip_id'];
|
||||
$ipToChange = $_POST['ipaddress'];
|
||||
$result = reserveIP($idToChange,$ipToChange);
|
||||
$result = enableIP($loggedin_id,$idToChange,$ipToChange);
|
||||
if($result === true) {
|
||||
header("Location: ipv6.php?s=3");
|
||||
} else {
|
||||
$error = "IP reservation failed: ".$result;
|
||||
$error = "IP enable failed: ".$result;
|
||||
}
|
||||
}
|
||||
if (isset($_POST['unreserve_ip'])) {
|
||||
$idToChange = $_POST['ipid'];
|
||||
if(isset($_POST['disable_ip'])) {
|
||||
$idToChange = $_POST['ip_id'];
|
||||
$ipToChange = $_POST['ipaddress'];
|
||||
$result = unreserveIP($idToChange,$ipToChange);
|
||||
$result = disableIP($loggedin_id,$idToChange,$ipToChange);
|
||||
if($result === true) {
|
||||
header("Location: ipv6.php?s=4");
|
||||
} else {
|
||||
$error = "IP unreserve failed: ".$result;
|
||||
$error = "IP disable failed: ".$result;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -101,20 +102,19 @@ $clusters = getClusters('1');
|
|||
<label class="logo"><a href="index.php"><img src="assets/logo.png" alt="KontrolVM Logo"></a></label>
|
||||
<ul>
|
||||
<li><a href="index.php">Dashboard</a></li>
|
||||
<li><a href="nodes.php">Nodes</a></li>
|
||||
<li><a href="vms.php">VMs</a></li>
|
||||
<li><a href="users.php">Users</a></li>
|
||||
<?php if(in_array($myrole, ['2', '9'])) { ?> <li><a href="clusters.php">Infrastructure</a></li> <?php } ?>
|
||||
<?php if(in_array($myrole, ['1', '9'])) { ?> <li><a href="users.php">Users</a></li> <?php } ?>
|
||||
<li><a class="active" href="settings.php">Settings</a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo $_SESSION["username"]; ?></a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo htmlspecialchars($_SESSION["username"]); ?></a></li>
|
||||
<li><a href="logout.php"><i class="fa fa-sign-out" aria-hidden="true"></i></a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<ul class="submenu">
|
||||
<li><a href="settings.php">General</a></li>
|
||||
<li><a href="clusters.php">Clusters</a></li>
|
||||
<li><a href="isos.php">ISOs</a></li>
|
||||
<li><a href="ipv4.php">IPv4 Addresses</a></li>
|
||||
<li><a class="active" href="ipv6.php">IPv6 Addresses</a></li>
|
||||
<li><a href="logs.php">Logs</a></li>
|
||||
</ul>
|
||||
<div class="container">
|
||||
<p style="float:right;"><button id="addBtn" class="stylish-button"><i class="fa-solid fa-square-plus"></i> ADD /64</button></p>
|
||||
|
@ -129,10 +129,10 @@ $clusters = getClusters('1');
|
|||
<input type="text" id="ipaddress" name="ipaddress" placeholder="XXXX:XXXX:XXXX:XXXX" maxlength="19" required><br><br>
|
||||
<label for="gwip">Gateway:</label>
|
||||
<input type="text" id="gwip" name="gwip" required><br><br>
|
||||
<label for="loc">Cluster:</label>
|
||||
<select name="loc" style="text-align:center;">
|
||||
<label for="cluster">Cluster:</label>
|
||||
<select name="cluster" style="text-align:center;">
|
||||
<?php foreach ($clusters as $cluster):?>
|
||||
<option value="<?php echo htmlspecialchars($cluster['loc']);?>">
|
||||
<option value="<?php echo htmlspecialchars($cluster['cluster_id']);?>">
|
||||
<?php echo htmlspecialchars($cluster['friendlyname']);?>
|
||||
</option>
|
||||
<?php endforeach;?>
|
||||
|
@ -142,10 +142,10 @@ $clusters = getClusters('1');
|
|||
</div>
|
||||
</div>
|
||||
<h1>IPv6 Addresses</h1>
|
||||
<?php if (isset($success)) { ?>
|
||||
<?php if(isset($success)) { ?>
|
||||
<div class="success-message"><?php echo $success; ?></div>
|
||||
<?php } ?>
|
||||
<?php if (isset($error)) { ?>
|
||||
<?php if(isset($error)) { ?>
|
||||
<div class="error-message"><?php echo $error; ?></div>
|
||||
<?php } ?>
|
||||
<div class="table-container" style="max-width:1500px;">
|
||||
|
@ -159,42 +159,45 @@ $clusters = getClusters('1');
|
|||
<th>Cluster</th>
|
||||
<th>Node</th>
|
||||
<th>Available</th>
|
||||
<th>Notes</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
foreach ($ips as $ip) {
|
||||
$ipid = $ip['ipid'];
|
||||
$ip_id = $ip['ip_id'];
|
||||
echo '<tr>';
|
||||
echo "<td class='tname'>" . $ip['ipaddress'] . "</td>";
|
||||
echo "<td style='font-size:small;'>" . $ip['vmid'] . "</td>";
|
||||
if($ip['vmid'] == "0") {
|
||||
echo "<td style='font-size:small;'>" . $ip['vmid'] . "</td>";
|
||||
} else {
|
||||
echo "<td style='font-size:small;'><a href='vm.php?id=" . $ip['vmid'] . "'>" . $ip['vmid'] . "</a></td>";
|
||||
}
|
||||
echo "<td style='font-size:small;'>" . $ip['gwip'] . "</td>";
|
||||
echo "<td style='font-size:small;'>" . getClusterName($ip['loc']) . "</td>";
|
||||
echo "<td style='font-size:small;'>" . getClusterName($ip['cluster']) . "</td>";
|
||||
echo "<td style='font-size:small;'>" . getNodeName($ip['node']) . "</td>";
|
||||
echo "<td><span class='ticon' style='padding-right:4px;'>Available: </span>";
|
||||
if ($ip['reserved'] == "0") {
|
||||
echo "<img src='assets/1.png' alt='Available'>";
|
||||
} else {
|
||||
if($ip['status'] == "0") {
|
||||
echo "<img src='assets/0.png' alt='Unavailable'>";
|
||||
} else {
|
||||
echo "<img src='assets/1.png' alt='Available'>";
|
||||
}
|
||||
echo "</td>";
|
||||
echo "<td style='font-size:small;'>" . $ip['notes'] . "</td><td>";
|
||||
echo "<td>";
|
||||
echo '<form action="'.htmlspecialchars($_SERVER["PHP_SELF"]).'" method="post">
|
||||
<input type="hidden" name="csrf_token" value="'.$csrfToken.'">
|
||||
<input type="hidden" name="ipid" value="'.$ipid.'">
|
||||
<input type="hidden" name="ip_id" value="'.$ip_id.'">
|
||||
<input type="hidden" name="ipaddress" value="'.$ip['ipaddress'].'">
|
||||
<button type="submit" class="stylish-button" name="delete_ip">Delete</button>
|
||||
</form>';
|
||||
echo '<br /><form action="'.htmlspecialchars($_SERVER["PHP_SELF"]).'" method="post">
|
||||
<input type="hidden" name="csrf_token" value="'.$csrfToken.'">
|
||||
<input type="hidden" name="ipid" value="'.$ipid.'">
|
||||
<input type="hidden" name="ip_id" value="'.$ip_id.'">
|
||||
<input type="hidden" name="ipaddress" value="'.$ip['ipaddress'].'">';
|
||||
if ($ip['reserved'] == "1") {
|
||||
echo '<button type="submit" class="stylish-button" name="unreserve_ip">Enable</button>';
|
||||
if($ip['status'] == "0") {
|
||||
echo '<button type="submit" class="stylish-button" name="enable_ip">Enable</button>';
|
||||
} else {
|
||||
echo '<button type="submit" class="stylish-button" name="reserve_ip">Disabled</button>';
|
||||
echo '<button type="submit" class="stylish-button" name="disable_ip">Disabled</button>';
|
||||
}
|
||||
echo '</form>';
|
||||
echo '</td></tr>';
|
||||
|
@ -206,26 +209,6 @@ $clusters = getClusters('1');
|
|||
</div>
|
||||
<?php include('footer.php'); ?>
|
||||
<script>
|
||||
// JavaScript code to handle theme switching (from previous responses)
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const body = document.body;
|
||||
|
||||
// Load the user's preferred theme from localStorage
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
body.classList.add('dark-mode');
|
||||
themeToggle.checked = true;
|
||||
}
|
||||
|
||||
themeToggle.addEventListener('change', () => {
|
||||
if (themeToggle.checked) {
|
||||
body.classList.add('dark-mode');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
body.classList.remove('dark-mode');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
});
|
||||
// Get the modal
|
||||
var modal = document.getElementById("addModal");
|
||||
|
||||
|
@ -247,7 +230,7 @@ $clusters = getClusters('1');
|
|||
|
||||
// When the user clicks anywhere outside of the modal, close it
|
||||
window.onclick = function(event) {
|
||||
if (event.target == modal) {
|
||||
if(event.target == modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
|
96
isos.php
96
isos.php
|
@ -2,17 +2,21 @@
|
|||
/** KontrolVM By KuJoe (https://github.com/KuJoe/kontrolvm) **/
|
||||
|
||||
session_start();
|
||||
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
} else {
|
||||
if (isset($_GET['s']) AND $_GET['s'] == '1') {
|
||||
if(isset($_GET['s']) AND $_GET['s'] == '1') {
|
||||
$success = "ISO added successfully.";
|
||||
}
|
||||
if(isset($_GET['s']) AND $_GET['s'] == '2') {
|
||||
$success = "ISO deleted successfully.";
|
||||
}
|
||||
define('AmAllowed', TRUE);
|
||||
require_once('config.php');
|
||||
require_once('functions.php');
|
||||
$loggedin_id = $_SESSION['staff_id'];
|
||||
$loggedin_id = (int)$_SESSION['staff_id'];
|
||||
$myrole = (int)$_SESSION["staff_role"];
|
||||
$chkActive = checkActive($loggedin_id);
|
||||
$chkLocked = checkLockedOut($loggedin_id);
|
||||
if($chkLocked == true OR $chkActive == false) {
|
||||
|
@ -20,16 +24,27 @@ if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
|||
exit;
|
||||
}
|
||||
}
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
if($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$token = $_POST["csrf_token"];
|
||||
if (validateCSRFToken($token)) {
|
||||
$friendlyname = $_POST["friendlyname"];
|
||||
$download = $_POST["download"];
|
||||
$result = addISOs($download, $friendlyname);
|
||||
if($result === true) {
|
||||
header("Location: isos.php?s=1");
|
||||
} else {
|
||||
$error = "ISO add failed: ".$result;
|
||||
if(validateCSRFToken($token)) {
|
||||
if(isset($_POST['add_iso'])) {
|
||||
$friendlyname = $_POST["friendlyname"];
|
||||
$download = $_POST["download"];
|
||||
$result = addISOs($loggedin_id,$download,$friendlyname);
|
||||
if($result === true) {
|
||||
header("Location: isos.php?s=1");
|
||||
} else {
|
||||
$error = $result;
|
||||
}
|
||||
}
|
||||
if(isset($_POST['delete_iso'])) {
|
||||
$idToChange = $_POST['template_id'];
|
||||
$result = deleteISO($loggedin_id,$idToChange);
|
||||
if($result === true) {
|
||||
header("Location: isos.php?s=2");
|
||||
} else {
|
||||
$error = $result;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$error = "Invalid CSRF token.";
|
||||
|
@ -54,20 +69,19 @@ $isos = getISOs();
|
|||
<label class="logo"><a href="index.php"><img src="assets/logo.png" alt="KontrolVM Logo"></a></label>
|
||||
<ul>
|
||||
<li><a href="index.php">Dashboard</a></li>
|
||||
<li><a href="nodes.php">Nodes</a></li>
|
||||
<li><a href="vms.php">VMs</a></li>
|
||||
<li><a href="users.php">Users</a></li>
|
||||
<?php if(in_array($myrole, ['2', '9'])) { ?> <li><a href="clusters.php">Infrastructure</a></li> <?php } ?>
|
||||
<?php if(in_array($myrole, ['1', '9'])) { ?> <li><a href="users.php">Users</a></li> <?php } ?>
|
||||
<li><a class="active" href="settings.php">Settings</a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo $_SESSION["username"]; ?></a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo htmlspecialchars($_SESSION["username"]); ?></a></li>
|
||||
<li><a href="logout.php"><i class="fa fa-sign-out" aria-hidden="true"></i></a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<ul class="submenu">
|
||||
<li><a href="settings.php">General</a></li>
|
||||
<li><a href="clusters.php">Clusters</a></li>
|
||||
<li><a class="active" href="isos.php">ISOs</a></li>
|
||||
<li><a href="ipv4.php">IPv4 Addresses</a></li>
|
||||
<li><a href="ipv6.php">IPv6 Addresses</a></li>
|
||||
<li><a href="logs.php">Logs</a></li>
|
||||
</ul>
|
||||
<div class="container">
|
||||
<p style="float:right;"><button id="addBtn" class="stylish-button"><i class="fa-solid fa-square-plus"></i> ADD ISO</button></p>
|
||||
|
@ -82,15 +96,15 @@ $isos = getISOs();
|
|||
<input type="text" id="friendlyname" name="friendlyname" required><br><br>
|
||||
<label for="download">Download URL:</label>
|
||||
<input type="text" id="download" name="download" required><br><br>
|
||||
<center><button type="submit" class="stylish-button"><i class="fa-solid fa-square-plus"></i> ADD ISO</button></center>
|
||||
<center><button type="submit" class="stylish-button" name="add_iso"><i class="fa-solid fa-square-plus"></i> ADD ISO</button></center>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<h1>ISOs</h1>
|
||||
<?php if (isset($success)) { ?>
|
||||
<?php if(isset($success)) { ?>
|
||||
<div class="success-message"><?php echo $success; ?></div>
|
||||
<?php } ?>
|
||||
<?php if (isset($error)) { ?>
|
||||
<?php if(isset($error)) { ?>
|
||||
<div class="error-message"><?php echo $error; ?></div>
|
||||
<?php } ?>
|
||||
<div class="table-container" style="max-width:1500px;">
|
||||
|
@ -100,26 +114,22 @@ $isos = getISOs();
|
|||
<tr>
|
||||
<th>OS</th>
|
||||
<th>File</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
foreach ($isos as $iso) {
|
||||
$templateid = $iso['templateid'];
|
||||
$template_id = $iso['template_id'];
|
||||
echo '<tr>';
|
||||
echo "<td class='tname'>" . $iso['friendlyname'] . "</td>";
|
||||
echo "<td style='font-size:small;'>" . $iso['filename'] . "</td>";
|
||||
echo "<td><span class='ticon' style='padding-right:4px;'>Status: </span>";
|
||||
if ($iso['status'] == "1") {
|
||||
echo "<img src='assets/1.png' alt='Enabled'>";
|
||||
} else {
|
||||
echo "<img src='assets/0.png' alt='Disabled'>";
|
||||
}
|
||||
echo "</td>";
|
||||
echo "<td>N/A</td>";
|
||||
echo '</tr>';
|
||||
echo "<td style='font-size:small;'>" . $iso['filename'] . "</td><td>";
|
||||
echo '<form action="'.htmlspecialchars($_SERVER["PHP_SELF"]).'" method="post">
|
||||
<input type="hidden" name="csrf_token" value="'.$csrfToken.'">
|
||||
<input type="hidden" name="template_id" value="'.$template_id.'">
|
||||
<button type="submit" class="stylish-button" name="delete_iso">Delete</button>
|
||||
</form>';
|
||||
echo '</td></tr>';
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
|
@ -128,26 +138,6 @@ $isos = getISOs();
|
|||
</div>
|
||||
<?php include('footer.php'); ?>
|
||||
<script>
|
||||
// JavaScript code to handle theme switching (from previous responses)
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const body = document.body;
|
||||
|
||||
// Load the user's preferred theme from localStorage
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
body.classList.add('dark-mode');
|
||||
themeToggle.checked = true;
|
||||
}
|
||||
|
||||
themeToggle.addEventListener('change', () => {
|
||||
if (themeToggle.checked) {
|
||||
body.classList.add('dark-mode');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
body.classList.remove('dark-mode');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
});
|
||||
// Get the modal
|
||||
var modal = document.getElementById("addModal");
|
||||
|
||||
|
@ -169,7 +159,7 @@ $isos = getISOs();
|
|||
|
||||
// When the user clicks anywhere outside of the modal, close it
|
||||
window.onclick = function(event) {
|
||||
if (event.target == modal) {
|
||||
if(event.target == modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
|
28
logout.php
28
logout.php
|
@ -1,30 +1,12 @@
|
|||
<?php
|
||||
/** KontrolVM By KuJoe (https://github.com/KuJoe/kontrolvm) **/
|
||||
|
||||
session_start(); // Start the session
|
||||
|
||||
// Unset all session variables
|
||||
$_SESSION = array();
|
||||
|
||||
// If it's desired to kill the session, also delete the session cookie.
|
||||
// Note: This will destroy the session, and not just the session data!
|
||||
if (ini_get("session.use_cookies")) {
|
||||
$params = session_get_cookie_params();
|
||||
setcookie(session_name(), '', time() - 42000,
|
||||
$params["path"], $params["domain"],
|
||||
$params["secure"], $params["httponly"]
|
||||
);
|
||||
}
|
||||
|
||||
// Finally, destroy the session
|
||||
session_start();
|
||||
unset($_SESSION['username']);
|
||||
unset($_SESSION['password']);
|
||||
unset($_SESSION['loggedin']);
|
||||
unset($_SESSION['mfa_required']);
|
||||
session_destroy();
|
||||
|
||||
// Clear all cookies
|
||||
foreach ($_COOKIE as $key => $value) {
|
||||
setcookie($key, '', time() - 3600, '/'); // Set expiration to the past
|
||||
}
|
||||
|
||||
// Redirect to index.php after 3 seconds
|
||||
header("refresh:3;url=index.php");
|
||||
|
||||
?>
|
||||
|
|
122
logs.php
Normal file
122
logs.php
Normal file
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
/** KontrolVM By KuJoe (https://github.com/KuJoe/kontrolvm) **/
|
||||
|
||||
session_start();
|
||||
if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
} else {
|
||||
define('AmAllowed', TRUE);
|
||||
require_once('config.php');
|
||||
require_once('functions.php');
|
||||
$loggedin_id = (int)$_SESSION['staff_id'];
|
||||
$myrole = (int)$_SESSION["staff_role"];
|
||||
$chkActive = checkActive($loggedin_id);
|
||||
$chkLocked = checkLockedOut($loggedin_id);
|
||||
if($chkLocked == true OR $chkActive == false) {
|
||||
header("Location: logout.php");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
$csrfToken = getCSRFToken();
|
||||
$totalLogs = getLogsTotal();
|
||||
$page = isset($_GET['p']) && is_numeric($_GET['p']) ? $_GET['p'] : 1;
|
||||
$perPage = 20;
|
||||
$offset = ($page - 1) * $perPage;
|
||||
$totalPages = ceil($totalLogs / $perPage);
|
||||
$logs = getLogs($perPage,$offset);
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<?php include('header.php'); ?>
|
||||
<body>
|
||||
<nav>
|
||||
<input type="checkbox" id="check">
|
||||
<label for="check" class="checkbtn">
|
||||
<i class="fas fa-bars"></i>
|
||||
</label>
|
||||
<label class="logo"><a href="index.php"><img src="assets/logo.png" alt="KontrolVM Logo"></a></label>
|
||||
<ul>
|
||||
<li><a href="index.php">Dashboard</a></li>
|
||||
<?php if(in_array($myrole, ['2', '9'])) { ?> <li><a href="clusters.php">Infrastructure</a></li> <?php } ?>
|
||||
<?php if(in_array($myrole, ['1', '9'])) { ?> <li><a href="users.php">Users</a></li> <?php } ?>
|
||||
<li><a class="active" href="settings.php">Settings</a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo htmlspecialchars($_SESSION["username"]); ?></a></li>
|
||||
<li><a href="logout.php"><i class="fa fa-sign-out" aria-hidden="true"></i></a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<ul class="submenu">
|
||||
<li><a href="settings.php">General</a></li>
|
||||
<li><a href="isos.php">ISOs</a></li>
|
||||
<li><a href="ipv4.php">IPv4 Addresses</a></li>
|
||||
<li><a href="ipv6.php">IPv6 Addresses</a></li>
|
||||
<li><a class="active" href="logs.php">Logs</a></li>
|
||||
</ul>
|
||||
<div class="container">
|
||||
<h1>Logs</h1>
|
||||
<?php if(isset($success)) { ?>
|
||||
<div class="success-message"><?php echo $success; ?></div>
|
||||
<?php } ?>
|
||||
<?php if(isset($error)) { ?>
|
||||
<div class="error-message"><?php echo $error; ?></div>
|
||||
<?php } ?>
|
||||
<div class="table-container" style="max-width:1500px;">
|
||||
<table id="server_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Message</th>
|
||||
<th>Timestamp</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if(count($logs) > 0) {
|
||||
foreach ($logs as $log) {
|
||||
echo '<tr>';
|
||||
echo "<td class='tname'>" . htmlspecialchars($log['log_message']) . "</td>";
|
||||
echo "<td>" . htmlspecialchars($log['created_at']) . "</td>";
|
||||
echo '</tr>';
|
||||
}
|
||||
} else {
|
||||
echo '<tr><td colspan="2">No log entries</td></tr>';
|
||||
}?>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pagination">
|
||||
<?php if($page > 5): ?>
|
||||
<a href="?p=<?php echo $page - 1; ?>">« Previous</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$maxPagesToShow = 5;
|
||||
$startPage = max(1, $page - floor($maxPagesToShow / 2));
|
||||
$endPage = min($totalPages, $startPage + $maxPagesToShow - 1);
|
||||
|
||||
if($page > floor($maxPagesToShow/2) && $totalPages > $maxPagesToShow) {
|
||||
echo "<a href='?p=1'>1</a> ... ";
|
||||
}
|
||||
|
||||
for ($i = $startPage; $i <= $endPage; $i++): ?>
|
||||
<a href="?p=<?php echo $i; ?>" <?php if($i == $page) echo 'class="active"'; ?>><?php echo $i; ?></a>
|
||||
<?php endfor;
|
||||
|
||||
if($endPage < $totalPages) {
|
||||
echo "... <a href='?p=$totalPages'>$totalPages</a>";
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if($page < $totalPages): ?>
|
||||
<a href="?p=<?php echo $page + 1; ?>">Next »</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php include('footer.php'); ?>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="assets/filtertable.js"></script>
|
||||
</body>
|
||||
</html>
|
198
node.php
198
node.php
|
@ -3,33 +3,51 @@
|
|||
|
||||
session_start();
|
||||
define('AmAllowed', TRUE);
|
||||
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
} else {
|
||||
if (isset($_GET['id'])) {
|
||||
if (isset($_GET['s']) AND $_GET['s'] == '1') {
|
||||
if(isset($_GET['id'])) {
|
||||
if(isset($_GET['s']) AND $_GET['s'] == '1') {
|
||||
$success = "Node updated successfully.";
|
||||
}
|
||||
if(isset($_GET['s'])) {
|
||||
if($_GET['s'] == '1') {
|
||||
$success = "Node updated successfully.";
|
||||
} elseif($_GET['s'] == '2') {
|
||||
$success = "Node VMs imported successfully.";
|
||||
} elseif($_GET['s'] == '3') {
|
||||
$success = "Network added successfully.";
|
||||
} elseif($_GET['s'] == '4') {
|
||||
$success = "Network deleted successfully.";
|
||||
}
|
||||
}
|
||||
$node_id = $_GET['id'];
|
||||
require_once('functions.php');
|
||||
$node = getNodeDetails($node_id);
|
||||
} elseif (isset($_POST['id'])) {
|
||||
} elseif(isset($_POST['id'])) {
|
||||
$node_id = $_POST['id'];
|
||||
require_once('functions.php');
|
||||
$node = getNodeDetails($node_id);
|
||||
} else {
|
||||
error_log("Error node ID missing.");
|
||||
header("Location: nodes.php?s=3");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
$loggedin_id = $_SESSION['staff_id'];
|
||||
$loggedin_id = (int)$_SESSION['staff_id'];
|
||||
$myrole = (int)$_SESSION["staff_role"];
|
||||
$chkActive = checkActive($loggedin_id);
|
||||
$chkLocked = checkLockedOut($loggedin_id);
|
||||
if($chkLocked == true OR $chkActive == false) {
|
||||
header("Location: logout.php");
|
||||
exit;
|
||||
}
|
||||
$chkRole = getStaffRole($loggedin_id);
|
||||
$allowedRoles = ['2', '9'];
|
||||
if(!in_array($chkRole, $allowedRoles)) {
|
||||
header("Location: home.php?s=99");
|
||||
exit;
|
||||
}
|
||||
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
|
@ -37,35 +55,48 @@ header('Cache-Control: post-check=0, pre-check=0', false);
|
|||
header('Pragma: no-cache');
|
||||
$csrfToken = getCSRFToken();
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
if($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$token = $_POST["csrf_token"];
|
||||
if (validateCSRFToken($token)) {
|
||||
if (isset($_POST['editNode'])) {
|
||||
$hostname = $_POST["hostname"];
|
||||
$ipaddr = $_POST["ipaddr"];
|
||||
$sshport = $_POST["sshport"];
|
||||
$lastvm = $_POST["lastvm"];
|
||||
$lastvnc = $_POST["lastvnc"];
|
||||
$lastws = $_POST["lastws"];
|
||||
$loc = $_POST["loc"];
|
||||
if($_POST["status"]) {
|
||||
$status = '1';
|
||||
} else {
|
||||
$status = '0';
|
||||
}
|
||||
$result = editNode($node_id, $hostname, $ipaddr, $sshport, $status, $lastvm, $lastvnc, $lastws, $loc);
|
||||
if(validateCSRFToken($token)) {
|
||||
if(isset($_POST['editNode'])) {
|
||||
$node_data = [':hostname' => $_POST["hostname"],':ipaddr' => $_POST["ipaddr"],':sshport' => $_POST["sshport"],':lastvm' => $_POST["lastvm"],':lastvnc' => $_POST["lastvnc"],':lastws' => $_POST["lastws"],':cluster' => $_POST["cluster"],':status' => isset($_POST["status"])? 1: 0];
|
||||
$result = editNode($loggedin_id,$node_id,$node_data);
|
||||
if($result === true) {
|
||||
header("Location: node.php?id=$node_id&s=1");
|
||||
header("Location: node.php?id=". (int)$node_id. "&s=1");
|
||||
} else {
|
||||
$error = "Node update failed: ".$result;
|
||||
}
|
||||
}
|
||||
if (isset($_POST['deleteNode'])) {
|
||||
if (isset($_POST['confirm'])) {
|
||||
if(isset($_POST['importVMs'])) {
|
||||
$result = importVMs($loggedin_id,$node_id);
|
||||
if($result === true) {
|
||||
header("Location: node.php?id=". (int)$node_id. "&s=2");
|
||||
} else {
|
||||
$error = "Node import failed: ".$result;
|
||||
}
|
||||
}
|
||||
if(isset($_POST['add_network'])) {
|
||||
$result = addNetwork($loggedin_id,$node_id,$_POST["net_name"]);
|
||||
if($result === true) {
|
||||
header("Location: node.php?id=". (int)$node_id. "&s=3");
|
||||
} else {
|
||||
$error = "Node network add failed: ".$result;
|
||||
}
|
||||
}
|
||||
if(isset($_POST['delete_network'])) {
|
||||
$result = deleteNetwork($loggedin_id,$node_id,$_POST["id"]);
|
||||
if($result === true) {
|
||||
header("Location: node.php?id=". (int)$node_id. "&s=4");
|
||||
} else {
|
||||
$error = "Node network delete failed: ".$result;
|
||||
}
|
||||
}
|
||||
if(isset($_POST['deleteNode'])) {
|
||||
if(isset($_POST['confirm'])) {
|
||||
$confirm = $_POST['confirm'];
|
||||
$result = deleteNode($node_id, $node['hostname'], $confirm);
|
||||
$result = deleteNode($loggedin_id,$node_id,$confirm);
|
||||
if($result === true) {
|
||||
header("Location: nodes.php?s=2");
|
||||
header("Location: nodes.php?s=4");
|
||||
} else {
|
||||
$error = "Node delete failed: ".$result;
|
||||
}
|
||||
|
@ -78,19 +109,20 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
|||
}
|
||||
}
|
||||
|
||||
if ($node) {
|
||||
$clusters = getClusters('all');
|
||||
$script_name = 'updateNodes.php';
|
||||
if($node) {
|
||||
$script_name = 'refreshNodes.php';
|
||||
$last_run_time = getLastRunTime($script_name);
|
||||
if (!$last_run_time || time() - $last_run_time >= 3600) {
|
||||
if(!$last_run_time || time() - $last_run_time >= 3600) {
|
||||
include($script_name);
|
||||
updateLastRunTime($script_name);
|
||||
}
|
||||
if ($node['status'] == "1") {
|
||||
if($node['status'] == "1") {
|
||||
$state = " checked";
|
||||
} else {
|
||||
$state = "";
|
||||
}
|
||||
$clusters = getClusters('all');
|
||||
$networks = getNetworks($node_id);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
@ -104,27 +136,31 @@ if ($node) {
|
|||
<label class="logo"><a href="index.php"><img src="assets/logo.png" alt="KontrolVM Logo"></a></label>
|
||||
<ul>
|
||||
<li><a href="index.php">Dashboard</a></li>
|
||||
<li><a class="active" href="nodes.php">Nodes</a></li>
|
||||
<li><a href="vms.php">VMs</a></li>
|
||||
<li><a href="users.php">Users</a></li>
|
||||
<?php if(in_array($myrole, ['2', '9'])) { ?> <li><a class="active" href="clusters.php">Infrastructure</a></li> <?php } ?>
|
||||
<?php if(in_array($myrole, ['1', '9'])) { ?> <li><a href="users.php">Users</a></li> <?php } ?>
|
||||
<li><a href="settings.php">Settings</a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo $_SESSION["username"]; ?></a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo htmlspecialchars($_SESSION["username"]); ?></a></li>
|
||||
<li><a href="logout.php"><i class="fa fa-sign-out" aria-hidden="true"></i></a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<ul class="submenu">
|
||||
<li><a href="clusters.php">Clusters</a></li>
|
||||
<li><a class="active" href="nodes.php">Nodes</a></li>
|
||||
<li><a href="vms.php">VMs</a></li>
|
||||
</ul>
|
||||
<div class="container">
|
||||
<h1>Node Details</h1>
|
||||
<?php if (isset($success)) { ?>
|
||||
<?php if(isset($success)) { ?>
|
||||
<div class="success-message"><?php echo $success; ?></div>
|
||||
<?php } ?>
|
||||
<?php if (isset($error)) { ?>
|
||||
<?php if(isset($error)) { ?>
|
||||
<div class="error-message"><?php echo $error; ?></div>
|
||||
<?php } ?>
|
||||
<div class="table-container" style="max-width:1500px;">
|
||||
<div class="node-details">
|
||||
<form id="editNode" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo $csrfToken; ?>">
|
||||
<input type="hidden" name="id" value="<?php echo $node_id; ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken); ?>">
|
||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($node_id); ?>">
|
||||
<table>
|
||||
<tr>
|
||||
<td style="background-color:#999;">Hostname:</td>
|
||||
|
@ -149,12 +185,12 @@ if ($node) {
|
|||
</tr>
|
||||
<tr>
|
||||
<td style="background-color:#999;">Cluster:</td>
|
||||
<td><select id="loc" name="loc" style="text-align:center;width:80%;">
|
||||
<td><select id="cluster" name="cluster" style="text-align:center;width:80%;">
|
||||
<?php foreach ($clusters as $cluster):?>
|
||||
<?php if($cluster['loc'] == $node['loc']) { ?>
|
||||
<option value="<?php echo htmlspecialchars($cluster['loc']);?>" selected>
|
||||
<?php if($cluster['cluster_id'] == $node['cluster']) { ?>
|
||||
<option value="<?php echo htmlspecialchars($cluster['cluster_id']);?>" selected>
|
||||
<?php } else { ?>
|
||||
<option value="<?php echo htmlspecialchars($cluster['loc']);?>">
|
||||
<option value="<?php echo htmlspecialchars($cluster['cluster_id']);?>">
|
||||
<?php } ?>
|
||||
<?php echo htmlspecialchars($cluster['friendlyname']);?>
|
||||
</option>
|
||||
|
@ -187,19 +223,19 @@ if ($node) {
|
|||
</tr>
|
||||
<tr>
|
||||
<td style="background-color:#999;">Uptime:</td>
|
||||
<td><?php echo $node['uptime']; ?></td>
|
||||
<td><?php echo htmlspecialchars($node['uptime']); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="background-color:#999;">Memory Used:</td>
|
||||
<td><?php echo $node['memused']; ?></td>
|
||||
<td><?php echo htmlspecialchars($node['memused']); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="background-color:#999;">Disk Usage:</td>
|
||||
<td><?php echo $node['diskclnt']; ?></td>
|
||||
<td><?php echo htmlspecialchars($node['diskclnt']); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="background-color:#999;">Load Average:</td>
|
||||
<td><?php echo $node['load']; ?></td>
|
||||
<td><?php echo htmlspecialchars($node['load']); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="background-color:#999;">Make:</td>
|
||||
|
@ -238,7 +274,43 @@ if ($node) {
|
|||
<br />
|
||||
<center><button type="submit" name="editNode" id="editNode" class="stylish-button">SAVE NODE</button><br /></center>
|
||||
</form>
|
||||
<?php if ($node['status'] == "0") { ?>
|
||||
<br />
|
||||
<hr />
|
||||
<br />
|
||||
<h2>Network Management</h2>
|
||||
<div class="disk-list">
|
||||
<form id="add_network" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
|
||||
<h3>Add New Network</h3>
|
||||
<small><i>This is for adding an existing network, this does not create a new network on the node (yet).</i></small><br />
|
||||
<input type="hidden" name="csrf_token" value="<?php echo $csrfToken; ?>">
|
||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($node_id); ?>">
|
||||
<input type="text" id="net_name" name="net_name" placeholder="br1" style="text-align:center;width:80px;"><button class="stylish-button" id="add_network" name="add_network">Add Network</button>
|
||||
</form>
|
||||
<br />
|
||||
<hr />
|
||||
<br />
|
||||
<h3>Available Networks</h3>
|
||||
<?php foreach ($networks as $network):
|
||||
$name = $network['net_name'];
|
||||
$net_id = $network['net_id'];
|
||||
$csrf = '<input type="hidden" name="csrf_token" value="'.$csrfToken.'">';
|
||||
$netname = '<input type="hidden" name="net_name" value="'.$name.'">';
|
||||
$netid = '<input type="hidden" name="net_id" value="'.$net_id.'">';
|
||||
echo "<form id='delete_network' action='node.php?id=".htmlspecialchars($node_id)." method='post'>$csrf $netname $netid $name <button class='stylish-button' id='delete_network' name='delete_network'>Delete</button></form>";
|
||||
endforeach;?>
|
||||
</div>
|
||||
<br />
|
||||
<hr />
|
||||
<br />
|
||||
<h2>Import VMs</h2>
|
||||
<div class="disk-list">
|
||||
<form id="importVMs" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken); ?>">
|
||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($node_id); ?>">
|
||||
<button type="submit" name="importVMs" id="importVMs" class="stylish-button" style="background-color:green;">IMPORT</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php if($node['status'] == "0") { ?>
|
||||
<br />
|
||||
<hr />
|
||||
<br />
|
||||
|
@ -254,8 +326,8 @@ if ($node) {
|
|||
Confirm
|
||||
</td>
|
||||
<td>
|
||||
<input type="hidden" name="csrf_token" value="<?php echo $csrfToken; ?>">
|
||||
<input type="hidden" name="id" value="<?php echo $node_id; ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken); ?>">
|
||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($node_id); ?>">
|
||||
<button type="submit" name="deleteNode" id="deleteNode" class="stylish-button" style="background-color:red;">DELETE</button>
|
||||
</form>
|
||||
</td>
|
||||
|
@ -278,33 +350,13 @@ if ($node) {
|
|||
const checkboxId = 'enable' + inputId;
|
||||
const checkbox = document.getElementById(checkboxId);
|
||||
const inputField = document.getElementById(inputId);
|
||||
if (checkbox) { // Check if checkbox exists
|
||||
if(checkbox) { // Check if checkbox exists
|
||||
inputField.readOnly =!checkbox.checked;
|
||||
} else {
|
||||
console.error("Checkbox not found:", checkboxId);
|
||||
}
|
||||
}
|
||||
|
||||
// JavaScript code to handle theme switching (from previous responses)
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const body = document.body;
|
||||
|
||||
// Load the user's preferred theme from localStorage
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
body.classList.add('dark-mode');
|
||||
themeToggle.checked = true;
|
||||
}
|
||||
|
||||
themeToggle.addEventListener('change', () => {
|
||||
if (themeToggle.checked) {
|
||||
body.classList.add('dark-mode');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
body.classList.remove('dark-mode');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
});
|
||||
// Get the modal
|
||||
var modal = document.getElementById("addServerModal");
|
||||
|
||||
|
@ -326,7 +378,7 @@ if ($node) {
|
|||
|
||||
// When the user clicks anywhere outside of the modal, close it
|
||||
window.onclick = function(event) {
|
||||
if (event.target == modal) {
|
||||
if(event.target == modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
|
91
nodes.php
91
nodes.php
|
@ -2,41 +2,54 @@
|
|||
/** KontrolVM By KuJoe (https://github.com/KuJoe/kontrolvm) **/
|
||||
|
||||
session_start();
|
||||
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
} else {
|
||||
if (isset($_GET['s']) AND $_GET['s'] == '1') {
|
||||
$success = "Node added successfully.";
|
||||
} elseif (isset($_GET['s']) AND $_GET['s'] == '2') {
|
||||
$success = "Nodes updated.";
|
||||
if(isset($_GET['s']) ) {
|
||||
if($_GET['s'] == '1') {
|
||||
$success = "Node added successfully.";
|
||||
} elseif($_GET['s'] == '2') {
|
||||
$success = "Nodes updated.";
|
||||
} elseif($_GET['s'] == '3') {
|
||||
$error = "Node ID missing.";
|
||||
} elseif($_GET['s'] == '4') {
|
||||
$error = "Node deleted successfully.";
|
||||
}
|
||||
}
|
||||
define('AmAllowed', TRUE);
|
||||
require_once('config.php');
|
||||
require_once('functions.php');
|
||||
$loggedin_id = $_SESSION['staff_id'];
|
||||
$loggedin_id = (int)$_SESSION['staff_id'];
|
||||
$myrole = (int)$_SESSION["staff_role"];
|
||||
$chkActive = checkActive($loggedin_id);
|
||||
$chkLocked = checkLockedOut($loggedin_id);
|
||||
if($chkLocked == true OR $chkActive == false) {
|
||||
header("Location: logout.php");
|
||||
exit;
|
||||
}
|
||||
if (isset($_GET['update']) AND $_GET['update'] == '1') {
|
||||
include('updateNodes.php');
|
||||
$chkRole = getStaffRole($loggedin_id);
|
||||
$allowedRoles = ['2', '9'];
|
||||
if(!in_array($chkRole, $allowedRoles)) {
|
||||
header("Location: home.php?s=99");
|
||||
exit;
|
||||
}
|
||||
if(isset($_GET['update']) AND $_GET['update'] == '1') {
|
||||
include('refreshNodes.php');
|
||||
header("Location: nodes.php?state=all&s=2");
|
||||
}
|
||||
}
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
if($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$token = $_POST["csrf_token"];
|
||||
if (validateCSRFToken($token)) {
|
||||
if(validateCSRFToken($token)) {
|
||||
$hostname = $_POST["hostname"];
|
||||
$ipaddr = $_POST["ipaddr"];
|
||||
$sshport = $_POST["sshport"];
|
||||
$loc = $_POST["loc"];
|
||||
$cluster = $_POST["cluster"];
|
||||
$rootpw = $_POST["rootpw"];
|
||||
$result = addNode($hostname, $ipaddr, $sshport, $rootpw, $loc);
|
||||
$result = addNode($loggedin_id,$hostname,$ipaddr,$sshport,$rootpw,$cluster);
|
||||
if($result === true) {
|
||||
include('updateNodes.php');
|
||||
include('refreshNodes.php');
|
||||
header("Location: nodes.php?state=all&s=1");
|
||||
} else {
|
||||
$error = "Node add failed: ".$result;
|
||||
|
@ -56,11 +69,11 @@ if(isset($_GET['state']) AND $_GET['state'] == "all") {
|
|||
$servers = getServerList('1');
|
||||
}
|
||||
$clusters = getClusters('1');
|
||||
$script_name = 'updateNodes.php';
|
||||
$script_name = 'refreshNodes.php';
|
||||
$last_run_time = getLastRunTime($script_name);
|
||||
if (!$last_run_time || time() - $last_run_time >= 3600) {
|
||||
if((!$last_run_time || time() - $last_run_time >= 3600) AND isset($bgupdate)) {
|
||||
include($script_name);
|
||||
updateLastRunTime($script_name);
|
||||
$last_run_time = time();
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
|
@ -75,14 +88,18 @@ if (!$last_run_time || time() - $last_run_time >= 3600) {
|
|||
<label class="logo"><a href="index.php"><img src="assets/logo.png" alt="KontrolVM Logo"></a></label>
|
||||
<ul>
|
||||
<li><a href="index.php">Dashboard</a></li>
|
||||
<li><a class="active" href="nodes.php">Nodes</a></li>
|
||||
<li><a href="vms.php">VMs</a></li>
|
||||
<li><a href="users.php">Users</a></li>
|
||||
<?php if(in_array($myrole, ['2', '9'])) { ?> <li><a class="active" href="clusters.php">Infrastructure</a></li> <?php } ?>
|
||||
<?php if(in_array($myrole, ['1', '9'])) { ?> <li><a href="users.php">Users</a></li> <?php } ?>
|
||||
<li><a href="settings.php">Settings</a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo $_SESSION["username"]; ?></a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo htmlspecialchars($_SESSION["username"]); ?></a></li>
|
||||
<li><a href="logout.php"><i class="fa fa-sign-out" aria-hidden="true"></i></a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<ul class="submenu">
|
||||
<li><a href="clusters.php">Clusters</a></li>
|
||||
<li><a class="active" href="nodes.php">Nodes</a></li>
|
||||
<li><a href="vms.php">VMs</a></li>
|
||||
</ul>
|
||||
<div class="container">
|
||||
<p style="float:right;"><button id="addBtn" class="stylish-button"><i class="fa-solid fa-square-plus"></i> ADD NODE</button></p>
|
||||
<div id="addModal" class="modal">
|
||||
|
@ -100,10 +117,10 @@ if (!$last_run_time || time() - $last_run_time >= 3600) {
|
|||
<input type="text" id="sshport" name="sshport" required><br><br>
|
||||
<label for="rootpw">Root Password:</label>
|
||||
<input type="password" id="rootpw" name="rootpw" required><br><br>
|
||||
<label for="loc">Cluster:</label>
|
||||
<select name="loc">
|
||||
<label for="cluster">Cluster:</label>
|
||||
<select name="cluster">
|
||||
<?php foreach ($clusters as $cluster):?>
|
||||
<option value="<?php echo htmlspecialchars($cluster['loc']);?>">
|
||||
<option value="<?php echo htmlspecialchars($cluster['cluster_id']);?>">
|
||||
<?php echo htmlspecialchars($cluster['friendlyname']);?>
|
||||
</option>
|
||||
<?php endforeach;?>
|
||||
|
@ -113,10 +130,10 @@ if (!$last_run_time || time() - $last_run_time >= 3600) {
|
|||
</div>
|
||||
</div>
|
||||
<h1>Nodes</h1>
|
||||
<?php if (isset($success)) { ?>
|
||||
<?php if(isset($success)) { ?>
|
||||
<div class="success-message"><?php echo $success; ?></div>
|
||||
<?php } ?>
|
||||
<?php if (isset($error)) { ?>
|
||||
<?php if(isset($error)) { ?>
|
||||
<div class="error-message"><?php echo $error; ?></div>
|
||||
<?php } ?>
|
||||
<div class="table-container" style="max-width:1500px;">
|
||||
|
@ -136,7 +153,7 @@ if (!$last_run_time || time() - $last_run_time >= 3600) {
|
|||
foreach ($servers as $server) {
|
||||
$node_id = $server['node_id'];
|
||||
$connection = @fsockopen($server['ipaddr'], $server['sshport'], $errno, $errstr, 2);
|
||||
if (is_resource($connection)) {
|
||||
if(is_resource($connection)) {
|
||||
$online = "<img src='assets/1.png' alt='Online' />";
|
||||
fclose($connection);
|
||||
} else {
|
||||
|
@ -158,26 +175,6 @@ if (!$last_run_time || time() - $last_run_time >= 3600) {
|
|||
</div>
|
||||
<?php include('footer.php'); ?>
|
||||
<script>
|
||||
// JavaScript code to handle theme switching (from previous responses)
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const body = document.body;
|
||||
|
||||
// Load the user's preferred theme from localStorage
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
body.classList.add('dark-mode');
|
||||
themeToggle.checked = true;
|
||||
}
|
||||
|
||||
themeToggle.addEventListener('change', () => {
|
||||
if (themeToggle.checked) {
|
||||
body.classList.add('dark-mode');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
body.classList.remove('dark-mode');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
});
|
||||
// Get the modal
|
||||
var modal = document.getElementById("addModal");
|
||||
|
||||
|
@ -199,7 +196,7 @@ if (!$last_run_time || time() - $last_run_time >= 3600) {
|
|||
|
||||
// When the user clicks anywhere outside of the modal, close it
|
||||
window.onclick = function(event) {
|
||||
if (event.target == modal) {
|
||||
if(event.target == modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
|
158
password_reset.php
Normal file
158
password_reset.php
Normal file
|
@ -0,0 +1,158 @@
|
|||
<?php
|
||||
/** KontrolVM By KuJoe (https://github.com/KuJoe/kontrolvm) **/
|
||||
|
||||
session_start();
|
||||
define('AmAllowed', TRUE);
|
||||
require_once('config.php');
|
||||
require_once('functions.php');
|
||||
if($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
if(isset($secretkey)) {
|
||||
$cf_url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
|
||||
$token = $_POST['cf-turnstile-response'];
|
||||
$data = array(
|
||||
"secret" => $secretkey,
|
||||
"response" => $token,
|
||||
"remoteip" => $remote_addr
|
||||
);
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, $cf_url);
|
||||
curl_setopt($curl, CURLOPT_POST, true);
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
$response = curl_exec($curl);
|
||||
if(curl_errno($curl)) {
|
||||
$error_message = curl_error($curl);
|
||||
$error = "Error with CAPTCHA: " . $error_message;
|
||||
curl_close($curl);
|
||||
} else {
|
||||
$response = json_decode($response,true);
|
||||
if($response['error-codes'] && count($response['error-codes']) > 0){
|
||||
error_log("Cloudflare Turnstile check failed.");
|
||||
$error = "Error with CAPTCHA.";
|
||||
curl_close($curl);
|
||||
}
|
||||
}
|
||||
curl_close($curl);
|
||||
}
|
||||
if(isset($_POST['email'])) {
|
||||
if(sendPasswordResetEmail($_POST['email'])) {
|
||||
$success = "Password reset e-mail sent successfully.";
|
||||
} else {
|
||||
$error = "Password reset e-mail failed to send.";
|
||||
}
|
||||
}
|
||||
}
|
||||
if(isset($_GET['id']) AND isset($_GET['token'])) {
|
||||
if(verifyToken($_GET['token'],$_GET['id'])) {
|
||||
header("Location: index.php?e=4");
|
||||
} else {
|
||||
$error = "Password reset e-mail failed to send.";
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KontrolVM</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background-color: #f4f4f7;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
background-color: #fff;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
input[type="email"] {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
color: #155724;
|
||||
background-color: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
padding: 10px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #dc3545;
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
padding: 10px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<img src="assets/logo.png" alt="KontrolVM Logo" style="display:block;margin:0 auto;" />
|
||||
<br />
|
||||
<br />
|
||||
<h1>Password Reset</h1>
|
||||
<?php if(isset($success)) { ?>
|
||||
<div class="success-message"><?php echo $success; ?></div>
|
||||
<?php } elseif(isset($error)) { ?>
|
||||
<div class="error-message"><?php echo $error; ?></div>
|
||||
<?php } else { ?>
|
||||
<form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
|
||||
<label for="email">E-mail Address:</label>
|
||||
<input type="email" id="email" name="email" required>
|
||||
<?php if(isset($sitekey)) { ?>
|
||||
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" defer></script>
|
||||
<div class="cf-turnstile" data-sitekey="<?php echo $sitekey; ?>"></div>
|
||||
<?php } ?>
|
||||
<br />
|
||||
<br />
|
||||
<button type="submit">Reset Password</button>
|
||||
</form>
|
||||
<?php } ?>
|
||||
<br /><br />
|
||||
<p style="text-align:center;font-size:0.9em;">Powered by <a href="https://github.com/KuJoe/kontrolvm" target="_blank">KontrolVM</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
23
refreshNodes.php
Normal file
23
refreshNodes.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
/** KontrolVM By KuJoe (https://github.com/KuJoe/kontrolvm) **/
|
||||
|
||||
if(!defined('AmAllowed')) {
|
||||
die('Error 001A');
|
||||
}
|
||||
require_once('functions.php');
|
||||
$servers = getServerList('1');
|
||||
|
||||
if(count($servers) > 0) {
|
||||
foreach ($servers as $server) {
|
||||
if(getNodeInfo($server['node_id'])) {
|
||||
getNodeStats($server['node_id']);
|
||||
}
|
||||
}
|
||||
updateLastRunTime('refreshNodes.php');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
?>
|
58
scripts/backup_vm.sh
Normal file
58
scripts/backup_vm.sh
Normal file
|
@ -0,0 +1,58 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Configuration (adjust as needed)
|
||||
DISABLE_SUSPEND=0
|
||||
|
||||
if [ $# -ne 2 ]; then
|
||||
echo "Usage: $0 <vm_name> <backup_file_name>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VM_NAME="$1"
|
||||
BACKUP_NAME="$2"
|
||||
BACKUP_DIR="/home/kontrolvm/kvm_backups"
|
||||
TEMP_DIR="/home/kontrolvm/backups_tmp"
|
||||
VM_TEMP_DIR="$TEMP_DIR/$VM_NAME"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
mkdir -p "$TEMP_DIR"
|
||||
|
||||
vm_state=$(virsh domstate "$VM_NAME")
|
||||
|
||||
if test -d $VM_TEMP_DIR; then
|
||||
echo "A backup already appears to be running. Delete $VM_TEMP_DIR if one is not."
|
||||
else
|
||||
mkdir -p "$VM_TEMP_DIR"
|
||||
echo "Starting backup."
|
||||
if [[ "$vm_state" == "running" ]] && [[ "$DISABLE_SUSPEND" -eq 0 ]]; then
|
||||
virsh suspend "$VM_NAME"
|
||||
backup_type="suspended"
|
||||
elif [[ "$vm_state" == "running" ]] && [[ "$DISABLE_SUSPEND" -eq 1 ]]; then
|
||||
backup_type="online"
|
||||
elif [[ "$vm_state" == "shut off" ]]; then # Correct check for "shut off" state
|
||||
backup_type="offline"
|
||||
else
|
||||
echo "VM '$VM_NAME' backup failed: Unknown state."
|
||||
exit 1
|
||||
fi
|
||||
disks=$(virsh dumpxml "$VM_NAME" | xmllint --xpath '//disk[not(@device="cdrom")]/source/@file' -)
|
||||
for disk in $disks; do
|
||||
disk=$(echo "$disk" | awk -F'"' '{print $2}')
|
||||
disk_name=$(basename "$disk")
|
||||
echo "Backing up $disk_name"
|
||||
backup_file="$BACKUP_DIR/${BACKUP_NAME}.tar.gz"
|
||||
cp "$disk" "$VM_TEMP_DIR/$disk_name"
|
||||
echo "$disk_name backed up."
|
||||
done
|
||||
echo "Backing up XML file."
|
||||
vm_xml_file="/home/kontrolvm/xmls/${VM_NAME}.xml"
|
||||
/usr/bin/virsh dumpxml "$VM_NAME" --security-info > "$VM_TEMP_DIR/$VM_NAME.xml"
|
||||
echo "Creating compressed backup file."
|
||||
tar -czvf "$backup_file" -C "$TEMP_DIR" "$VM_NAME"
|
||||
echo "Compressed backup file created."
|
||||
rm -rf "$VM_TEMP_DIR"
|
||||
if [[ "$backup_type" == "suspended" ]]; then
|
||||
virsh resume "$VM_NAME"
|
||||
fi
|
||||
|
||||
echo "VM '$VM_NAME' backup completed."
|
||||
fi
|
85
scripts/restore_vm.sh
Normal file
85
scripts/restore_vm.sh
Normal file
|
@ -0,0 +1,85 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Configuration (adjust as needed)
|
||||
BACKUP_DIR="/home/kontrolvm/kvm_backups"
|
||||
DISK_LOCATION="/home/kontrolvm/data"
|
||||
XML_DIR="/home/kontrolvm/xmls"
|
||||
TEMP_DIR="/home/kontrolvm/backups_tmp/"
|
||||
mkdir -p "$TEMP_DIR"
|
||||
|
||||
# Check for required arguments
|
||||
if [ $# -ne 3 ]; then
|
||||
echo "Usage: $0 <backup_file.tar.gz> <new_vm_name> <vnc_port>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BACKUP_FILE_ARG="$1"
|
||||
NEW_VM_NAME="$2"
|
||||
VNC_PORT_NAME="$3"
|
||||
|
||||
# Check if the argument includes the full path or just the filename
|
||||
if [[ "$BACKUP_FILE_ARG" == /* ]]; then # Check if it starts with a / (full path)
|
||||
BACKUP_FILE="$BACKUP_FILE_ARG"
|
||||
else
|
||||
BACKUP_FILE="$BACKUP_DIR/$BACKUP_FILE_ARG" # Prepend BACKUP_DIR
|
||||
fi
|
||||
|
||||
OLD_VM_NAME=$(basename "$BACKUP_FILE" | cut -d '.' -f 1 | cut -d '_' -f 1) # Extract before timestamp
|
||||
VM_TEMP_DIR="$TEMP_DIR/$OLD_VM_NAME"
|
||||
|
||||
# Check if the backup file exists
|
||||
if [ ! -f "$BACKUP_FILE" ]; then
|
||||
echo "Error: Backup file '$BACKUP_FILE' not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract the archive
|
||||
tar -xzf "$BACKUP_FILE" -C "$TEMP_DIR"
|
||||
|
||||
# Find the disk image file(s) and the XML file
|
||||
disk_image_files=$(find "$VM_TEMP_DIR" -type f -name "*.img" -o -name "*.qcow2")
|
||||
vm_xml_file=$(find "$VM_TEMP_DIR" -name "*.xml") # Find the XML file
|
||||
|
||||
# Check if the XML file and any disk images were found
|
||||
if [ -z "$vm_xml_file" ] || [ -z "$disk_image_files" ]; then
|
||||
echo "Error: XML configuration file or disk image files not found in the archive."
|
||||
rm -rf "$VM_TEMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Loop through the disk images and restore them
|
||||
disk_counter=1 # Counter for disk numbering
|
||||
for disk_image_file in $disk_image_files; do
|
||||
disk_name=$(basename "$disk_image_file")
|
||||
disk_path_new="$DISK_LOCATION/${NEW_VM_NAME}-disk${disk_counter}.${disk_name##*.}" # Use NEW_VM_NAME and numbered disks
|
||||
|
||||
# Restore the disk image (copy or convert if needed)
|
||||
if [[ "$disk_name" == *.qcow2 ]]; then
|
||||
cp "$disk_image_file" "$disk_path_new"
|
||||
elif [[ "$disk_name" == *.img ]]; then # Assuming .img is raw format
|
||||
qemu-img convert -f raw -O qcow2 "$disk_image_file" "$disk_path_new"
|
||||
else
|
||||
echo "Unknown disk format: $disk_name. Skipping."
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Disk '$disk_name' restored to '$disk_path_new'."
|
||||
disk_counter=$((disk_counter + 1)) # Increment disk counter
|
||||
done
|
||||
|
||||
# Build and define the new XML file
|
||||
NEW_XML_FILE="$XML_DIR/$NEW_VM_NAME.xml"
|
||||
sed "s/$OLD_VM_NAME/$NEW_VM_NAME/" "$vm_xml_file" > "$NEW_XML_FILE"
|
||||
NEW_UUID=$(uuidgen)
|
||||
sed -i "s/<uuid>.*<\/uuid>/<uuid>$NEW_UUID<\/uuid>/" "$NEW_XML_FILE"
|
||||
sed -i "s/type='vnc' port='\([0-9]*\)'/type='vnc' port='$VNC_PORT_NAME'/g" "$NEW_XML_FILE"
|
||||
NEW_MAC=$(printf "%02x:%02x:%02x:%02x:%02x:%02x" $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256)))
|
||||
NEW_MAC=$(echo "$NEW_MAC" | sed 's/:\(.\)$/:\1/')
|
||||
sed -i "s/address='[0-9a-fA-F:]*'/address='$NEW_MAC'/" "$NEW_XML_FILE"
|
||||
virsh define "$NEW_XML_FILE"
|
||||
virsh start "$NEW_VM_NAME"
|
||||
|
||||
# Clean up the temporary directory
|
||||
rm -rf "$VM_TEMP_DIR"
|
||||
|
||||
echo "VM '$NEW_VM_NAME' restored."
|
|
@ -1,26 +1,32 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "Installing required software via yum..."
|
||||
exec 1>kontrolvm.log 2>&1
|
||||
|
||||
echo "Kicking off the setup script, you can check the kontrolvm.log file for more details." > /dev/tty
|
||||
|
||||
echo "Installing required software via yum (this might take a while)..." > /dev/tty
|
||||
yum install epel-release -y
|
||||
/usr/bin/crb enable
|
||||
yum install -y wget gcc make tar bind-utils zlib-devel openssl-devel pam pam-devel krb5-devel ncurses-devel e4fsprogs openssh-clients rrdtool smartmontools bridge-utils qemu-kvm libvirt virt-manager virt-install virt-top libguestfs-tools virt-viewer libvirt-daemon-kvm novnc ncurses-compat-libs iptables-services unzip net-tools
|
||||
yum install --skip-broken -y wget gcc make tar bind-utils zlib-devel openssl-devel pam pam-devel krb5-devel ncurses-devel e4fsprogs openssh-clients rrdtool smartmontools bridge-utils qemu-kvm libvirt virt-manager virt-install virt-top libguestfs-tools virt-viewer libvirt-daemon-kvm novnc ncurses-compat-libs iptables-services unzip net-tools
|
||||
|
||||
echo "Setting up KontrolVM user..."
|
||||
echo "Setting up KontrolVM user..." > /dev/tty
|
||||
adduser kontrolvm
|
||||
chown -R kontrolvm:kontrolvm /home/kontrolvm
|
||||
/usr/bin/setfacl -m u:qemu:rx /home/kontrolvm
|
||||
echo "kontrolvm ALL = NOPASSWD: /usr/bin/virsh, /usr/bin/virt-install, /sbin/iptables, /sbin/ip6tables, /sbin/ebtables, /bin/sh, /bin/sed, /sbin/ifconfig, /usr/bin/qemu-img, /home/kontrolvm/destroyvps.sh, /usr/bin/test, /usr/bin/novnc_proxy, /usr/bin/nohup, /usr/sbin/dmidecode" | sudo EDITOR='tee -a' visudo
|
||||
|
||||
echo "Disabling firewalld..."
|
||||
echo "Disabling firewalld..." > /dev/tty
|
||||
systemctl stop firewalld
|
||||
systemctl disable firewalld
|
||||
systemctl mask firewalld
|
||||
|
||||
echo "Enabling iptables services..."
|
||||
echo "Enabling iptables services..." > /dev/tty
|
||||
systemctl enable --now iptables
|
||||
systemctl enable --now ip6tables
|
||||
iptables -F
|
||||
service iptables save
|
||||
|
||||
echo "Configuring network..."
|
||||
echo "Configuring network..." > /dev/tty
|
||||
echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.conf
|
||||
echo 'net.ipv6.conf.default.forwarding = 1' >> /etc/sysctl.conf
|
||||
echo 'net.ipv6.conf.all.forwarding = 1' >> /etc/sysctl.conf
|
||||
|
@ -47,13 +53,12 @@ INTERFACE_NAME=`ip a show | grep 'state UP' | awk 'NR==1{print $2}' | sed 's/:$/
|
|||
/usr/bin/nmcli connection up br0
|
||||
echo "allow all" > /etc/qemu-kvm/bridge.conf
|
||||
|
||||
echo "Updating sshd Config..."
|
||||
echo "Updating sshd Config..." > /dev/tty
|
||||
echo "Match User kontrolvm" >> /etc/ssh/sshd_config
|
||||
echo " PasswordAuthentication no" >> /etc/ssh/sshd_config
|
||||
|
||||
echo "Creating directories/files for KontrolVM..."
|
||||
echo "Creating directories/files for KontrolVM..." > /dev/tty
|
||||
cd
|
||||
touch /var/www/html/index.html
|
||||
mkdir /home/kontrolvm/.ssh
|
||||
touch /home/kontrolvm/.ssh/authorized_keys
|
||||
mkdir /home/kontrolvm/tc
|
||||
|
@ -64,10 +69,12 @@ mkdir /home/kontrolvm/data
|
|||
mkdir /home/kontrolvm/iow
|
||||
mkdir /home/kontrolvm/isos
|
||||
mkdir /home/kontrolvm/xmls
|
||||
mkdir /home/kontrolvm/kvm_backups
|
||||
mkdir /home/kontrolvm/backups_tmp
|
||||
touch /home/kontrolvm/ip4
|
||||
touch /home/kontrolvm/ip6
|
||||
|
||||
echo "Configuring KontrolVM..."
|
||||
echo "Configuring KontrolVM (this might take a while)..." > /dev/tty
|
||||
cd /home/kontrolvm/
|
||||
wget -N https://raw.githubusercontent.com/KuJoe/kontrolvm/refs/heads/main/scripts/killconsole.sh
|
||||
wget -N https://raw.githubusercontent.com/KuJoe/kontrolvm/refs/heads/main/scripts/buildnet.sh
|
||||
|
@ -76,6 +83,8 @@ wget -N https://raw.githubusercontent.com/KuJoe/kontrolvm/refs/heads/main/script
|
|||
wget -N https://raw.githubusercontent.com/KuJoe/kontrolvm/refs/heads/main/scripts/iolimits.sh
|
||||
wget -N https://raw.githubusercontent.com/KuJoe/kontrolvm/refs/heads/main/scripts/tc_start.sh
|
||||
wget -N https://raw.githubusercontent.com/KuJoe/kontrolvm/refs/heads/main/scripts/tc_stop.sh
|
||||
wget -N https://raw.githubusercontent.com/KuJoe/kontrolvm/refs/heads/main/scripts/backup_vm.sh
|
||||
wget -N https://raw.githubusercontent.com/KuJoe/kontrolvm/refs/heads/main/scripts/restore_vm.sh
|
||||
wget -O /home/kontrolvm/isos/systemrescue-amd64.iso https://sourceforge.net/projects/systemrescuecd/files/sysresccd-x86/11.03/systemrescue-11.03-amd64.iso/download
|
||||
echo "kontrolvm_version=0.1" > /home/kontrolvm/conf/kontrolvm.conf
|
||||
echo '#!/bin/sh' > /home/kontrolvm/create_console.sh
|
||||
|
@ -85,14 +94,14 @@ echo " " >> /home/kontrolvm/create_console.sh
|
|||
/usr/bin/openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=US/ST=Online/L=Web/O=Web/CN=example.com" -keyout /home/kontrolvm/key.pem -out /home/kontrolvm/cert.pem
|
||||
echo 'sudo /usr/bin/nohup /usr/bin/novnc_proxy --listen $1 --vnc localhost:$2 --ssl-only --cert /home/kontrolvm/cert.pem --key /home/kontrolvm/key.pem > /dev/null 2>&1 &' >> /home/kontrolvm/create_console.sh
|
||||
|
||||
echo "Set permissions for KontrolVM..."
|
||||
echo "Set permissions for KontrolVM..." > /dev/tty
|
||||
cd
|
||||
chown -R kontrolvm:kontrolvm /home/kontrolvm
|
||||
chmod 0755 /home/kontrolvm/*.sh
|
||||
chmod 0700 /home/kontrolvm/.ssh
|
||||
chmod 0600 /home/kontrolvm/.ssh/*
|
||||
|
||||
echo "Setting up Websockify and noVNC..."
|
||||
echo "Setting up Websockify and noVNC..." > /dev/tty
|
||||
cd
|
||||
wget https://github.com/novnc/websockify/archive/refs/tags/v0.11.0.zip
|
||||
unzip v0.11.0.zip
|
||||
|
@ -100,12 +109,15 @@ rm v0.11.0.zip
|
|||
cd websockify-0.11.0/
|
||||
python3 setup.py install
|
||||
|
||||
systemctl restart sshd
|
||||
systemctl enable --now libvirtd
|
||||
|
||||
echo "Setting up cronjobs..."
|
||||
echo "Setting up cronjobs..." > /dev/tty
|
||||
echo 'MAILTO=""' >> /var/spool/cron/root
|
||||
echo '*/30 * * * * sh /home/kontrolvm/tc_start.sh' >> /var/spool/cron/root
|
||||
echo '*/5 * * * * sh /home/kontrolvm/vz_traffic.sh' >> /var/spool/cron/root
|
||||
echo '*/15 * * * * sh /home/kontrolvm/buildnet.sh' >> /var/spool/cron/root
|
||||
echo '*/15 * * * * sh /home/kontrolvm/iolimits.sh' >> /var/spool/cron/root
|
||||
echo '0 * * * * sh /home/kontrolvm/traffic.sh' >> /var/spool/cron/root
|
||||
echo '0 * * * * sh /home/kontrolvm/traffic.sh' >> /var/spool/cron/root
|
||||
|
||||
echo "KontrolVM node setup completed, a full log is available in kontrolvm.log" > /dev/tty
|
26
scripts/update.sh
Normal file
26
scripts/update.sh
Normal file
|
@ -0,0 +1,26 @@
|
|||
#!/bin/bash
|
||||
|
||||
exec 1>kontrolvm.log 2>&1
|
||||
|
||||
echo "Kicking off the update script, you can check the kontrolvm.log file for more details." > /dev/tty
|
||||
|
||||
echo "Updating KontrolVM..." > /dev/tty
|
||||
cd /home/kontrolvm/
|
||||
wget -O killconsole.sh https://raw.githubusercontent.com/KuJoe/kontrolvm/refs/heads/main/scripts/killconsole.sh
|
||||
wget -O buildnet.sh https://raw.githubusercontent.com/KuJoe/kontrolvm/refs/heads/main/scripts/buildnet.sh
|
||||
wget -O cleandata.sh https://raw.githubusercontent.com/KuJoe/kontrolvm/refs/heads/main/scripts/cleandata.sh
|
||||
wget -O destroyvps.sh https://raw.githubusercontent.com/KuJoe/kontrolvm/refs/heads/main/scripts/destroyvps.sh
|
||||
wget -O iolimits.sh https://raw.githubusercontent.com/KuJoe/kontrolvm/refs/heads/main/scripts/iolimits.sh
|
||||
wget -O tc_start.sh https://raw.githubusercontent.com/KuJoe/kontrolvm/refs/heads/main/scripts/tc_start.sh
|
||||
wget -O tc_stop.sh https://raw.githubusercontent.com/KuJoe/kontrolvm/refs/heads/main/scripts/tc_stop.sh
|
||||
wget -O backup_vm.sh https://raw.githubusercontent.com/KuJoe/kontrolvm/refs/heads/main/scripts/backup_vm.sh
|
||||
wget -O restore_vm.sh https://raw.githubusercontent.com/KuJoe/kontrolvm/refs/heads/main/scripts/restore_vm.sh
|
||||
new_version=$(/usr/bin/curl -s https://kontrolvm.com/version)
|
||||
sed -i "s/kontrolvm_version=[^[:space:]]\+/kontrolvm_version=$new_version/" /home/kontrolvm/conf/kontrolvm.conf
|
||||
|
||||
echo "Set permissions for KontrolVM..." > /dev/tty
|
||||
chmod 0755 /home/kontrolvm/*.sh
|
||||
chmod 0700 /home/kontrolvm/.ssh
|
||||
chmod 0600 /home/kontrolvm/.ssh/*
|
||||
|
||||
echo "KontrolVM node update completed, a full log is available in kontrolvm.log" > /dev/tty
|
161
settings.php
161
settings.php
|
@ -2,14 +2,15 @@
|
|||
/** KontrolVM By KuJoe (https://github.com/KuJoe/kontrolvm) **/
|
||||
|
||||
session_start();
|
||||
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
} else {
|
||||
define('AmAllowed', TRUE);
|
||||
require_once('config.php');
|
||||
require_once('functions.php');
|
||||
$loggedin_id = $_SESSION['staff_id'];
|
||||
$loggedin_id = (int)$_SESSION['staff_id'];
|
||||
$myrole = (int)$_SESSION["staff_role"];
|
||||
$chkActive = checkActive($loggedin_id);
|
||||
$chkLocked = checkLockedOut($loggedin_id);
|
||||
if($chkLocked == true OR $chkActive == false) {
|
||||
|
@ -17,6 +18,16 @@ if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
|||
exit;
|
||||
}
|
||||
}
|
||||
if($bgupdate == true) {
|
||||
$bgustate = " checked";
|
||||
} else {
|
||||
$bgustate = "";
|
||||
}
|
||||
if($smtp_tls == true) {
|
||||
$smtp_tls = " checked";
|
||||
} else {
|
||||
$smtp_tls = "";
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
@ -30,85 +41,99 @@ if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
|||
<label class="logo"><a href="index.php"><img src="assets/logo.png" alt="KontrolVM Logo"></a></label>
|
||||
<ul>
|
||||
<li><a href="index.php">Dashboard</a></li>
|
||||
<li><a href="nodes.php">Nodes</a></li>
|
||||
<li><a href="vms.php">VMs</a></li>
|
||||
<li><a href="users.php">Users</a></li>
|
||||
<?php if(in_array($myrole, ['2', '9'])) { ?> <li><a href="clusters.php">Infrastructure</a></li> <?php } ?>
|
||||
<?php if(in_array($myrole, ['1', '9'])) { ?> <li><a href="users.php">Users</a></li> <?php } ?>
|
||||
<li><a class="active" href="settings.php">Settings</a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo $_SESSION["username"]; ?></a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo htmlspecialchars($_SESSION["username"]); ?></a></li>
|
||||
<li><a href="logout.php"><i class="fa fa-sign-out" aria-hidden="true"></i></a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<ul class="submenu">
|
||||
<li><a class="active" href="settings.php">General</a></li>
|
||||
<li><a href="clusters.php">Clusters</a></li>
|
||||
<li><a href="isos.php">ISOs</a></li>
|
||||
<li><a href="ipv4.php">IPv4 Addresses</a></li>
|
||||
<li><a href="ipv6.php">IPv6 Addresses</a></li>
|
||||
<li><a href="logs.php">Logs</a></li>
|
||||
</ul>
|
||||
<div class="container">
|
||||
<h1>Settings</h1>
|
||||
|
||||
<form action="#" method="post">
|
||||
|
||||
<h2>General</h2>
|
||||
<div class="form-group">
|
||||
<label for="hostname">Hostname:</label>
|
||||
<input type="text" id="hostname" name="hostname" value="kvm-cluster-01" disabled>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="timezone">Timezone:</label>
|
||||
<select id="timezone" name="timezone" disabled>
|
||||
<option value="UTC">UTC</option>
|
||||
<option value="EST" selected>EST</option>
|
||||
<option value="PST">PST</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<h2>Network</h2>
|
||||
<div class="form-group">
|
||||
<label for="dns-server">DNS Server:</label>
|
||||
<input type="text" id="dns-server" name="dns-server" value="8.8.8.8" disabled>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ntp-server">NTP Server:</label>
|
||||
<input type="text" id="ntp-server" name="ntp-server" value="time.google.com" disabled>
|
||||
</div>
|
||||
|
||||
<h2>Security</h2>
|
||||
<div class="form-group">
|
||||
<label for="password">SSH Password:</label>
|
||||
<input type="password" id="password" name="password" value="12345" disabled>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ssh-port">SSH Port:</label>
|
||||
<input type="number" id="ssh-port" name="ssh-port" value="22" disabled>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="stylish-button">Save Changes</button>
|
||||
</form>
|
||||
<p>These are the current settings inside the config.php file, eventually most will be moved into the database.<br /><br /></p>
|
||||
<h2>Core</h2><br />
|
||||
<div class="form-group" style="width:200px;">
|
||||
<label for="database">Database:</label>
|
||||
<input type="text" id="database" name="database" value="<?php echo htmlspecialchars($db_file_path); ?>" readonly>
|
||||
</div>
|
||||
<div class="form-group" style="width:200px;">
|
||||
<label for="sshusernow">SSH Username:</label>
|
||||
<input type="text" id="sshusernow" name="sshusernow" value="<?php echo htmlspecialchars($sshusernow); ?>" readonly>
|
||||
</div>
|
||||
<div class="form-group" style="width:200px;">
|
||||
<label for="sshkeypriv">SSH Private Key:</label>
|
||||
<input type="text" id="sshkeypriv" name="sshkeypriv" value="<?php echo htmlspecialchars($sshkeypriv); ?>" readonly>
|
||||
</div>
|
||||
<div class="form-group" style="width:200px;">
|
||||
<label for="sshkeypub">SSH Public Key:</label>
|
||||
<input type="text" id="sshkeypub" name="sshkeypub" value="<?php echo htmlspecialchars($sshkeypub); ?>" readonly>
|
||||
</div>
|
||||
<div class="form-group" style="width:200px;">
|
||||
<label for="cryptkey">Cryptography Key:</label>
|
||||
<input type="password" id="cryptkey" name="cryptkey" value="<?php echo htmlspecialchars($cryptkey); ?>" readonly>
|
||||
</div>
|
||||
<div class="form-group" style="width:210px;">
|
||||
<label for="bgupdate" style="float:left;">Background Updates: </label>
|
||||
<label class="checkbox-container" style="float:right;">
|
||||
<input type="checkbox" name="bgupdate"<?php echo $bgustate; ?> disabled>
|
||||
<span class="checkmark"></span>
|
||||
</label>
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<hr />
|
||||
<br />
|
||||
<h2>SMTP</h2><br />
|
||||
<div class="form-group" style="width:200px;">
|
||||
<label for="smtp_server">SMTP Server:</label>
|
||||
<input type="text" id="smtp_server" name="smtp_server" value="<?php echo htmlspecialchars($smtp_server); ?>" readonly>
|
||||
</div>
|
||||
<div class="form-group" style="width:200px;">
|
||||
<label for="smtp_port">SMTP Port:</label>
|
||||
<input type="text" id="smtp_port" name="smtp_port" value="<?php echo htmlspecialchars($smtp_port); ?>" readonly>
|
||||
</div>
|
||||
<div class="form-group" style="width:200px;">
|
||||
<label for="smtp_user">SMTP User:</label>
|
||||
<input type="text" id="smtp_user" name="smtp_user" value="<?php echo htmlspecialchars($smtp_user); ?>" readonly>
|
||||
</div>
|
||||
<div class="form-group" style="width:200px;">
|
||||
<label for="smtp_password">SMTP Password:</label>
|
||||
<input type="password" id="smtp_password" name="smtp_password" value="<?php echo htmlspecialchars($smtp_password); ?>" readonly>
|
||||
</div>
|
||||
<div class="form-group" style="width:150px;">
|
||||
<label for="smtp_tls" style="float:left;">TLS Enabled: </label>
|
||||
<label class="checkbox-container" style="float:right;">
|
||||
<input type="checkbox" name="smtp_tls"<?php echo $tlsstate; ?> disabled>
|
||||
<span class="checkmark"></span>
|
||||
</label>
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
<div class="form-group" style="width:200px;">
|
||||
<label for="smtp_sender">SMTP Sender:</label>
|
||||
<input type="text" id="smtp_sender" name="smtp_sender" value="<?php echo htmlspecialchars($smtp_sender); ?>" readonly>
|
||||
</div>
|
||||
<br />
|
||||
<hr />
|
||||
<br />
|
||||
<h2>Cloudflare Turnstile</h2><br />
|
||||
<div class="form-group" style="width:200px;">
|
||||
<label for="sitekey">Site Key:</label>
|
||||
<input type="text" id="sitekey" name="sitekey" value="<?php echo htmlspecialchars($sitekey); ?>" readonly>
|
||||
</div>
|
||||
<div class="form-group" style="width:200px;">
|
||||
<label for="secretkey">Secret Key:</label>
|
||||
<input type="password" id="secretkey" name="secretkey" value="<?php echo htmlspecialchars($secretkey); ?>" readonly>
|
||||
</div>
|
||||
</div>
|
||||
<?php include('footer.php'); ?>
|
||||
<script>
|
||||
// JavaScript code to handle theme switching (from previous responses)
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const body = document.body;
|
||||
|
||||
// Load the user's preferred theme from localStorage
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
body.classList.add('dark-mode');
|
||||
themeToggle.checked = true;
|
||||
}
|
||||
|
||||
themeToggle.addEventListener('change', () => {
|
||||
if (themeToggle.checked) {
|
||||
body.classList.add('dark-mode');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
body.classList.remove('dark-mode');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
171
update.php
Normal file
171
update.php
Normal file
|
@ -0,0 +1,171 @@
|
|||
<?PHP
|
||||
/** KontrolVM By KuJoe (https://github.com/KuJoe/kontrolvm) **/
|
||||
|
||||
session_start();
|
||||
if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
} else {
|
||||
define('AmAllowed', TRUE);
|
||||
require_once('config.php');
|
||||
require_once('functions.php');
|
||||
}
|
||||
|
||||
function fetchReleaseData($repoOwner, $repoName) {
|
||||
$apiUrl = "https://api.github.com/repos/{$repoOwner}/{$repoName}/releases/latest";
|
||||
$options = [
|
||||
'http' => [
|
||||
'method' => 'GET',
|
||||
'header' => 'User-Agent: PHP Script'
|
||||
]
|
||||
];
|
||||
$context = stream_context_create($options);
|
||||
$response = @file_get_contents($apiUrl, false, $context);
|
||||
|
||||
if (!$response) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$releaseData = json_decode($response, true);
|
||||
if (!isset($releaseData['zipball_url']) || !isset($releaseData['tag_name'])) {
|
||||
return false;
|
||||
}
|
||||
return $releaseData;
|
||||
}
|
||||
|
||||
function downloadAndExtractZip($zipUrl, $installPath) {
|
||||
$zipFilePath = tempnam(sys_get_temp_dir(), 'github_update_');
|
||||
$zipData = @file_get_contents($zipUrl);
|
||||
|
||||
if (!$zipData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file_put_contents($zipFilePath, $zipData);
|
||||
$zip = new ZipArchive;
|
||||
|
||||
if ($zip->open($zipFilePath) !== true) {
|
||||
unlink($zipFilePath);
|
||||
return false;
|
||||
}
|
||||
|
||||
$rootDirectory = $zip->getNameIndex(0);
|
||||
if (substr($rootDirectory, -1) !== '/') {
|
||||
$rootDirectory = dirname($rootDirectory) . '/';
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$entryName = $zip->getNameIndex($i);
|
||||
$localName = str_replace($rootDirectory, '', $entryName);
|
||||
|
||||
if (empty($localName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetPath = $installPath . '/' . $localName;
|
||||
if (substr($entryName, -1) === '/') {
|
||||
if (!file_exists($targetPath)) {
|
||||
mkdir($targetPath, 0755, true);
|
||||
}
|
||||
} else {
|
||||
$fileContent = $zip->getFromIndex($i);
|
||||
if ($fileContent !== false) {
|
||||
file_put_contents($targetPath, $fileContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
unlink($zipFilePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateFiles($repoOwner = "KuJoe", $repoName = "kontrolvm") {
|
||||
$releaseData = fetchReleaseData($repoOwner, $repoName);
|
||||
|
||||
if (!$releaseData) {
|
||||
return "Failed to fetch release information from GitHub.";
|
||||
}
|
||||
|
||||
$releaseVersion = preg_replace('/[a-zA-Z-]/', '', $releaseData['tag_name']);
|
||||
|
||||
if (checkVersion($releaseVersion)) {
|
||||
return "The latest release version is the same as the current version.";
|
||||
}
|
||||
|
||||
if (!downloadAndExtractZip($releaseData['zipball_url'], __DIR__)) {
|
||||
return "Failed to download or extract the zip archive.";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
$resultFiles = updateFiles();
|
||||
if($resultFiles === true) {
|
||||
$servers = getServerList('1');
|
||||
if(count($servers) > 0) {
|
||||
foreach ($servers as $server) {
|
||||
updateKontrolVMNode($server['node_id']);
|
||||
}
|
||||
}
|
||||
header("Location: update_db.php");
|
||||
exit;
|
||||
} else {
|
||||
$error = $resultFiles;
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KontrolVM</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background-color: #f4f4f7;
|
||||
}
|
||||
|
||||
.container {
|
||||
background-color: #fff;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #dc3545;
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
padding: 10px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<img src="assets/logo.png" alt="KontrolVM Logo" style="display:block;margin:0 auto;" />
|
||||
<br />
|
||||
<br />
|
||||
<?php if(isset($error)) { ?>
|
||||
<h2 style="color:red;">Update Failed</h2>
|
||||
<br />
|
||||
<div class="error-message"><?php echo $error; ?></div>
|
||||
<?php } ?>
|
||||
<br /><br />
|
||||
<p style="text-align:center;font-size:0.9em;">Powered by <a href="https://github.com/KuJoe/kontrolvm" target="_blank">KontrolVM</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,26 +0,0 @@
|
|||
<?php
|
||||
/** KontrolVM By KuJoe (https://github.com/KuJoe/kontrolvm) **/
|
||||
|
||||
if(!defined('AmAllowed')) {
|
||||
die('Error 001A');
|
||||
}
|
||||
require_once('functions.php');
|
||||
$servers = getServerList('1');
|
||||
|
||||
if (count($servers) > 0) {
|
||||
foreach ($servers as $server) {
|
||||
$node_id = $server['node_id'];
|
||||
$ipaddr = $server['ipaddr'];
|
||||
$sshport = $server['sshport'];
|
||||
$sshuser = $server['sshuser'];
|
||||
$sshkey = $server['sshkey'];
|
||||
getNodeInfo($node_id, $ipaddr, $sshport, $sshuser, $sshkey);
|
||||
getNodeStats($node_id, $ipaddr, $sshport, $sshuser, $sshkey);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
?>
|
149
update_db.php
Normal file
149
update_db.php
Normal file
|
@ -0,0 +1,149 @@
|
|||
<?PHP
|
||||
/** KontrolVM By KuJoe (https://github.com/KuJoe/kontrolvm) **/
|
||||
|
||||
session_start();
|
||||
if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
}
|
||||
function alterTableIfExists($conn, $alterQuery) {
|
||||
try {
|
||||
$conn->exec($alterQuery);
|
||||
} catch (Exception $e) {
|
||||
// Check if the error indicates the change already exists
|
||||
if (strpos($e->getMessage(), 'duplicate column name') !== false ||
|
||||
strpos($e->getMessage(), 'duplicate table column') !== false ||
|
||||
strpos($e->getMessage(), 'duplicate column') !== false ||
|
||||
strpos($e->getMessage(), 'duplicate key name') !== false ||
|
||||
strpos($e->getMessage(), 'duplicate index name') !== false ||
|
||||
strpos($e->getMessage(), 'no such column') !== false ||
|
||||
strpos($e->getMessage(), 'already exists') !== false) {
|
||||
//echo "ALTER TABLE already exists or is a duplicate. Ignoring.\n";
|
||||
return true;
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
function updateDB() {
|
||||
try {
|
||||
include('config.php');
|
||||
$conn = new PDO("sqlite:$db_file_path");
|
||||
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
// SQL statement to alter the table
|
||||
$dropColumns = [
|
||||
'staff' => 'staff_salt', 'nodes' => 'loc', 'vms' => 'loc', 'clusters' => 'loc', 'ipv4' => 'loc', 'ipv6' => 'loc', 'ipv4' => 'reserved', 'ipv6' => 'reserved' ];
|
||||
|
||||
$addColumns = [
|
||||
'clusters' => 'deployment INTEGER', 'nodes' => 'cluster INTEGER', 'vms' => 'cluster INTEGER', 'ipv4' => 'cluster INTEGER', 'ipv6' => 'cluster INTEGER', 'staff' => 'staff_role INTEGER', 'ipv4' => 'bw INTEGER', 'ipv6' => 'bw INTEGER' ];
|
||||
|
||||
$renameColumns = [
|
||||
'clusters' => ['clusterid', 'cluster_id'], 'last_run' => ['id', 'run_id'], 'ostemplates' => ['templateid', 'template_id'], 'ipv4' => ['ipid', 'ip_id'], 'ipv6' => ['ipid', 'ip_id'] ];
|
||||
|
||||
// Drop Columns
|
||||
foreach ($dropColumns as $table => $column) {
|
||||
$alterQuery = "ALTER TABLE {$table} DROP COLUMN {$column}";
|
||||
alterTableIfExists($conn, $alterQuery);
|
||||
}
|
||||
|
||||
// Add Columns
|
||||
foreach ($addColumns as $table => $columnDefinition) {
|
||||
$alterQuery = "ALTER TABLE {$table} ADD COLUMN {$columnDefinition}";
|
||||
alterTableIfExists($conn, $alterQuery);
|
||||
}
|
||||
|
||||
// Rename Columns
|
||||
foreach ($renameColumns as $table => $columns) {
|
||||
$oldColumn = $columns[0];
|
||||
$newColumn = $columns[1];
|
||||
$alterQuery = "ALTER TABLE {$table} RENAME COLUMN {$oldColumn} TO {$newColumn}";
|
||||
alterTableIfExists($conn, $alterQuery);
|
||||
}
|
||||
|
||||
// Create new tables if they don't exist.
|
||||
$sql = "CREATE TABLE IF NOT EXISTS settings (setting_id INTEGER PRIMARY KEY AUTOINCREMENT, setting_name TEXT NOT NULL, setting_value TEXT NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS disks (disk_id INTEGER PRIMARY KEY AUTOINCREMENT, disk_name TEXT NOT NULL, disk_size INTEGER, vm_id INTEGER, node_id INTEGER, last_updated DATETIME);
|
||||
CREATE TABLE IF NOT EXISTS logs (log_id INTEGER PRIMARY KEY AUTOINCREMENT, log_message TEXT NOT NULL, straff_id INTEGER, created_at DATETIME DEFAULT CURRENT_TIMESTAMP);
|
||||
CREATE TABLE IF NOT EXISTS backups (backup_id INTEGER PRIMARY KEY AUTOINCREMENT, backup_name TEXT NOT NULL, backup_size INTEGER, vm_id INTEGER, node_id INTEGER, status INTEGER, created_at DATETIME);
|
||||
CREATE TABLE IF NOT EXISTS nics (nic_id INTEGER PRIMARY KEY AUTOINCREMENT, nic_name TEXT NOT NULL, mac_address TEXT, vm_id INTEGER, node_id INTEGER, last_updated DATETIME);
|
||||
CREATE TABLE IF NOT EXISTS networks (net_id INTEGER PRIMARY KEY AUTOINCREMENT, net_name TEXT NOT NULL, node_id INTEGER, last_updated DATETIME);
|
||||
";
|
||||
$conn->exec($sql);
|
||||
return true;
|
||||
} catch (PDOException $e) {
|
||||
return "Error updating tables: ". $e->getMessage();
|
||||
}
|
||||
|
||||
$conn = null;
|
||||
}
|
||||
$resultDB = updateDB();
|
||||
if($resultDB === true) {
|
||||
$success = "✅";
|
||||
} else {
|
||||
$error = $resultDB;
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KontrolVM</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background-color: #f4f4f7;
|
||||
}
|
||||
|
||||
.container {
|
||||
background-color: #fff;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #dc3545;
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
padding: 10px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<img src="assets/logo.png" alt="KontrolVM Logo" style="display:block;margin:0 auto;" />
|
||||
<br />
|
||||
<br />
|
||||
<?php if(isset($error)) { ?>
|
||||
<h2 style="color:red;">Update Failed</h2>
|
||||
<br />
|
||||
<div class="error-message"><?php echo $error; ?></div>
|
||||
<?php } ?>
|
||||
<?php if(isset($success)) { ?>
|
||||
<h2 style="color:green;">Update Success</h2>
|
||||
<br />
|
||||
<div style="text-align:center;"><?php echo $success; ?></div>
|
||||
<p>
|
||||
</p>
|
||||
<?php } ?>
|
||||
<br /><br />
|
||||
<p style="text-align:center;font-size:0.9em;">Powered by <a href="https://github.com/KuJoe/kontrolvm" target="_blank">KontrolVM</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
64
users.php
64
users.php
|
@ -2,39 +2,46 @@
|
|||
/** KontrolVM By KuJoe (https://github.com/KuJoe/kontrolvm) **/
|
||||
|
||||
session_start();
|
||||
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
} else {
|
||||
if (isset($_GET['s'])) {
|
||||
if ($_GET['s'] == '1') {
|
||||
if(isset($_GET['s'])) {
|
||||
if($_GET['s'] == '1') {
|
||||
$success = "User created successfully.";
|
||||
} elseif ($_GET['s'] == '2') {
|
||||
} elseif($_GET['s'] == '2') {
|
||||
$success = "User deleted successfully.";
|
||||
}
|
||||
}
|
||||
define('AmAllowed', TRUE);
|
||||
require_once('config.php');
|
||||
require_once('functions.php');
|
||||
$loggedin_id = $_SESSION['staff_id'];
|
||||
$loggedin_id = (int)$_SESSION['staff_id'];
|
||||
$myrole = (int)$_SESSION["staff_role"];
|
||||
$chkActive = checkActive($loggedin_id);
|
||||
$chkLocked = checkLockedOut($loggedin_id);
|
||||
if($chkLocked == true OR $chkActive == false) {
|
||||
header("Location: logout.php");
|
||||
exit;
|
||||
}
|
||||
$chkRole = getStaffRole($loggedin_id);
|
||||
$allowedRoles = ['1', '9'];
|
||||
if(!in_array($chkRole, $allowedRoles)) {
|
||||
header("Location: home.php?s=99");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
if($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$token = $_POST["csrf_token"];
|
||||
if (validateCSRFToken($token)) {
|
||||
if(validateCSRFToken($token)) {
|
||||
$username = $_POST["username"];
|
||||
$email = $_POST["email"];
|
||||
$result = createUser($username,$email);
|
||||
if ($variable!== false) {
|
||||
$result = createUser($loggedin_id,$username,$email);
|
||||
if(!str_contains($result, "Error")) {
|
||||
$success = "User created successfully with this temporary password: ".$result;
|
||||
$_GET['state'] = "all";
|
||||
} else {
|
||||
$error = "User create failed: ".$result;
|
||||
$error = $result;
|
||||
}
|
||||
} else {
|
||||
$error = "Invalid CSRF token.";
|
||||
|
@ -63,15 +70,14 @@ if(isset($_GET['state']) AND $_GET['state'] == "all") {
|
|||
<label class="logo"><a href="index.php"><img src="assets/logo.png" alt="KontrolVM Logo"></a></label>
|
||||
<ul>
|
||||
<li><a href="index.php">Dashboard</a></li>
|
||||
<li><a href="nodes.php">Nodes</a></li>
|
||||
<li><a href="vms.php">VMs</a></li>
|
||||
<li><a href="users.php">Users</a></li>
|
||||
<?php if(in_array($myrole, ['2', '9'])) { ?> <li><a href="clusters.php">Infrastructure</a></li> <?php } ?>
|
||||
<?php if(in_array($myrole, ['1', '9'])) { ?> <li><a class="active" href="users.php">Users</a></li> <?php } ?>
|
||||
<li><a href="settings.php">Settings</a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo $_SESSION["username"]; ?></a></li>
|
||||
<li style="font-weight: bold;"><a href="account.php"><?php echo htmlspecialchars($_SESSION["username"]); ?></a></li>
|
||||
<li><a href="logout.php"><i class="fa fa-sign-out" aria-hidden="true"></i></a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="container" style="min-width:350px;max-width:1500px;">
|
||||
<div class="container" style="max-width:1500px;">
|
||||
<p style="float:right;"><button id="addBtn" class="stylish-button"><i class="fa-solid fa-square-plus"></i> CREATE USER</button></p>
|
||||
<div id="addModal" class="modal">
|
||||
<div class="modal-content">
|
||||
|
@ -89,10 +95,10 @@ if(isset($_GET['state']) AND $_GET['state'] == "all") {
|
|||
</div>
|
||||
</div>
|
||||
<h1>Users</h1>
|
||||
<?php if (isset($success)) { ?>
|
||||
<?php if(isset($success)) { ?>
|
||||
<div class="success-message"><?php echo $success; ?></div>
|
||||
<?php } ?>
|
||||
<?php if (isset($error)) { ?>
|
||||
<?php if(isset($error)) { ?>
|
||||
<div class="error-message"><?php echo $error; ?></div>
|
||||
<?php } ?>
|
||||
<div class="table-container" style="max-width:1500px;">
|
||||
|
@ -113,7 +119,7 @@ if(isset($_GET['state']) AND $_GET['state'] == "all") {
|
|||
<td class="tname"><a href="account.php?id=<?php echo $staff_id; ?>"><?php echo $username; ?></a></td>
|
||||
<td>
|
||||
<?php
|
||||
if ($user['staff_active'] == "1") {
|
||||
if($user['staff_active'] == "1") {
|
||||
echo "<img src='assets/1.png' alt='Active'>";
|
||||
} else {
|
||||
echo "<img src='assets/0.png' alt='Disabled'>";
|
||||
|
@ -131,26 +137,6 @@ if(isset($_GET['state']) AND $_GET['state'] == "all") {
|
|||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="assets/filtertable.js"></script>
|
||||
<script>
|
||||
// JavaScript code to handle theme switching (from previous responses)
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const body = document.body;
|
||||
|
||||
// Load the user's preferred theme from localStorage
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
body.classList.add('dark-mode');
|
||||
themeToggle.checked = true;
|
||||
}
|
||||
|
||||
themeToggle.addEventListener('change', () => {
|
||||
if (themeToggle.checked) {
|
||||
body.classList.add('dark-mode');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
body.classList.remove('dark-mode');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
});
|
||||
// Get the modal
|
||||
var modal = document.getElementById("addModal");
|
||||
|
||||
|
@ -172,7 +158,7 @@ if(isset($_GET['state']) AND $_GET['state'] == "all") {
|
|||
|
||||
// When the user clicks anywhere outside of the modal, close it
|
||||
window.onclick = function(event) {
|
||||
if (event.target == modal) {
|
||||
if(event.target == modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
|
17
vendor/autoload.php
vendored
17
vendor/autoload.php
vendored
|
@ -3,8 +3,21 @@
|
|||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
exit(1);
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
|
22
vendor/bacon/bacon-qr-code/LICENSE
vendored
Normal file
22
vendor/bacon/bacon-qr-code/LICENSE
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2017, Ben Scholzen 'DASPRiD'
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
57
vendor/bacon/bacon-qr-code/README.md
vendored
Normal file
57
vendor/bacon/bacon-qr-code/README.md
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
# QR Code generator
|
||||
|
||||
[](https://github.com/Bacon/BaconQrCode/actions/workflows/ci.yml)
|
||||
[](https://codecov.io/gh/Bacon/BaconQrCode)
|
||||
[](https://packagist.org/packages/bacon/bacon-qr-code)
|
||||
[](https://packagist.org/packages/bacon/bacon-qr-code)
|
||||
[](https://packagist.org/packages/bacon/bacon-qr-code)
|
||||
|
||||
|
||||
## Introduction
|
||||
BaconQrCode is a port of QR code portion of the ZXing library. It currently
|
||||
only features the encoder part, but could later receive the decoder part as
|
||||
well.
|
||||
|
||||
As the Reed Solomon codec implementation of the ZXing library performs quite
|
||||
slow in PHP, it was exchanged with the implementation by Phil Karn.
|
||||
|
||||
|
||||
## Example usage
|
||||
```php
|
||||
use BaconQrCode\Renderer\ImageRenderer;
|
||||
use BaconQrCode\Renderer\Image\ImagickImageBackEnd;
|
||||
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
|
||||
use BaconQrCode\Writer;
|
||||
|
||||
$renderer = new ImageRenderer(
|
||||
new RendererStyle(400),
|
||||
new ImagickImageBackEnd()
|
||||
);
|
||||
$writer = new Writer($renderer);
|
||||
$writer->writeFile('Hello World!', 'qrcode.png');
|
||||
```
|
||||
|
||||
## Available image renderer back ends
|
||||
BaconQrCode comes with multiple back ends for rendering images. Currently included are the following:
|
||||
|
||||
- `ImagickImageBackEnd`: renders raster images using the Imagick library
|
||||
- `SvgImageBackEnd`: renders SVG files using XMLWriter
|
||||
- `EpsImageBackEnd`: renders EPS files
|
||||
|
||||
### GDLib Renderer
|
||||
GD library has so many limitations, that GD support is not added as backend, but as separated renderer.
|
||||
Use `GDLibRenderer` instead of `ImageRenderer`. These are the limitations:
|
||||
|
||||
- Does not support gradient.
|
||||
- Does not support any curves, so you QR code is always squared.
|
||||
|
||||
Example usage:
|
||||
|
||||
```php
|
||||
use BaconQrCode\Renderer\GDLibRenderer;
|
||||
use BaconQrCode\Writer;
|
||||
|
||||
$renderer = new GDLibRenderer(400);
|
||||
$writer = new Writer($renderer);
|
||||
$writer->writeFile('Hello World!', 'qrcode.png');
|
||||
```
|
50
vendor/bacon/bacon-qr-code/composer.json
vendored
Normal file
50
vendor/bacon/bacon-qr-code/composer.json
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
"description": "BaconQrCode is a QR code generator for PHP.",
|
||||
"license": "BSD-2-Clause",
|
||||
"homepage": "https://github.com/Bacon/BaconQrCode",
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"ext-iconv": "*",
|
||||
"dasprid/enum": "^1.0.3"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-imagick": "to generate QR code images"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ben Scholzen 'DASPRiD'",
|
||||
"email": "mail@dasprids.de",
|
||||
"homepage": "https://dasprids.de/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"BaconQrCode\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"BaconQrCodeTest\\": "test/"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^10.5.11 || 11.0.4",
|
||||
"spatie/phpunit-snapshot-assertions": "^5.1.5",
|
||||
"squizlabs/php_codesniffer": "^3.9",
|
||||
"phly/keep-a-changelog": "^2.12"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"ocramius/package-versions": true,
|
||||
"php-http/discovery": true
|
||||
}
|
||||
},
|
||||
"archive": {
|
||||
"exclude": [
|
||||
"/test",
|
||||
"/phpunit.xml.dist"
|
||||
]
|
||||
}
|
||||
}
|
364
vendor/bacon/bacon-qr-code/src/Common/BitArray.php
vendored
Normal file
364
vendor/bacon/bacon-qr-code/src/Common/BitArray.php
vendored
Normal file
|
@ -0,0 +1,364 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
use BaconQrCode\Exception\InvalidArgumentException;
|
||||
use SplFixedArray;
|
||||
|
||||
/**
|
||||
* A simple, fast array of bits.
|
||||
*/
|
||||
final class BitArray
|
||||
{
|
||||
/**
|
||||
* Bits represented as an array of integers.
|
||||
*
|
||||
* @var SplFixedArray<int>
|
||||
*/
|
||||
private SplFixedArray $bits;
|
||||
|
||||
/**
|
||||
* Creates a new bit array with a given size.
|
||||
*/
|
||||
public function __construct(private int $size = 0)
|
||||
{
|
||||
$this->bits = SplFixedArray::fromArray(array_fill(0, ($this->size + 31) >> 3, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size in bits.
|
||||
*/
|
||||
public function getSize() : int
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size in bytes.
|
||||
*/
|
||||
public function getSizeInBytes() : int
|
||||
{
|
||||
return ($this->size + 7) >> 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the array has a minimum capacity.
|
||||
*/
|
||||
public function ensureCapacity(int $size) : void
|
||||
{
|
||||
if ($size > count($this->bits) << 5) {
|
||||
$this->bits->setSize(($size + 31) >> 5);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a specific bit.
|
||||
*/
|
||||
public function get(int $i) : bool
|
||||
{
|
||||
return 0 !== ($this->bits[$i >> 5] & (1 << ($i & 0x1f)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a specific bit.
|
||||
*/
|
||||
public function set(int $i) : void
|
||||
{
|
||||
$this->bits[$i >> 5] = $this->bits[$i >> 5] | 1 << ($i & 0x1f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flips a specific bit.
|
||||
*/
|
||||
public function flip(int $i) : void
|
||||
{
|
||||
$this->bits[$i >> 5] ^= 1 << ($i & 0x1f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next set bit position from a given position.
|
||||
*/
|
||||
public function getNextSet(int $from) : int
|
||||
{
|
||||
if ($from >= $this->size) {
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
$bitsOffset = $from >> 5;
|
||||
$currentBits = $this->bits[$bitsOffset];
|
||||
$bitsLength = count($this->bits);
|
||||
$currentBits &= ~((1 << ($from & 0x1f)) - 1);
|
||||
|
||||
while (0 === $currentBits) {
|
||||
if (++$bitsOffset === $bitsLength) {
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
$currentBits = $this->bits[$bitsOffset];
|
||||
}
|
||||
|
||||
$result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits);
|
||||
return min($result, $this->size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next unset bit position from a given position.
|
||||
*/
|
||||
public function getNextUnset(int $from) : int
|
||||
{
|
||||
if ($from >= $this->size) {
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
$bitsOffset = $from >> 5;
|
||||
$currentBits = ~$this->bits[$bitsOffset];
|
||||
$bitsLength = count($this->bits);
|
||||
$currentBits &= ~((1 << ($from & 0x1f)) - 1);
|
||||
|
||||
while (0 === $currentBits) {
|
||||
if (++$bitsOffset === $bitsLength) {
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
$currentBits = ~$this->bits[$bitsOffset];
|
||||
}
|
||||
|
||||
$result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits);
|
||||
return min($result, $this->size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a bulk of bits.
|
||||
*/
|
||||
public function setBulk(int $i, int $newBits) : void
|
||||
{
|
||||
$this->bits[$i >> 5] = $newBits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a range of bits.
|
||||
*
|
||||
* @throws InvalidArgumentException if end is smaller than start
|
||||
*/
|
||||
public function setRange(int $start, int $end) : void
|
||||
{
|
||||
if ($end < $start) {
|
||||
throw new InvalidArgumentException('End must be greater or equal to start');
|
||||
}
|
||||
|
||||
if ($end === $start) {
|
||||
return;
|
||||
}
|
||||
|
||||
--$end;
|
||||
|
||||
$firstInt = $start >> 5;
|
||||
$lastInt = $end >> 5;
|
||||
|
||||
for ($i = $firstInt; $i <= $lastInt; ++$i) {
|
||||
$firstBit = $i > $firstInt ? 0 : $start & 0x1f;
|
||||
$lastBit = $i < $lastInt ? 31 : $end & 0x1f;
|
||||
|
||||
if (0 === $firstBit && 31 === $lastBit) {
|
||||
$mask = 0x7fffffff;
|
||||
} else {
|
||||
$mask = 0;
|
||||
|
||||
for ($j = $firstBit; $j < $lastBit; ++$j) {
|
||||
$mask |= 1 << $j;
|
||||
}
|
||||
}
|
||||
|
||||
$this->bits[$i] = $this->bits[$i] | $mask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the bit array, unsetting every bit.
|
||||
*/
|
||||
public function clear() : void
|
||||
{
|
||||
$bitsLength = count($this->bits);
|
||||
|
||||
for ($i = 0; $i < $bitsLength; ++$i) {
|
||||
$this->bits[$i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a range of bits is set or not set.
|
||||
|
||||
* @throws InvalidArgumentException if end is smaller than start
|
||||
*/
|
||||
public function isRange(int $start, int $end, bool $value) : bool
|
||||
{
|
||||
if ($end < $start) {
|
||||
throw new InvalidArgumentException('End must be greater or equal to start');
|
||||
}
|
||||
|
||||
if ($end === $start) {
|
||||
return true;
|
||||
}
|
||||
|
||||
--$end;
|
||||
|
||||
$firstInt = $start >> 5;
|
||||
$lastInt = $end >> 5;
|
||||
|
||||
for ($i = $firstInt; $i <= $lastInt; ++$i) {
|
||||
$firstBit = $i > $firstInt ? 0 : $start & 0x1f;
|
||||
$lastBit = $i < $lastInt ? 31 : $end & 0x1f;
|
||||
|
||||
if (0 === $firstBit && 31 === $lastBit) {
|
||||
$mask = 0x7fffffff;
|
||||
} else {
|
||||
$mask = 0;
|
||||
|
||||
for ($j = $firstBit; $j <= $lastBit; ++$j) {
|
||||
$mask |= 1 << $j;
|
||||
}
|
||||
}
|
||||
|
||||
if (($this->bits[$i] & $mask) !== ($value ? $mask : 0)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a bit to the array.
|
||||
*/
|
||||
public function appendBit(bool $bit) : void
|
||||
{
|
||||
$this->ensureCapacity($this->size + 1);
|
||||
|
||||
if ($bit) {
|
||||
$this->bits[$this->size >> 5] = $this->bits[$this->size >> 5] | (1 << ($this->size & 0x1f));
|
||||
}
|
||||
|
||||
++$this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a number of bits (up to 32) to the array.
|
||||
|
||||
* @throws InvalidArgumentException if num bits is not between 0 and 32
|
||||
*/
|
||||
public function appendBits(int $value, int $numBits) : void
|
||||
{
|
||||
if ($numBits < 0 || $numBits > 32) {
|
||||
throw new InvalidArgumentException('Num bits must be between 0 and 32');
|
||||
}
|
||||
|
||||
$this->ensureCapacity($this->size + $numBits);
|
||||
|
||||
for ($numBitsLeft = $numBits; $numBitsLeft > 0; $numBitsLeft--) {
|
||||
$this->appendBit((($value >> ($numBitsLeft - 1)) & 0x01) === 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends another bit array to this array.
|
||||
*/
|
||||
public function appendBitArray(self $other) : void
|
||||
{
|
||||
$otherSize = $other->getSize();
|
||||
$this->ensureCapacity($this->size + $other->getSize());
|
||||
|
||||
for ($i = 0; $i < $otherSize; ++$i) {
|
||||
$this->appendBit($other->get($i));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes an exclusive-or comparision on the current bit array.
|
||||
*
|
||||
* @throws InvalidArgumentException if sizes don't match
|
||||
*/
|
||||
public function xorBits(self $other) : void
|
||||
{
|
||||
$bitsLength = count($this->bits);
|
||||
$otherBits = $other->getBitArray();
|
||||
|
||||
if ($bitsLength !== count($otherBits)) {
|
||||
throw new InvalidArgumentException('Sizes don\'t match');
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $bitsLength; ++$i) {
|
||||
$this->bits[$i] = $this->bits[$i] ^ $otherBits[$i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the bit array to a byte array.
|
||||
*
|
||||
* @return SplFixedArray<int>
|
||||
*/
|
||||
public function toBytes(int $bitOffset, int $numBytes) : SplFixedArray
|
||||
{
|
||||
$bytes = new SplFixedArray($numBytes);
|
||||
|
||||
for ($i = 0; $i < $numBytes; ++$i) {
|
||||
$byte = 0;
|
||||
|
||||
for ($j = 0; $j < 8; ++$j) {
|
||||
if ($this->get($bitOffset)) {
|
||||
$byte |= 1 << (7 - $j);
|
||||
}
|
||||
|
||||
++$bitOffset;
|
||||
}
|
||||
|
||||
$bytes[$i] = $byte;
|
||||
}
|
||||
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the internal bit array.
|
||||
*
|
||||
* @return SplFixedArray<int>
|
||||
*/
|
||||
public function getBitArray() : SplFixedArray
|
||||
{
|
||||
return $this->bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses the array.
|
||||
*/
|
||||
public function reverse() : void
|
||||
{
|
||||
$newBits = new SplFixedArray(count($this->bits));
|
||||
|
||||
for ($i = 0; $i < $this->size; ++$i) {
|
||||
if ($this->get($this->size - $i - 1)) {
|
||||
$newBits[$i >> 5] = $newBits[$i >> 5] | (1 << ($i & 0x1f));
|
||||
}
|
||||
}
|
||||
|
||||
$this->bits = $newBits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the bit array.
|
||||
*/
|
||||
public function __toString() : string
|
||||
{
|
||||
$result = '';
|
||||
|
||||
for ($i = 0; $i < $this->size; ++$i) {
|
||||
if (0 === ($i & 0x07)) {
|
||||
$result .= ' ';
|
||||
}
|
||||
|
||||
$result .= $this->get($i) ? 'X' : '.';
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
307
vendor/bacon/bacon-qr-code/src/Common/BitMatrix.php
vendored
Normal file
307
vendor/bacon/bacon-qr-code/src/Common/BitMatrix.php
vendored
Normal file
|
@ -0,0 +1,307 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
use BaconQrCode\Exception\InvalidArgumentException;
|
||||
use SplFixedArray;
|
||||
|
||||
/**
|
||||
* Bit matrix.
|
||||
*
|
||||
* Represents a 2D matrix of bits. In function arguments below, and throughout
|
||||
* the common module, x is the column position, and y is the row position. The
|
||||
* ordering is always x, y. The origin is at the top-left.
|
||||
*/
|
||||
class BitMatrix
|
||||
{
|
||||
/**
|
||||
* Width of the bit matrix.
|
||||
*/
|
||||
private int $width;
|
||||
|
||||
/**
|
||||
* Height of the bit matrix.
|
||||
*/
|
||||
private ?int $height;
|
||||
|
||||
/**
|
||||
* Size in bits of each individual row.
|
||||
*/
|
||||
private int $rowSize;
|
||||
|
||||
/**
|
||||
* Bits representation.
|
||||
*
|
||||
* @var SplFixedArray<int>
|
||||
*/
|
||||
private SplFixedArray $bits;
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if a dimension is smaller than zero
|
||||
*/
|
||||
public function __construct(int $width, ?int $height = null)
|
||||
{
|
||||
if (null === $height) {
|
||||
$height = $width;
|
||||
}
|
||||
|
||||
if ($width < 1 || $height < 1) {
|
||||
throw new InvalidArgumentException('Both dimensions must be greater than zero');
|
||||
}
|
||||
|
||||
$this->width = $width;
|
||||
$this->height = $height;
|
||||
$this->rowSize = ($width + 31) >> 5;
|
||||
$this->bits = SplFixedArray::fromArray(array_fill(0, $this->rowSize * $height, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the requested bit, where true means black.
|
||||
*/
|
||||
public function get(int $x, int $y) : bool
|
||||
{
|
||||
$offset = $y * $this->rowSize + ($x >> 5);
|
||||
return 0 !== (BitUtils::unsignedRightShift($this->bits[$offset], ($x & 0x1f)) & 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given bit to true.
|
||||
*/
|
||||
public function set(int $x, int $y) : void
|
||||
{
|
||||
$offset = $y * $this->rowSize + ($x >> 5);
|
||||
$this->bits[$offset] = $this->bits[$offset] | (1 << ($x & 0x1f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Flips the given bit.
|
||||
*/
|
||||
public function flip(int $x, int $y) : void
|
||||
{
|
||||
$offset = $y * $this->rowSize + ($x >> 5);
|
||||
$this->bits[$offset] = $this->bits[$offset] ^ (1 << ($x & 0x1f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all bits (set to false).
|
||||
*/
|
||||
public function clear() : void
|
||||
{
|
||||
$max = count($this->bits);
|
||||
|
||||
for ($i = 0; $i < $max; ++$i) {
|
||||
$this->bits[$i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a square region of the bit matrix to true.
|
||||
*
|
||||
* @throws InvalidArgumentException if left or top are negative
|
||||
* @throws InvalidArgumentException if width or height are smaller than 1
|
||||
* @throws InvalidArgumentException if region does not fit into the matix
|
||||
*/
|
||||
public function setRegion(int $left, int $top, int $width, int $height) : void
|
||||
{
|
||||
if ($top < 0 || $left < 0) {
|
||||
throw new InvalidArgumentException('Left and top must be non-negative');
|
||||
}
|
||||
|
||||
if ($height < 1 || $width < 1) {
|
||||
throw new InvalidArgumentException('Width and height must be at least 1');
|
||||
}
|
||||
|
||||
$right = $left + $width;
|
||||
$bottom = $top + $height;
|
||||
|
||||
if ($bottom > $this->height || $right > $this->width) {
|
||||
throw new InvalidArgumentException('The region must fit inside the matrix');
|
||||
}
|
||||
|
||||
for ($y = $top; $y < $bottom; ++$y) {
|
||||
$offset = $y * $this->rowSize;
|
||||
|
||||
for ($x = $left; $x < $right; ++$x) {
|
||||
$index = $offset + ($x >> 5);
|
||||
$this->bits[$index] = $this->bits[$index] | (1 << ($x & 0x1f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A fast method to retrieve one row of data from the matrix as a BitArray.
|
||||
*/
|
||||
public function getRow(int $y, ?BitArray $row = null) : BitArray
|
||||
{
|
||||
if (null === $row || $row->getSize() < $this->width) {
|
||||
$row = new BitArray($this->width);
|
||||
}
|
||||
|
||||
$offset = $y * $this->rowSize;
|
||||
|
||||
for ($x = 0; $x < $this->rowSize; ++$x) {
|
||||
$row->setBulk($x << 5, $this->bits[$offset + $x]);
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a row of data from a BitArray.
|
||||
*/
|
||||
public function setRow(int $y, BitArray $row) : void
|
||||
{
|
||||
$bits = $row->getBitArray();
|
||||
|
||||
for ($i = 0; $i < $this->rowSize; ++$i) {
|
||||
$this->bits[$y * $this->rowSize + $i] = $bits[$i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is useful in detecting the enclosing rectangle of a 'pure' barcode.
|
||||
*
|
||||
* @return int[]|null
|
||||
*/
|
||||
public function getEnclosingRectangle() : ?array
|
||||
{
|
||||
$left = $this->width;
|
||||
$top = $this->height;
|
||||
$right = -1;
|
||||
$bottom = -1;
|
||||
|
||||
for ($y = 0; $y < $this->height; ++$y) {
|
||||
for ($x32 = 0; $x32 < $this->rowSize; ++$x32) {
|
||||
$bits = $this->bits[$y * $this->rowSize + $x32];
|
||||
|
||||
if (0 !== $bits) {
|
||||
if ($y < $top) {
|
||||
$top = $y;
|
||||
}
|
||||
|
||||
if ($y > $bottom) {
|
||||
$bottom = $y;
|
||||
}
|
||||
|
||||
if ($x32 * 32 < $left) {
|
||||
$bit = 0;
|
||||
|
||||
while (($bits << (31 - $bit)) === 0) {
|
||||
$bit++;
|
||||
}
|
||||
|
||||
if (($x32 * 32 + $bit) < $left) {
|
||||
$left = $x32 * 32 + $bit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($x32 * 32 + 31 > $right) {
|
||||
$bit = 31;
|
||||
|
||||
while (0 === BitUtils::unsignedRightShift($bits, $bit)) {
|
||||
--$bit;
|
||||
}
|
||||
|
||||
if (($x32 * 32 + $bit) > $right) {
|
||||
$right = $x32 * 32 + $bit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$width = $right - $left;
|
||||
$height = $bottom - $top;
|
||||
|
||||
if ($width < 0 || $height < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [$left, $top, $width, $height];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the most top left set bit.
|
||||
*
|
||||
* This is useful in detecting a corner of a 'pure' barcode.
|
||||
*
|
||||
* @return int[]|null
|
||||
*/
|
||||
public function getTopLeftOnBit() : ?array
|
||||
{
|
||||
$bitsOffset = 0;
|
||||
|
||||
while ($bitsOffset < count($this->bits) && 0 === $this->bits[$bitsOffset]) {
|
||||
++$bitsOffset;
|
||||
}
|
||||
|
||||
if (count($this->bits) === $bitsOffset) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$x = intdiv($bitsOffset, $this->rowSize);
|
||||
$y = ($bitsOffset % $this->rowSize) << 5;
|
||||
|
||||
$bits = $this->bits[$bitsOffset];
|
||||
$bit = 0;
|
||||
|
||||
while (0 === ($bits << (31 - $bit))) {
|
||||
++$bit;
|
||||
}
|
||||
|
||||
$x += $bit;
|
||||
|
||||
return [$x, $y];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the most bottom right set bit.
|
||||
*
|
||||
* This is useful in detecting a corner of a 'pure' barcode.
|
||||
*
|
||||
* @return int[]|null
|
||||
*/
|
||||
public function getBottomRightOnBit() : ?array
|
||||
{
|
||||
$bitsOffset = count($this->bits) - 1;
|
||||
|
||||
while ($bitsOffset >= 0 && 0 === $this->bits[$bitsOffset]) {
|
||||
--$bitsOffset;
|
||||
}
|
||||
|
||||
if ($bitsOffset < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$x = intdiv($bitsOffset, $this->rowSize);
|
||||
$y = ($bitsOffset % $this->rowSize) << 5;
|
||||
|
||||
$bits = $this->bits[$bitsOffset];
|
||||
$bit = 0;
|
||||
|
||||
while (0 === BitUtils::unsignedRightShift($bits, $bit)) {
|
||||
--$bit;
|
||||
}
|
||||
|
||||
$x += $bit;
|
||||
|
||||
return [$x, $y];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the width of the matrix,
|
||||
*/
|
||||
public function getWidth() : int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the height of the matrix.
|
||||
*/
|
||||
public function getHeight() : int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
}
|
41
vendor/bacon/bacon-qr-code/src/Common/BitUtils.php
vendored
Normal file
41
vendor/bacon/bacon-qr-code/src/Common/BitUtils.php
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
/**
|
||||
* General bit utilities.
|
||||
*
|
||||
* All utility methods are based on 32-bit integers and also work on 64-bit
|
||||
* systems.
|
||||
*/
|
||||
final class BitUtils
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an unsigned right shift.
|
||||
*
|
||||
* This is the same as the unsigned right shift operator ">>>" in other
|
||||
* languages.
|
||||
*/
|
||||
public static function unsignedRightShift(int $a, int $b) : int
|
||||
{
|
||||
return (
|
||||
$a >= 0
|
||||
? $a >> $b
|
||||
: (($a & 0x7fffffff) >> $b) | (0x40000000 >> ($b - 1))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of trailing zeros.
|
||||
*/
|
||||
public static function numberOfTrailingZeros(int $i) : int
|
||||
{
|
||||
$lastPos = strrpos(str_pad(decbin($i), 32, '0', STR_PAD_LEFT), '1');
|
||||
return $lastPos === false ? 32 : 31 - $lastPos;
|
||||
}
|
||||
}
|
177
vendor/bacon/bacon-qr-code/src/Common/CharacterSetEci.php
vendored
Normal file
177
vendor/bacon/bacon-qr-code/src/Common/CharacterSetEci.php
vendored
Normal file
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
use BaconQrCode\Exception\InvalidArgumentException;
|
||||
use DASPRiD\Enum\AbstractEnum;
|
||||
|
||||
/**
|
||||
* Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1 of ISO 18004.
|
||||
*
|
||||
* @method static self CP437()
|
||||
* @method static self ISO8859_1()
|
||||
* @method static self ISO8859_2()
|
||||
* @method static self ISO8859_3()
|
||||
* @method static self ISO8859_4()
|
||||
* @method static self ISO8859_5()
|
||||
* @method static self ISO8859_6()
|
||||
* @method static self ISO8859_7()
|
||||
* @method static self ISO8859_8()
|
||||
* @method static self ISO8859_9()
|
||||
* @method static self ISO8859_10()
|
||||
* @method static self ISO8859_11()
|
||||
* @method static self ISO8859_12()
|
||||
* @method static self ISO8859_13()
|
||||
* @method static self ISO8859_14()
|
||||
* @method static self ISO8859_15()
|
||||
* @method static self ISO8859_16()
|
||||
* @method static self SJIS()
|
||||
* @method static self CP1250()
|
||||
* @method static self CP1251()
|
||||
* @method static self CP1252()
|
||||
* @method static self CP1256()
|
||||
* @method static self UNICODE_BIG_UNMARKED()
|
||||
* @method static self UTF8()
|
||||
* @method static self ASCII()
|
||||
* @method static self BIG5()
|
||||
* @method static self GB18030()
|
||||
* @method static self EUC_KR()
|
||||
*/
|
||||
final class CharacterSetEci extends AbstractEnum
|
||||
{
|
||||
protected const CP437 = [[0, 2]];
|
||||
protected const ISO8859_1 = [[1, 3], 'ISO-8859-1'];
|
||||
protected const ISO8859_2 = [[4], 'ISO-8859-2'];
|
||||
protected const ISO8859_3 = [[5], 'ISO-8859-3'];
|
||||
protected const ISO8859_4 = [[6], 'ISO-8859-4'];
|
||||
protected const ISO8859_5 = [[7], 'ISO-8859-5'];
|
||||
protected const ISO8859_6 = [[8], 'ISO-8859-6'];
|
||||
protected const ISO8859_7 = [[9], 'ISO-8859-7'];
|
||||
protected const ISO8859_8 = [[10], 'ISO-8859-8'];
|
||||
protected const ISO8859_9 = [[11], 'ISO-8859-9'];
|
||||
protected const ISO8859_10 = [[12], 'ISO-8859-10'];
|
||||
protected const ISO8859_11 = [[13], 'ISO-8859-11'];
|
||||
protected const ISO8859_12 = [[14], 'ISO-8859-12'];
|
||||
protected const ISO8859_13 = [[15], 'ISO-8859-13'];
|
||||
protected const ISO8859_14 = [[16], 'ISO-8859-14'];
|
||||
protected const ISO8859_15 = [[17], 'ISO-8859-15'];
|
||||
protected const ISO8859_16 = [[18], 'ISO-8859-16'];
|
||||
protected const SJIS = [[20], 'Shift_JIS'];
|
||||
protected const CP1250 = [[21], 'windows-1250'];
|
||||
protected const CP1251 = [[22], 'windows-1251'];
|
||||
protected const CP1252 = [[23], 'windows-1252'];
|
||||
protected const CP1256 = [[24], 'windows-1256'];
|
||||
protected const UNICODE_BIG_UNMARKED = [[25], 'UTF-16BE', 'UnicodeBig'];
|
||||
protected const UTF8 = [[26], 'UTF-8'];
|
||||
protected const ASCII = [[27, 170], 'US-ASCII'];
|
||||
protected const BIG5 = [[28]];
|
||||
protected const GB18030 = [[29], 'GB2312', 'EUC_CN', 'GBK'];
|
||||
protected const EUC_KR = [[30], 'EUC-KR'];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $otherEncodingNames;
|
||||
|
||||
/**
|
||||
* @var array<int, self>|null
|
||||
*/
|
||||
private static ?array $valueToEci;
|
||||
|
||||
/**
|
||||
* @var array<string, self>|null
|
||||
*/
|
||||
private static ?array $nameToEci = null;
|
||||
|
||||
/**
|
||||
* @param int[] $values
|
||||
*/
|
||||
public function __construct(private readonly array $values, string ...$otherEncodingNames)
|
||||
{
|
||||
$this->otherEncodingNames = $otherEncodingNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the primary value.
|
||||
*/
|
||||
public function getValue() : int
|
||||
{
|
||||
return $this->values[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets character set ECI by value.
|
||||
*
|
||||
* Returns the representing ECI of a given value, or null if it is legal but unsupported.
|
||||
*
|
||||
* @throws InvalidArgumentException if value is not between 0 and 900
|
||||
*/
|
||||
public static function getCharacterSetEciByValue(int $value) : ?self
|
||||
{
|
||||
if ($value < 0 || $value >= 900) {
|
||||
throw new InvalidArgumentException('Value must be between 0 and 900');
|
||||
}
|
||||
|
||||
$valueToEci = self::valueToEci();
|
||||
|
||||
if (! array_key_exists($value, $valueToEci)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $valueToEci[$value];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns character set ECI by name.
|
||||
*
|
||||
* Returns the representing ECI of a given name, or null if it is legal but unsupported
|
||||
*/
|
||||
public static function getCharacterSetEciByName(string $name) : ?self
|
||||
{
|
||||
$nameToEci = self::nameToEci();
|
||||
$name = strtolower($name);
|
||||
|
||||
if (! array_key_exists($name, $nameToEci)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $nameToEci[$name];
|
||||
}
|
||||
|
||||
private static function valueToEci() : array
|
||||
{
|
||||
if (null !== self::$valueToEci) {
|
||||
return self::$valueToEci;
|
||||
}
|
||||
|
||||
self::$valueToEci = [];
|
||||
|
||||
foreach (self::values() as $eci) {
|
||||
foreach ($eci->values as $value) {
|
||||
self::$valueToEci[$value] = $eci;
|
||||
}
|
||||
}
|
||||
|
||||
return self::$valueToEci;
|
||||
}
|
||||
|
||||
private static function nameToEci() : array
|
||||
{
|
||||
if (null !== self::$nameToEci) {
|
||||
return self::$nameToEci;
|
||||
}
|
||||
|
||||
self::$nameToEci = [];
|
||||
|
||||
foreach (self::values() as $eci) {
|
||||
self::$nameToEci[strtolower($eci->name())] = $eci;
|
||||
|
||||
foreach ($eci->otherEncodingNames as $name) {
|
||||
self::$nameToEci[strtolower($name)] = $eci;
|
||||
}
|
||||
}
|
||||
|
||||
return self::$nameToEci;
|
||||
}
|
||||
}
|
33
vendor/bacon/bacon-qr-code/src/Common/EcBlock.php
vendored
Normal file
33
vendor/bacon/bacon-qr-code/src/Common/EcBlock.php
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
/**
|
||||
* Encapsulates the parameters for one error-correction block in one symbol version.
|
||||
*
|
||||
* This includes the number of data codewords, and the number of times a block with these parameters is used
|
||||
* consecutively in the QR code version's format.
|
||||
*/
|
||||
final class EcBlock
|
||||
{
|
||||
public function __construct(private readonly int $count, private readonly int $dataCodewords)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how many times the block is used.
|
||||
*/
|
||||
public function getCount() : int
|
||||
{
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of data codewords.
|
||||
*/
|
||||
public function getDataCodewords() : int
|
||||
{
|
||||
return $this->dataCodewords;
|
||||
}
|
||||
}
|
66
vendor/bacon/bacon-qr-code/src/Common/EcBlocks.php
vendored
Normal file
66
vendor/bacon/bacon-qr-code/src/Common/EcBlocks.php
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
/**
|
||||
* Encapsulates a set of error-correction blocks in one symbol version.
|
||||
*
|
||||
* Most versions will use blocks of differing sizes within one version, so, this encapsulates the parameters for each
|
||||
* set of blocks. It also holds the number of error-correction codewords per block since it will be the same across all
|
||||
* blocks within one version.
|
||||
*/
|
||||
final class EcBlocks
|
||||
{
|
||||
/**
|
||||
* List of EC blocks.
|
||||
*
|
||||
* @var EcBlock[]
|
||||
*/
|
||||
private array $ecBlocks;
|
||||
|
||||
public function __construct(private readonly int $ecCodewordsPerBlock, EcBlock ...$ecBlocks)
|
||||
{
|
||||
$this->ecBlocks = $ecBlocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of EC codewords per block.
|
||||
*/
|
||||
public function getEcCodewordsPerBlock() : int
|
||||
{
|
||||
return $this->ecCodewordsPerBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of EC block appearances.
|
||||
*/
|
||||
public function getNumBlocks() : int
|
||||
{
|
||||
$total = 0;
|
||||
|
||||
foreach ($this->ecBlocks as $ecBlock) {
|
||||
$total += $ecBlock->getCount();
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total count of EC codewords.
|
||||
*/
|
||||
public function getTotalEcCodewords() : int
|
||||
{
|
||||
return $this->ecCodewordsPerBlock * $this->getNumBlocks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the EC blocks included in this collection.
|
||||
*
|
||||
* @return EcBlock[]
|
||||
*/
|
||||
public function getEcBlocks() : array
|
||||
{
|
||||
return $this->ecBlocks;
|
||||
}
|
||||
}
|
57
vendor/bacon/bacon-qr-code/src/Common/ErrorCorrectionLevel.php
vendored
Normal file
57
vendor/bacon/bacon-qr-code/src/Common/ErrorCorrectionLevel.php
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
use BaconQrCode\Exception\OutOfBoundsException;
|
||||
use DASPRiD\Enum\AbstractEnum;
|
||||
|
||||
/**
|
||||
* Enum representing the four error correction levels.
|
||||
*
|
||||
* @method static self L() ~7% correction
|
||||
* @method static self M() ~15% correction
|
||||
* @method static self Q() ~25% correction
|
||||
* @method static self H() ~30% correction
|
||||
*/
|
||||
final class ErrorCorrectionLevel extends AbstractEnum
|
||||
{
|
||||
protected const L = [0x01];
|
||||
protected const M = [0x00];
|
||||
protected const Q = [0x03];
|
||||
protected const H = [0x02];
|
||||
|
||||
protected function __construct(private readonly int $bits)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws OutOfBoundsException if number of bits is invalid
|
||||
*/
|
||||
public static function forBits(int $bits) : self
|
||||
{
|
||||
switch ($bits) {
|
||||
case 0:
|
||||
return self::M();
|
||||
|
||||
case 1:
|
||||
return self::L();
|
||||
|
||||
case 2:
|
||||
return self::H();
|
||||
|
||||
case 3:
|
||||
return self::Q();
|
||||
}
|
||||
|
||||
throw new OutOfBoundsException('Invalid number of bits');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the two bits used to encode this error correction level.
|
||||
*/
|
||||
public function getBits() : int
|
||||
{
|
||||
return $this->bits;
|
||||
}
|
||||
}
|
196
vendor/bacon/bacon-qr-code/src/Common/FormatInformation.php
vendored
Normal file
196
vendor/bacon/bacon-qr-code/src/Common/FormatInformation.php
vendored
Normal file
|
@ -0,0 +1,196 @@
|
|||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
/**
|
||||
* Encapsulates a QR Code's format information, including the data mask used and error correction level.
|
||||
*/
|
||||
class FormatInformation
|
||||
{
|
||||
/**
|
||||
* Mask for format information.
|
||||
*/
|
||||
private const FORMAT_INFO_MASK_QR = 0x5412;
|
||||
|
||||
/**
|
||||
* Lookup table for decoding format information.
|
||||
*
|
||||
* See ISO 18004:2006, Annex C, Table C.1
|
||||
*/
|
||||
private const FORMAT_INFO_DECODE_LOOKUP = [
|
||||
[0x5412, 0x00],
|
||||
[0x5125, 0x01],
|
||||
[0x5e7c, 0x02],
|
||||
[0x5b4b, 0x03],
|
||||
[0x45f9, 0x04],
|
||||
[0x40ce, 0x05],
|
||||
[0x4f97, 0x06],
|
||||
[0x4aa0, 0x07],
|
||||
[0x77c4, 0x08],
|
||||
[0x72f3, 0x09],
|
||||
[0x7daa, 0x0a],
|
||||
[0x789d, 0x0b],
|
||||
[0x662f, 0x0c],
|
||||
[0x6318, 0x0d],
|
||||
[0x6c41, 0x0e],
|
||||
[0x6976, 0x0f],
|
||||
[0x1689, 0x10],
|
||||
[0x13be, 0x11],
|
||||
[0x1ce7, 0x12],
|
||||
[0x19d0, 0x13],
|
||||
[0x0762, 0x14],
|
||||
[0x0255, 0x15],
|
||||
[0x0d0c, 0x16],
|
||||
[0x083b, 0x17],
|
||||
[0x355f, 0x18],
|
||||
[0x3068, 0x19],
|
||||
[0x3f31, 0x1a],
|
||||
[0x3a06, 0x1b],
|
||||
[0x24b4, 0x1c],
|
||||
[0x2183, 0x1d],
|
||||
[0x2eda, 0x1e],
|
||||
[0x2bed, 0x1f],
|
||||
];
|
||||
|
||||
/**
|
||||
* Offset i holds the number of 1 bits in the binary representation of i.
|
||||
*
|
||||
* @var int[]
|
||||
*/
|
||||
private const BITS_SET_IN_HALF_BYTE = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4];
|
||||
|
||||
/**
|
||||
* Error correction level.
|
||||
*/
|
||||
private ErrorCorrectionLevel $ecLevel;
|
||||
|
||||
private int $dataMask;
|
||||
|
||||
protected function __construct(int $formatInfo)
|
||||
{
|
||||
$this->ecLevel = ErrorCorrectionLevel::forBits(($formatInfo >> 3) & 0x3);
|
||||
$this->dataMask = $formatInfo & 0x7;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks how many bits are different between two integers.
|
||||
*/
|
||||
public static function numBitsDiffering(int $a, int $b) : int
|
||||
{
|
||||
$a ^= $b;
|
||||
|
||||
return (
|
||||
self::BITS_SET_IN_HALF_BYTE[$a & 0xf]
|
||||
+ self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 4) & 0xf)]
|
||||
+ self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 8) & 0xf)]
|
||||
+ self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 12) & 0xf)]
|
||||
+ self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 16) & 0xf)]
|
||||
+ self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 20) & 0xf)]
|
||||
+ self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 24) & 0xf)]
|
||||
+ self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 28) & 0xf)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes format information.
|
||||
*/
|
||||
public static function decodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self
|
||||
{
|
||||
$formatInfo = self::doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2);
|
||||
|
||||
if (null !== $formatInfo) {
|
||||
return $formatInfo;
|
||||
}
|
||||
|
||||
// Should return null, but, some QR codes apparently do not mask this info. Try again by actually masking the
|
||||
// pattern first.
|
||||
return self::doDecodeFormatInformation(
|
||||
$maskedFormatInfo1 ^ self::FORMAT_INFO_MASK_QR,
|
||||
$maskedFormatInfo2 ^ self::FORMAT_INFO_MASK_QR
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method for decoding format information.
|
||||
*/
|
||||
private static function doDecodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self
|
||||
{
|
||||
$bestDifference = PHP_INT_MAX;
|
||||
$bestFormatInfo = 0;
|
||||
|
||||
foreach (self::FORMAT_INFO_DECODE_LOOKUP as $decodeInfo) {
|
||||
$targetInfo = $decodeInfo[0];
|
||||
|
||||
if ($targetInfo === $maskedFormatInfo1 || $targetInfo === $maskedFormatInfo2) {
|
||||
// Found an exact match
|
||||
return new self($decodeInfo[1]);
|
||||
}
|
||||
|
||||
$bitsDifference = self::numBitsDiffering($maskedFormatInfo1, $targetInfo);
|
||||
|
||||
if ($bitsDifference < $bestDifference) {
|
||||
$bestFormatInfo = $decodeInfo[1];
|
||||
$bestDifference = $bitsDifference;
|
||||
}
|
||||
|
||||
if ($maskedFormatInfo1 !== $maskedFormatInfo2) {
|
||||
// Also try the other option
|
||||
$bitsDifference = self::numBitsDiffering($maskedFormatInfo2, $targetInfo);
|
||||
|
||||
if ($bitsDifference < $bestDifference) {
|
||||
$bestFormatInfo = $decodeInfo[1];
|
||||
$bestDifference = $bitsDifference;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match.
|
||||
if ($bestDifference <= 3) {
|
||||
return new self($bestFormatInfo);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the error correction level.
|
||||
*/
|
||||
public function getErrorCorrectionLevel() : ErrorCorrectionLevel
|
||||
{
|
||||
return $this->ecLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data mask.
|
||||
*/
|
||||
public function getDataMask() : int
|
||||
{
|
||||
return $this->dataMask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashes the code of the EC level.
|
||||
*/
|
||||
public function hashCode() : int
|
||||
{
|
||||
return ($this->ecLevel->getBits() << 3) | $this->dataMask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if this instance equals another one.
|
||||
*/
|
||||
public function equals(self $other) : bool
|
||||
{
|
||||
return (
|
||||
$this->ecLevel === $other->ecLevel
|
||||
&& $this->dataMask === $other->dataMask
|
||||
);
|
||||
}
|
||||
}
|
69
vendor/bacon/bacon-qr-code/src/Common/Mode.php
vendored
Normal file
69
vendor/bacon/bacon-qr-code/src/Common/Mode.php
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
use DASPRiD\Enum\AbstractEnum;
|
||||
|
||||
/**
|
||||
* Enum representing various modes in which data can be encoded to bits.
|
||||
*
|
||||
* @method static self TERMINATOR()
|
||||
* @method static self NUMERIC()
|
||||
* @method static self ALPHANUMERIC()
|
||||
* @method static self STRUCTURED_APPEND()
|
||||
* @method static self BYTE()
|
||||
* @method static self ECI()
|
||||
* @method static self KANJI()
|
||||
* @method static self FNC1_FIRST_POSITION()
|
||||
* @method static self FNC1_SECOND_POSITION()
|
||||
* @method static self HANZI()
|
||||
*/
|
||||
final class Mode extends AbstractEnum
|
||||
{
|
||||
protected const TERMINATOR = [[0, 0, 0], 0x00];
|
||||
protected const NUMERIC = [[10, 12, 14], 0x01];
|
||||
protected const ALPHANUMERIC = [[9, 11, 13], 0x02];
|
||||
protected const STRUCTURED_APPEND = [[0, 0, 0], 0x03];
|
||||
protected const BYTE = [[8, 16, 16], 0x04];
|
||||
protected const ECI = [[0, 0, 0], 0x07];
|
||||
protected const KANJI = [[8, 10, 12], 0x08];
|
||||
protected const FNC1_FIRST_POSITION = [[0, 0, 0], 0x05];
|
||||
protected const FNC1_SECOND_POSITION = [[0, 0, 0], 0x09];
|
||||
protected const HANZI = [[8, 10, 12], 0x0d];
|
||||
|
||||
/**
|
||||
* @param int[] $characterCountBitsForVersions
|
||||
*/
|
||||
protected function __construct(
|
||||
private readonly array $characterCountBitsForVersions,
|
||||
private readonly int $bits
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bits used in a specific QR code version.
|
||||
*/
|
||||
public function getCharacterCountBits(Version $version) : int
|
||||
{
|
||||
$number = $version->getVersionNumber();
|
||||
|
||||
if ($number <= 9) {
|
||||
$offset = 0;
|
||||
} elseif ($number <= 26) {
|
||||
$offset = 1;
|
||||
} else {
|
||||
$offset = 2;
|
||||
}
|
||||
|
||||
return $this->characterCountBitsForVersions[$offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the four bits used to encode this mode.
|
||||
*/
|
||||
public function getBits() : int
|
||||
{
|
||||
return $this->bits;
|
||||
}
|
||||
}
|
454
vendor/bacon/bacon-qr-code/src/Common/ReedSolomonCodec.php
vendored
Normal file
454
vendor/bacon/bacon-qr-code/src/Common/ReedSolomonCodec.php
vendored
Normal file
|
@ -0,0 +1,454 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
use BaconQrCode\Exception\InvalidArgumentException;
|
||||
use BaconQrCode\Exception\RuntimeException;
|
||||
use SplFixedArray;
|
||||
|
||||
/**
|
||||
* Reed-Solomon codec for 8-bit characters.
|
||||
*
|
||||
* Based on libfec by Phil Karn, KA9Q.
|
||||
*/
|
||||
final class ReedSolomonCodec
|
||||
{
|
||||
/**
|
||||
* Symbol size in bits.
|
||||
*/
|
||||
private int $symbolSize;
|
||||
|
||||
/**
|
||||
* Block size in symbols.
|
||||
*/
|
||||
private int $blockSize;
|
||||
|
||||
/**
|
||||
* First root of RS code generator polynomial, index form.
|
||||
*/
|
||||
private int $firstRoot;
|
||||
|
||||
/**
|
||||
* Primitive element to generate polynomial roots, index form.
|
||||
*/
|
||||
private int $primitive;
|
||||
|
||||
/**
|
||||
* Prim-th root of 1, index form.
|
||||
*/
|
||||
private int $iPrimitive;
|
||||
|
||||
/**
|
||||
* RS code generator polynomial degree (number of roots).
|
||||
*/
|
||||
private int $numRoots;
|
||||
|
||||
/**
|
||||
* Padding bytes at front of shortened block.
|
||||
*/
|
||||
private int $padding;
|
||||
|
||||
/**
|
||||
* Log lookup table.
|
||||
*
|
||||
* @var SplFixedArray
|
||||
*/
|
||||
private SplFixedArray $alphaTo;
|
||||
|
||||
/**
|
||||
* Anti-Log lookup table.
|
||||
*
|
||||
* @var SplFixedArray
|
||||
*/
|
||||
private SplFixedArray $indexOf;
|
||||
|
||||
/**
|
||||
* Generator polynomial.
|
||||
*
|
||||
* @var SplFixedArray
|
||||
*/
|
||||
private SplFixedArray $generatorPoly;
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if symbol size ist not between 0 and 8
|
||||
* @throws InvalidArgumentException if first root is invalid
|
||||
* @throws InvalidArgumentException if num roots is invalid
|
||||
* @throws InvalidArgumentException if padding is invalid
|
||||
* @throws RuntimeException if field generator polynomial is not primitive
|
||||
*/
|
||||
public function __construct(
|
||||
int $symbolSize,
|
||||
int $gfPoly,
|
||||
int $firstRoot,
|
||||
int $primitive,
|
||||
int $numRoots,
|
||||
int $padding
|
||||
) {
|
||||
if ($symbolSize < 0 || $symbolSize > 8) {
|
||||
throw new InvalidArgumentException('Symbol size must be between 0 and 8');
|
||||
}
|
||||
|
||||
if ($firstRoot < 0 || $firstRoot >= (1 << $symbolSize)) {
|
||||
throw new InvalidArgumentException('First root must be between 0 and ' . (1 << $symbolSize));
|
||||
}
|
||||
|
||||
if ($numRoots < 0 || $numRoots >= (1 << $symbolSize)) {
|
||||
throw new InvalidArgumentException('Num roots must be between 0 and ' . (1 << $symbolSize));
|
||||
}
|
||||
|
||||
if ($padding < 0 || $padding >= ((1 << $symbolSize) - 1 - $numRoots)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Padding must be between 0 and ' . ((1 << $symbolSize) - 1 - $numRoots)
|
||||
);
|
||||
}
|
||||
|
||||
$this->symbolSize = $symbolSize;
|
||||
$this->blockSize = (1 << $symbolSize) - 1;
|
||||
$this->padding = $padding;
|
||||
$this->alphaTo = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false);
|
||||
$this->indexOf = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false);
|
||||
|
||||
// Generate galous field lookup table
|
||||
$this->indexOf[0] = $this->blockSize;
|
||||
$this->alphaTo[$this->blockSize] = 0;
|
||||
|
||||
$sr = 1;
|
||||
|
||||
for ($i = 0; $i < $this->blockSize; ++$i) {
|
||||
$this->indexOf[$sr] = $i;
|
||||
$this->alphaTo[$i] = $sr;
|
||||
|
||||
$sr <<= 1;
|
||||
|
||||
if ($sr & (1 << $symbolSize)) {
|
||||
$sr ^= $gfPoly;
|
||||
}
|
||||
|
||||
$sr &= $this->blockSize;
|
||||
}
|
||||
|
||||
if (1 !== $sr) {
|
||||
throw new RuntimeException('Field generator polynomial is not primitive');
|
||||
}
|
||||
|
||||
// Form RS code generator polynomial from its roots
|
||||
$this->generatorPoly = SplFixedArray::fromArray(array_fill(0, $numRoots + 1, 0), false);
|
||||
$this->firstRoot = $firstRoot;
|
||||
$this->primitive = $primitive;
|
||||
$this->numRoots = $numRoots;
|
||||
|
||||
// Find prim-th root of 1, used in decoding
|
||||
for ($iPrimitive = 1; ($iPrimitive % $primitive) !== 0; $iPrimitive += $this->blockSize) {
|
||||
}
|
||||
|
||||
$this->iPrimitive = intdiv($iPrimitive, $primitive);
|
||||
|
||||
$this->generatorPoly[0] = 1;
|
||||
|
||||
for ($i = 0, $root = $firstRoot * $primitive; $i < $numRoots; ++$i, $root += $primitive) {
|
||||
$this->generatorPoly[$i + 1] = 1;
|
||||
|
||||
for ($j = $i; $j > 0; $j--) {
|
||||
if ($this->generatorPoly[$j] !== 0) {
|
||||
$this->generatorPoly[$j] = $this->generatorPoly[$j - 1] ^ $this->alphaTo[
|
||||
$this->modNn($this->indexOf[$this->generatorPoly[$j]] + $root)
|
||||
];
|
||||
} else {
|
||||
$this->generatorPoly[$j] = $this->generatorPoly[$j - 1];
|
||||
}
|
||||
}
|
||||
|
||||
$this->generatorPoly[$j] = $this->alphaTo[$this->modNn($this->indexOf[$this->generatorPoly[0]] + $root)];
|
||||
}
|
||||
|
||||
// Convert generator poly to index form for quicker encoding
|
||||
for ($i = 0; $i <= $numRoots; ++$i) {
|
||||
$this->generatorPoly[$i] = $this->indexOf[$this->generatorPoly[$i]];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes data and writes result back into parity array.
|
||||
*/
|
||||
public function encode(SplFixedArray $data, SplFixedArray $parity) : void
|
||||
{
|
||||
for ($i = 0; $i < $this->numRoots; ++$i) {
|
||||
$parity[$i] = 0;
|
||||
}
|
||||
|
||||
$iterations = $this->blockSize - $this->numRoots - $this->padding;
|
||||
|
||||
for ($i = 0; $i < $iterations; ++$i) {
|
||||
$feedback = $this->indexOf[$data[$i] ^ $parity[0]];
|
||||
|
||||
if ($feedback !== $this->blockSize) {
|
||||
// Feedback term is non-zero
|
||||
$feedback = $this->modNn($this->blockSize - $this->generatorPoly[$this->numRoots] + $feedback);
|
||||
|
||||
for ($j = 1; $j < $this->numRoots; ++$j) {
|
||||
$parity[$j] = $parity[$j] ^ $this->alphaTo[
|
||||
$this->modNn($feedback + $this->generatorPoly[$this->numRoots - $j])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
for ($j = 0; $j < $this->numRoots - 1; ++$j) {
|
||||
$parity[$j] = $parity[$j + 1];
|
||||
}
|
||||
|
||||
if ($feedback !== $this->blockSize) {
|
||||
$parity[$this->numRoots - 1] = $this->alphaTo[$this->modNn($feedback + $this->generatorPoly[0])];
|
||||
} else {
|
||||
$parity[$this->numRoots - 1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes received data.
|
||||
*/
|
||||
public function decode(SplFixedArray $data, ?SplFixedArray $erasures = null) : ?int
|
||||
{
|
||||
// This speeds up the initialization a bit.
|
||||
$numRootsPlusOne = SplFixedArray::fromArray(array_fill(0, $this->numRoots + 1, 0), false);
|
||||
$numRoots = SplFixedArray::fromArray(array_fill(0, $this->numRoots, 0), false);
|
||||
|
||||
$lambda = clone $numRootsPlusOne;
|
||||
$b = clone $numRootsPlusOne;
|
||||
$t = clone $numRootsPlusOne;
|
||||
$omega = clone $numRootsPlusOne;
|
||||
$root = clone $numRoots;
|
||||
$loc = clone $numRoots;
|
||||
|
||||
$numErasures = (null !== $erasures ? count($erasures) : 0);
|
||||
|
||||
// Form the Syndromes; i.e., evaluate data(x) at roots of g(x)
|
||||
$syndromes = SplFixedArray::fromArray(array_fill(0, $this->numRoots, $data[0]), false);
|
||||
|
||||
for ($i = 1; $i < $this->blockSize - $this->padding; ++$i) {
|
||||
for ($j = 0; $j < $this->numRoots; ++$j) {
|
||||
if ($syndromes[$j] === 0) {
|
||||
$syndromes[$j] = $data[$i];
|
||||
} else {
|
||||
$syndromes[$j] = $data[$i] ^ $this->alphaTo[
|
||||
$this->modNn($this->indexOf[$syndromes[$j]] + ($this->firstRoot + $j) * $this->primitive)
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert syndromes to index form, checking for nonzero conditions
|
||||
$syndromeError = 0;
|
||||
|
||||
for ($i = 0; $i < $this->numRoots; ++$i) {
|
||||
$syndromeError |= $syndromes[$i];
|
||||
$syndromes[$i] = $this->indexOf[$syndromes[$i]];
|
||||
}
|
||||
|
||||
if (! $syndromeError) {
|
||||
// If syndrome is zero, data[] is a codeword and there are no errors to correct, so return data[]
|
||||
// unmodified.
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lambda[0] = 1;
|
||||
|
||||
if ($numErasures > 0) {
|
||||
// Init lambda to be the erasure locator polynomial
|
||||
$lambda[1] = $this->alphaTo[$this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[0]))];
|
||||
|
||||
for ($i = 1; $i < $numErasures; ++$i) {
|
||||
$u = $this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[$i]));
|
||||
|
||||
for ($j = $i + 1; $j > 0; --$j) {
|
||||
$tmp = $this->indexOf[$lambda[$j - 1]];
|
||||
|
||||
if ($tmp !== $this->blockSize) {
|
||||
$lambda[$j] = $lambda[$j] ^ $this->alphaTo[$this->modNn($u + $tmp)];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ($i = 0; $i <= $this->numRoots; ++$i) {
|
||||
$b[$i] = $this->indexOf[$lambda[$i]];
|
||||
}
|
||||
|
||||
// Begin Berlekamp-Massey algorithm to determine error+erasure locator polynomial
|
||||
$r = $numErasures;
|
||||
$el = $numErasures;
|
||||
|
||||
while (++$r <= $this->numRoots) {
|
||||
// Compute discrepancy at the r-th step in poly form
|
||||
$discrepancyR = 0;
|
||||
|
||||
for ($i = 0; $i < $r; ++$i) {
|
||||
if ($lambda[$i] !== 0 && $syndromes[$r - $i - 1] !== $this->blockSize) {
|
||||
$discrepancyR ^= $this->alphaTo[
|
||||
$this->modNn($this->indexOf[$lambda[$i]] + $syndromes[$r - $i - 1])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$discrepancyR = $this->indexOf[$discrepancyR];
|
||||
|
||||
if ($discrepancyR === $this->blockSize) {
|
||||
$tmp = $b->toArray();
|
||||
array_unshift($tmp, $this->blockSize);
|
||||
array_pop($tmp);
|
||||
$b = SplFixedArray::fromArray($tmp, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
$t[0] = $lambda[0];
|
||||
|
||||
for ($i = 0; $i < $this->numRoots; ++$i) {
|
||||
if ($b[$i] !== $this->blockSize) {
|
||||
$t[$i + 1] = $lambda[$i + 1] ^ $this->alphaTo[$this->modNn($discrepancyR + $b[$i])];
|
||||
} else {
|
||||
$t[$i + 1] = $lambda[$i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
if (2 * $el <= $r + $numErasures - 1) {
|
||||
$el = $r + $numErasures - $el;
|
||||
|
||||
for ($i = 0; $i <= $this->numRoots; ++$i) {
|
||||
$b[$i] = (
|
||||
$lambda[$i] === 0
|
||||
? $this->blockSize
|
||||
: $this->modNn($this->indexOf[$lambda[$i]] - $discrepancyR + $this->blockSize)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$tmp = $b->toArray();
|
||||
array_unshift($tmp, $this->blockSize);
|
||||
array_pop($tmp);
|
||||
$b = SplFixedArray::fromArray($tmp, false);
|
||||
}
|
||||
|
||||
$lambda = clone $t;
|
||||
}
|
||||
|
||||
// Convert lambda to index form and compute deg(lambda(x))
|
||||
$degLambda = 0;
|
||||
|
||||
for ($i = 0; $i <= $this->numRoots; ++$i) {
|
||||
$lambda[$i] = $this->indexOf[$lambda[$i]];
|
||||
|
||||
if ($lambda[$i] !== $this->blockSize) {
|
||||
$degLambda = $i;
|
||||
}
|
||||
}
|
||||
|
||||
// Find roots of the error+erasure locator polynomial by Chien search.
|
||||
$reg = clone $lambda;
|
||||
$reg[0] = 0;
|
||||
$count = 0;
|
||||
$i = 1;
|
||||
|
||||
for ($k = $this->iPrimitive - 1; $i <= $this->blockSize; ++$i, $k = $this->modNn($k + $this->iPrimitive)) {
|
||||
$q = 1;
|
||||
|
||||
for ($j = $degLambda; $j > 0; $j--) {
|
||||
if ($reg[$j] !== $this->blockSize) {
|
||||
$reg[$j] = $this->modNn($reg[$j] + $j);
|
||||
$q ^= $this->alphaTo[$reg[$j]];
|
||||
}
|
||||
}
|
||||
|
||||
if ($q !== 0) {
|
||||
// Not a root
|
||||
continue;
|
||||
}
|
||||
|
||||
// Store root (index-form) and error location number
|
||||
$root[$count] = $i;
|
||||
$loc[$count] = $k;
|
||||
|
||||
if (++$count === $degLambda) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($degLambda !== $count) {
|
||||
// deg(lambda) unequal to number of roots: uncorrectable error detected
|
||||
return null;
|
||||
}
|
||||
|
||||
// Compute err+eras evaluate poly omega(x) = s(x)*lambda(x) (modulo x**numRoots). In index form. Also find
|
||||
// deg(omega).
|
||||
$degOmega = $degLambda - 1;
|
||||
|
||||
for ($i = 0; $i <= $degOmega; ++$i) {
|
||||
$tmp = 0;
|
||||
|
||||
for ($j = $i; $j >= 0; --$j) {
|
||||
if ($syndromes[$i - $j] !== $this->blockSize && $lambda[$j] !== $this->blockSize) {
|
||||
$tmp ^= $this->alphaTo[$this->modNn($syndromes[$i - $j] + $lambda[$j])];
|
||||
}
|
||||
}
|
||||
|
||||
$omega[$i] = $this->indexOf[$tmp];
|
||||
}
|
||||
|
||||
// Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = inv(X(l))**(firstRoot-1) and
|
||||
// den = lambda_pr(inv(X(l))) all in poly form.
|
||||
for ($j = $count - 1; $j >= 0; --$j) {
|
||||
$num1 = 0;
|
||||
|
||||
for ($i = $degOmega; $i >= 0; $i--) {
|
||||
if ($omega[$i] !== $this->blockSize) {
|
||||
$num1 ^= $this->alphaTo[$this->modNn($omega[$i] + $i * $root[$j])];
|
||||
}
|
||||
}
|
||||
|
||||
$num2 = $this->alphaTo[$this->modNn($root[$j] * ($this->firstRoot - 1) + $this->blockSize)];
|
||||
$den = 0;
|
||||
|
||||
// lambda[i+1] for i even is the formal derivativelambda_pr of lambda[i]
|
||||
for ($i = min($degLambda, $this->numRoots - 1) & ~1; $i >= 0; $i -= 2) {
|
||||
if ($lambda[$i + 1] !== $this->blockSize) {
|
||||
$den ^= $this->alphaTo[$this->modNn($lambda[$i + 1] + $i * $root[$j])];
|
||||
}
|
||||
}
|
||||
|
||||
// Apply error to data
|
||||
if ($num1 !== 0 && $loc[$j] >= $this->padding) {
|
||||
$data[$loc[$j] - $this->padding] = $data[$loc[$j] - $this->padding] ^ (
|
||||
$this->alphaTo[
|
||||
$this->modNn(
|
||||
$this->indexOf[$num1] + $this->indexOf[$num2] + $this->blockSize - $this->indexOf[$den]
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $erasures) {
|
||||
if (count($erasures) < $count) {
|
||||
$erasures->setSize($count);
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$erasures[$i] = $loc[$i];
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes $x % GF_SIZE, where GF_SIZE is 2**GF_BITS - 1, without a slow divide.
|
||||
*/
|
||||
private function modNn(int $x) : int
|
||||
{
|
||||
while ($x >= $this->blockSize) {
|
||||
$x -= $this->blockSize;
|
||||
$x = ($x >> $this->symbolSize) + ($x & $this->blockSize);
|
||||
}
|
||||
|
||||
return $x;
|
||||
}
|
||||
}
|
592
vendor/bacon/bacon-qr-code/src/Common/Version.php
vendored
Normal file
592
vendor/bacon/bacon-qr-code/src/Common/Version.php
vendored
Normal file
|
@ -0,0 +1,592 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
use BaconQrCode\Exception\InvalidArgumentException;
|
||||
use SplFixedArray;
|
||||
|
||||
/**
|
||||
* Version representation.
|
||||
*/
|
||||
final class Version
|
||||
{
|
||||
private const VERSION_DECODE_INFO = [
|
||||
0x07c94,
|
||||
0x085bc,
|
||||
0x09a99,
|
||||
0x0a4d3,
|
||||
0x0bbf6,
|
||||
0x0c762,
|
||||
0x0d847,
|
||||
0x0e60d,
|
||||
0x0f928,
|
||||
0x10b78,
|
||||
0x1145d,
|
||||
0x12a17,
|
||||
0x13532,
|
||||
0x149a6,
|
||||
0x15683,
|
||||
0x168c9,
|
||||
0x177ec,
|
||||
0x18ec4,
|
||||
0x191e1,
|
||||
0x1afab,
|
||||
0x1b08e,
|
||||
0x1cc1a,
|
||||
0x1d33f,
|
||||
0x1ed75,
|
||||
0x1f250,
|
||||
0x209d5,
|
||||
0x216f0,
|
||||
0x228ba,
|
||||
0x2379f,
|
||||
0x24b0b,
|
||||
0x2542e,
|
||||
0x26a64,
|
||||
0x27541,
|
||||
0x28c69,
|
||||
];
|
||||
|
||||
/**
|
||||
* Version number of this version.
|
||||
*/
|
||||
private int $versionNumber;
|
||||
|
||||
/**
|
||||
* Alignment pattern centers.
|
||||
*
|
||||
* @var SplFixedArray|array
|
||||
*/
|
||||
private SplFixedArray|array $alignmentPatternCenters;
|
||||
|
||||
/**
|
||||
* Error correction blocks.
|
||||
*
|
||||
* @var EcBlocks[]
|
||||
*/
|
||||
private array $ecBlocks;
|
||||
|
||||
/**
|
||||
* Total number of codewords.
|
||||
*/
|
||||
private null|int|float $totalCodewords;
|
||||
|
||||
/**
|
||||
* Cached version instances.
|
||||
*
|
||||
* @var array<int, self>|null
|
||||
*/
|
||||
private static ?array $versions = null;
|
||||
|
||||
/**
|
||||
* @param int[] $alignmentPatternCenters
|
||||
*/
|
||||
private function __construct(
|
||||
int $versionNumber,
|
||||
array $alignmentPatternCenters,
|
||||
EcBlocks ...$ecBlocks
|
||||
) {
|
||||
$this->versionNumber = $versionNumber;
|
||||
$this->alignmentPatternCenters = $alignmentPatternCenters;
|
||||
$this->ecBlocks = $ecBlocks;
|
||||
|
||||
$totalCodewords = 0;
|
||||
$ecCodewords = $ecBlocks[0]->getEcCodewordsPerBlock();
|
||||
|
||||
foreach ($ecBlocks[0]->getEcBlocks() as $ecBlock) {
|
||||
$totalCodewords += $ecBlock->getCount() * ($ecBlock->getDataCodewords() + $ecCodewords);
|
||||
}
|
||||
|
||||
$this->totalCodewords = $totalCodewords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version number.
|
||||
*/
|
||||
public function getVersionNumber() : int
|
||||
{
|
||||
return $this->versionNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the alignment pattern centers.
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public function getAlignmentPatternCenters() : array
|
||||
{
|
||||
return $this->alignmentPatternCenters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of codewords.
|
||||
*/
|
||||
public function getTotalCodewords() : int
|
||||
{
|
||||
return $this->totalCodewords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the dimension for the current version.
|
||||
*/
|
||||
public function getDimensionForVersion() : int
|
||||
{
|
||||
return 17 + 4 * $this->versionNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of EC blocks for a specific EC level.
|
||||
*/
|
||||
public function getEcBlocksForLevel(ErrorCorrectionLevel $ecLevel) : EcBlocks
|
||||
{
|
||||
return $this->ecBlocks[$ecLevel->ordinal()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a provisional version number for a specific dimension.
|
||||
*
|
||||
* @throws InvalidArgumentException if dimension is not 1 mod 4
|
||||
*/
|
||||
public static function getProvisionalVersionForDimension(int $dimension) : self
|
||||
{
|
||||
if (1 !== $dimension % 4) {
|
||||
throw new InvalidArgumentException('Dimension is not 1 mod 4');
|
||||
}
|
||||
|
||||
return self::getVersionForNumber(intdiv($dimension - 17, 4));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a version instance for a specific version number.
|
||||
*
|
||||
* @throws InvalidArgumentException if version number is out of range
|
||||
*/
|
||||
public static function getVersionForNumber(int $versionNumber) : self
|
||||
{
|
||||
if ($versionNumber < 1 || $versionNumber > 40) {
|
||||
throw new InvalidArgumentException('Version number must be between 1 and 40');
|
||||
}
|
||||
|
||||
return self::versions()[$versionNumber - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes version information from an integer and returns the version.
|
||||
*/
|
||||
public static function decodeVersionInformation(int $versionBits) : ?self
|
||||
{
|
||||
$bestDifference = PHP_INT_MAX;
|
||||
$bestVersion = 0;
|
||||
|
||||
foreach (self::VERSION_DECODE_INFO as $i => $targetVersion) {
|
||||
if ($targetVersion === $versionBits) {
|
||||
return self::getVersionForNumber($i + 7);
|
||||
}
|
||||
|
||||
$bitsDifference = FormatInformation::numBitsDiffering($versionBits, $targetVersion);
|
||||
|
||||
if ($bitsDifference < $bestDifference) {
|
||||
$bestVersion = $i + 7;
|
||||
$bestDifference = $bitsDifference;
|
||||
}
|
||||
}
|
||||
|
||||
if ($bestDifference <= 3) {
|
||||
return self::getVersionForNumber($bestVersion);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the function pattern for the current version.
|
||||
*/
|
||||
public function buildFunctionPattern() : BitMatrix
|
||||
{
|
||||
$dimension = $this->getDimensionForVersion();
|
||||
$bitMatrix = new BitMatrix($dimension);
|
||||
|
||||
// Top left finder pattern + separator + format
|
||||
$bitMatrix->setRegion(0, 0, 9, 9);
|
||||
// Top right finder pattern + separator + format
|
||||
$bitMatrix->setRegion($dimension - 8, 0, 8, 9);
|
||||
// Bottom left finder pattern + separator + format
|
||||
$bitMatrix->setRegion(0, $dimension - 8, 9, 8);
|
||||
|
||||
// Alignment patterns
|
||||
$max = count($this->alignmentPatternCenters);
|
||||
|
||||
for ($x = 0; $x < $max; ++$x) {
|
||||
$i = $this->alignmentPatternCenters[$x] - 2;
|
||||
|
||||
for ($y = 0; $y < $max; ++$y) {
|
||||
if (($x === 0 && ($y === 0 || $y === $max - 1)) || ($x === $max - 1 && $y === 0)) {
|
||||
// No alignment patterns near the three finder paterns
|
||||
continue;
|
||||
}
|
||||
|
||||
$bitMatrix->setRegion($this->alignmentPatternCenters[$y] - 2, $i, 5, 5);
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical timing pattern
|
||||
$bitMatrix->setRegion(6, 9, 1, $dimension - 17);
|
||||
// Horizontal timing pattern
|
||||
$bitMatrix->setRegion(9, 6, $dimension - 17, 1);
|
||||
|
||||
if ($this->versionNumber > 6) {
|
||||
// Version info, top right
|
||||
$bitMatrix->setRegion($dimension - 11, 0, 3, 6);
|
||||
// Version info, bottom left
|
||||
$bitMatrix->setRegion(0, $dimension - 11, 6, 3);
|
||||
}
|
||||
|
||||
return $bitMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation for the version.
|
||||
*/
|
||||
public function __toString() : string
|
||||
{
|
||||
return (string) $this->versionNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and cache a specific version.
|
||||
*
|
||||
* See ISO 18004:2006 6.5.1 Table 9.
|
||||
*
|
||||
* @return array<int, self>
|
||||
*/
|
||||
private static function versions() : array
|
||||
{
|
||||
if (null !== self::$versions) {
|
||||
return self::$versions;
|
||||
}
|
||||
|
||||
return self::$versions = [
|
||||
new self(
|
||||
1,
|
||||
[],
|
||||
new EcBlocks(7, new EcBlock(1, 19)),
|
||||
new EcBlocks(10, new EcBlock(1, 16)),
|
||||
new EcBlocks(13, new EcBlock(1, 13)),
|
||||
new EcBlocks(17, new EcBlock(1, 9))
|
||||
),
|
||||
new self(
|
||||
2,
|
||||
[6, 18],
|
||||
new EcBlocks(10, new EcBlock(1, 34)),
|
||||
new EcBlocks(16, new EcBlock(1, 28)),
|
||||
new EcBlocks(22, new EcBlock(1, 22)),
|
||||
new EcBlocks(28, new EcBlock(1, 16))
|
||||
),
|
||||
new self(
|
||||
3,
|
||||
[6, 22],
|
||||
new EcBlocks(15, new EcBlock(1, 55)),
|
||||
new EcBlocks(26, new EcBlock(1, 44)),
|
||||
new EcBlocks(18, new EcBlock(2, 17)),
|
||||
new EcBlocks(22, new EcBlock(2, 13))
|
||||
),
|
||||
new self(
|
||||
4,
|
||||
[6, 26],
|
||||
new EcBlocks(20, new EcBlock(1, 80)),
|
||||
new EcBlocks(18, new EcBlock(2, 32)),
|
||||
new EcBlocks(26, new EcBlock(2, 24)),
|
||||
new EcBlocks(16, new EcBlock(4, 9))
|
||||
),
|
||||
new self(
|
||||
5,
|
||||
[6, 30],
|
||||
new EcBlocks(26, new EcBlock(1, 108)),
|
||||
new EcBlocks(24, new EcBlock(2, 43)),
|
||||
new EcBlocks(18, new EcBlock(2, 15), new EcBlock(2, 16)),
|
||||
new EcBlocks(22, new EcBlock(2, 11), new EcBlock(2, 12))
|
||||
),
|
||||
new self(
|
||||
6,
|
||||
[6, 34],
|
||||
new EcBlocks(18, new EcBlock(2, 68)),
|
||||
new EcBlocks(16, new EcBlock(4, 27)),
|
||||
new EcBlocks(24, new EcBlock(4, 19)),
|
||||
new EcBlocks(28, new EcBlock(4, 15))
|
||||
),
|
||||
new self(
|
||||
7,
|
||||
[6, 22, 38],
|
||||
new EcBlocks(20, new EcBlock(2, 78)),
|
||||
new EcBlocks(18, new EcBlock(4, 31)),
|
||||
new EcBlocks(18, new EcBlock(2, 14), new EcBlock(4, 15)),
|
||||
new EcBlocks(26, new EcBlock(4, 13), new EcBlock(1, 14))
|
||||
),
|
||||
new self(
|
||||
8,
|
||||
[6, 24, 42],
|
||||
new EcBlocks(24, new EcBlock(2, 97)),
|
||||
new EcBlocks(22, new EcBlock(2, 38), new EcBlock(2, 39)),
|
||||
new EcBlocks(22, new EcBlock(4, 18), new EcBlock(2, 19)),
|
||||
new EcBlocks(26, new EcBlock(4, 14), new EcBlock(2, 15))
|
||||
),
|
||||
new self(
|
||||
9,
|
||||
[6, 26, 46],
|
||||
new EcBlocks(30, new EcBlock(2, 116)),
|
||||
new EcBlocks(22, new EcBlock(3, 36), new EcBlock(2, 37)),
|
||||
new EcBlocks(20, new EcBlock(4, 16), new EcBlock(4, 17)),
|
||||
new EcBlocks(24, new EcBlock(4, 12), new EcBlock(4, 13))
|
||||
),
|
||||
new self(
|
||||
10,
|
||||
[6, 28, 50],
|
||||
new EcBlocks(18, new EcBlock(2, 68), new EcBlock(2, 69)),
|
||||
new EcBlocks(26, new EcBlock(4, 43), new EcBlock(1, 44)),
|
||||
new EcBlocks(24, new EcBlock(6, 19), new EcBlock(2, 20)),
|
||||
new EcBlocks(28, new EcBlock(6, 15), new EcBlock(2, 16))
|
||||
),
|
||||
new self(
|
||||
11,
|
||||
[6, 30, 54],
|
||||
new EcBlocks(20, new EcBlock(4, 81)),
|
||||
new EcBlocks(30, new EcBlock(1, 50), new EcBlock(4, 51)),
|
||||
new EcBlocks(28, new EcBlock(4, 22), new EcBlock(4, 23)),
|
||||
new EcBlocks(24, new EcBlock(3, 12), new EcBlock(8, 13))
|
||||
),
|
||||
new self(
|
||||
12,
|
||||
[6, 32, 58],
|
||||
new EcBlocks(24, new EcBlock(2, 92), new EcBlock(2, 93)),
|
||||
new EcBlocks(22, new EcBlock(6, 36), new EcBlock(2, 37)),
|
||||
new EcBlocks(26, new EcBlock(4, 20), new EcBlock(6, 21)),
|
||||
new EcBlocks(28, new EcBlock(7, 14), new EcBlock(4, 15))
|
||||
),
|
||||
new self(
|
||||
13,
|
||||
[6, 34, 62],
|
||||
new EcBlocks(26, new EcBlock(4, 107)),
|
||||
new EcBlocks(22, new EcBlock(8, 37), new EcBlock(1, 38)),
|
||||
new EcBlocks(24, new EcBlock(8, 20), new EcBlock(4, 21)),
|
||||
new EcBlocks(22, new EcBlock(12, 11), new EcBlock(4, 12))
|
||||
),
|
||||
new self(
|
||||
14,
|
||||
[6, 26, 46, 66],
|
||||
new EcBlocks(30, new EcBlock(3, 115), new EcBlock(1, 116)),
|
||||
new EcBlocks(24, new EcBlock(4, 40), new EcBlock(5, 41)),
|
||||
new EcBlocks(20, new EcBlock(11, 16), new EcBlock(5, 17)),
|
||||
new EcBlocks(24, new EcBlock(11, 12), new EcBlock(5, 13))
|
||||
),
|
||||
new self(
|
||||
15,
|
||||
[6, 26, 48, 70],
|
||||
new EcBlocks(22, new EcBlock(5, 87), new EcBlock(1, 88)),
|
||||
new EcBlocks(24, new EcBlock(5, 41), new EcBlock(5, 42)),
|
||||
new EcBlocks(30, new EcBlock(5, 24), new EcBlock(7, 25)),
|
||||
new EcBlocks(24, new EcBlock(11, 12), new EcBlock(7, 13))
|
||||
),
|
||||
new self(
|
||||
16,
|
||||
[6, 26, 50, 74],
|
||||
new EcBlocks(24, new EcBlock(5, 98), new EcBlock(1, 99)),
|
||||
new EcBlocks(28, new EcBlock(7, 45), new EcBlock(3, 46)),
|
||||
new EcBlocks(24, new EcBlock(15, 19), new EcBlock(2, 20)),
|
||||
new EcBlocks(30, new EcBlock(3, 15), new EcBlock(13, 16))
|
||||
),
|
||||
new self(
|
||||
17,
|
||||
[6, 30, 54, 78],
|
||||
new EcBlocks(28, new EcBlock(1, 107), new EcBlock(5, 108)),
|
||||
new EcBlocks(28, new EcBlock(10, 46), new EcBlock(1, 47)),
|
||||
new EcBlocks(28, new EcBlock(1, 22), new EcBlock(15, 23)),
|
||||
new EcBlocks(28, new EcBlock(2, 14), new EcBlock(17, 15))
|
||||
),
|
||||
new self(
|
||||
18,
|
||||
[6, 30, 56, 82],
|
||||
new EcBlocks(30, new EcBlock(5, 120), new EcBlock(1, 121)),
|
||||
new EcBlocks(26, new EcBlock(9, 43), new EcBlock(4, 44)),
|
||||
new EcBlocks(28, new EcBlock(17, 22), new EcBlock(1, 23)),
|
||||
new EcBlocks(28, new EcBlock(2, 14), new EcBlock(19, 15))
|
||||
),
|
||||
new self(
|
||||
19,
|
||||
[6, 30, 58, 86],
|
||||
new EcBlocks(28, new EcBlock(3, 113), new EcBlock(4, 114)),
|
||||
new EcBlocks(26, new EcBlock(3, 44), new EcBlock(11, 45)),
|
||||
new EcBlocks(26, new EcBlock(17, 21), new EcBlock(4, 22)),
|
||||
new EcBlocks(26, new EcBlock(9, 13), new EcBlock(16, 14))
|
||||
),
|
||||
new self(
|
||||
20,
|
||||
[6, 34, 62, 90],
|
||||
new EcBlocks(28, new EcBlock(3, 107), new EcBlock(5, 108)),
|
||||
new EcBlocks(26, new EcBlock(3, 41), new EcBlock(13, 42)),
|
||||
new EcBlocks(30, new EcBlock(15, 24), new EcBlock(5, 25)),
|
||||
new EcBlocks(28, new EcBlock(15, 15), new EcBlock(10, 16))
|
||||
),
|
||||
new self(
|
||||
21,
|
||||
[6, 28, 50, 72, 94],
|
||||
new EcBlocks(28, new EcBlock(4, 116), new EcBlock(4, 117)),
|
||||
new EcBlocks(26, new EcBlock(17, 42)),
|
||||
new EcBlocks(28, new EcBlock(17, 22), new EcBlock(6, 23)),
|
||||
new EcBlocks(30, new EcBlock(19, 16), new EcBlock(6, 17))
|
||||
),
|
||||
new self(
|
||||
22,
|
||||
[6, 26, 50, 74, 98],
|
||||
new EcBlocks(28, new EcBlock(2, 111), new EcBlock(7, 112)),
|
||||
new EcBlocks(28, new EcBlock(17, 46)),
|
||||
new EcBlocks(30, new EcBlock(7, 24), new EcBlock(16, 25)),
|
||||
new EcBlocks(24, new EcBlock(34, 13))
|
||||
),
|
||||
new self(
|
||||
23,
|
||||
[6, 30, 54, 78, 102],
|
||||
new EcBlocks(30, new EcBlock(4, 121), new EcBlock(5, 122)),
|
||||
new EcBlocks(28, new EcBlock(4, 47), new EcBlock(14, 48)),
|
||||
new EcBlocks(30, new EcBlock(11, 24), new EcBlock(14, 25)),
|
||||
new EcBlocks(30, new EcBlock(16, 15), new EcBlock(14, 16))
|
||||
),
|
||||
new self(
|
||||
24,
|
||||
[6, 28, 54, 80, 106],
|
||||
new EcBlocks(30, new EcBlock(6, 117), new EcBlock(4, 118)),
|
||||
new EcBlocks(28, new EcBlock(6, 45), new EcBlock(14, 46)),
|
||||
new EcBlocks(30, new EcBlock(11, 24), new EcBlock(16, 25)),
|
||||
new EcBlocks(30, new EcBlock(30, 16), new EcBlock(2, 17))
|
||||
),
|
||||
new self(
|
||||
25,
|
||||
[6, 32, 58, 84, 110],
|
||||
new EcBlocks(26, new EcBlock(8, 106), new EcBlock(4, 107)),
|
||||
new EcBlocks(28, new EcBlock(8, 47), new EcBlock(13, 48)),
|
||||
new EcBlocks(30, new EcBlock(7, 24), new EcBlock(22, 25)),
|
||||
new EcBlocks(30, new EcBlock(22, 15), new EcBlock(13, 16))
|
||||
),
|
||||
new self(
|
||||
26,
|
||||
[6, 30, 58, 86, 114],
|
||||
new EcBlocks(28, new EcBlock(10, 114), new EcBlock(2, 115)),
|
||||
new EcBlocks(28, new EcBlock(19, 46), new EcBlock(4, 47)),
|
||||
new EcBlocks(28, new EcBlock(28, 22), new EcBlock(6, 23)),
|
||||
new EcBlocks(30, new EcBlock(33, 16), new EcBlock(4, 17))
|
||||
),
|
||||
new self(
|
||||
27,
|
||||
[6, 34, 62, 90, 118],
|
||||
new EcBlocks(30, new EcBlock(8, 122), new EcBlock(4, 123)),
|
||||
new EcBlocks(28, new EcBlock(22, 45), new EcBlock(3, 46)),
|
||||
new EcBlocks(30, new EcBlock(8, 23), new EcBlock(26, 24)),
|
||||
new EcBlocks(30, new EcBlock(12, 15), new EcBlock(28, 16))
|
||||
),
|
||||
new self(
|
||||
28,
|
||||
[6, 26, 50, 74, 98, 122],
|
||||
new EcBlocks(30, new EcBlock(3, 117), new EcBlock(10, 118)),
|
||||
new EcBlocks(28, new EcBlock(3, 45), new EcBlock(23, 46)),
|
||||
new EcBlocks(30, new EcBlock(4, 24), new EcBlock(31, 25)),
|
||||
new EcBlocks(30, new EcBlock(11, 15), new EcBlock(31, 16))
|
||||
),
|
||||
new self(
|
||||
29,
|
||||
[6, 30, 54, 78, 102, 126],
|
||||
new EcBlocks(30, new EcBlock(7, 116), new EcBlock(7, 117)),
|
||||
new EcBlocks(28, new EcBlock(21, 45), new EcBlock(7, 46)),
|
||||
new EcBlocks(30, new EcBlock(1, 23), new EcBlock(37, 24)),
|
||||
new EcBlocks(30, new EcBlock(19, 15), new EcBlock(26, 16))
|
||||
),
|
||||
new self(
|
||||
30,
|
||||
[6, 26, 52, 78, 104, 130],
|
||||
new EcBlocks(30, new EcBlock(5, 115), new EcBlock(10, 116)),
|
||||
new EcBlocks(28, new EcBlock(19, 47), new EcBlock(10, 48)),
|
||||
new EcBlocks(30, new EcBlock(15, 24), new EcBlock(25, 25)),
|
||||
new EcBlocks(30, new EcBlock(23, 15), new EcBlock(25, 16))
|
||||
),
|
||||
new self(
|
||||
31,
|
||||
[6, 30, 56, 82, 108, 134],
|
||||
new EcBlocks(30, new EcBlock(13, 115), new EcBlock(3, 116)),
|
||||
new EcBlocks(28, new EcBlock(2, 46), new EcBlock(29, 47)),
|
||||
new EcBlocks(30, new EcBlock(42, 24), new EcBlock(1, 25)),
|
||||
new EcBlocks(30, new EcBlock(23, 15), new EcBlock(28, 16))
|
||||
),
|
||||
new self(
|
||||
32,
|
||||
[6, 34, 60, 86, 112, 138],
|
||||
new EcBlocks(30, new EcBlock(17, 115)),
|
||||
new EcBlocks(28, new EcBlock(10, 46), new EcBlock(23, 47)),
|
||||
new EcBlocks(30, new EcBlock(10, 24), new EcBlock(35, 25)),
|
||||
new EcBlocks(30, new EcBlock(19, 15), new EcBlock(35, 16))
|
||||
),
|
||||
new self(
|
||||
33,
|
||||
[6, 30, 58, 86, 114, 142],
|
||||
new EcBlocks(30, new EcBlock(17, 115), new EcBlock(1, 116)),
|
||||
new EcBlocks(28, new EcBlock(14, 46), new EcBlock(21, 47)),
|
||||
new EcBlocks(30, new EcBlock(29, 24), new EcBlock(19, 25)),
|
||||
new EcBlocks(30, new EcBlock(11, 15), new EcBlock(46, 16))
|
||||
),
|
||||
new self(
|
||||
34,
|
||||
[6, 34, 62, 90, 118, 146],
|
||||
new EcBlocks(30, new EcBlock(13, 115), new EcBlock(6, 116)),
|
||||
new EcBlocks(28, new EcBlock(14, 46), new EcBlock(23, 47)),
|
||||
new EcBlocks(30, new EcBlock(44, 24), new EcBlock(7, 25)),
|
||||
new EcBlocks(30, new EcBlock(59, 16), new EcBlock(1, 17))
|
||||
),
|
||||
new self(
|
||||
35,
|
||||
[6, 30, 54, 78, 102, 126, 150],
|
||||
new EcBlocks(30, new EcBlock(12, 121), new EcBlock(7, 122)),
|
||||
new EcBlocks(28, new EcBlock(12, 47), new EcBlock(26, 48)),
|
||||
new EcBlocks(30, new EcBlock(39, 24), new EcBlock(14, 25)),
|
||||
new EcBlocks(30, new EcBlock(22, 15), new EcBlock(41, 16))
|
||||
),
|
||||
new self(
|
||||
36,
|
||||
[6, 24, 50, 76, 102, 128, 154],
|
||||
new EcBlocks(30, new EcBlock(6, 121), new EcBlock(14, 122)),
|
||||
new EcBlocks(28, new EcBlock(6, 47), new EcBlock(34, 48)),
|
||||
new EcBlocks(30, new EcBlock(46, 24), new EcBlock(10, 25)),
|
||||
new EcBlocks(30, new EcBlock(2, 15), new EcBlock(64, 16))
|
||||
),
|
||||
new self(
|
||||
37,
|
||||
[6, 28, 54, 80, 106, 132, 158],
|
||||
new EcBlocks(30, new EcBlock(17, 122), new EcBlock(4, 123)),
|
||||
new EcBlocks(28, new EcBlock(29, 46), new EcBlock(14, 47)),
|
||||
new EcBlocks(30, new EcBlock(49, 24), new EcBlock(10, 25)),
|
||||
new EcBlocks(30, new EcBlock(24, 15), new EcBlock(46, 16))
|
||||
),
|
||||
new self(
|
||||
38,
|
||||
[6, 32, 58, 84, 110, 136, 162],
|
||||
new EcBlocks(30, new EcBlock(4, 122), new EcBlock(18, 123)),
|
||||
new EcBlocks(28, new EcBlock(13, 46), new EcBlock(32, 47)),
|
||||
new EcBlocks(30, new EcBlock(48, 24), new EcBlock(14, 25)),
|
||||
new EcBlocks(30, new EcBlock(42, 15), new EcBlock(32, 16))
|
||||
),
|
||||
new self(
|
||||
39,
|
||||
[6, 26, 54, 82, 110, 138, 166],
|
||||
new EcBlocks(30, new EcBlock(20, 117), new EcBlock(4, 118)),
|
||||
new EcBlocks(28, new EcBlock(40, 47), new EcBlock(7, 48)),
|
||||
new EcBlocks(30, new EcBlock(43, 24), new EcBlock(22, 25)),
|
||||
new EcBlocks(30, new EcBlock(10, 15), new EcBlock(67, 16))
|
||||
),
|
||||
new self(
|
||||
40,
|
||||
[6, 30, 58, 86, 114, 142, 170],
|
||||
new EcBlocks(30, new EcBlock(19, 118), new EcBlock(6, 119)),
|
||||
new EcBlocks(28, new EcBlock(18, 47), new EcBlock(31, 48)),
|
||||
new EcBlocks(30, new EcBlock(34, 24), new EcBlock(34, 25)),
|
||||
new EcBlocks(30, new EcBlock(20, 15), new EcBlock(61, 16))
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
44
vendor/bacon/bacon-qr-code/src/Encoder/BlockPair.php
vendored
Normal file
44
vendor/bacon/bacon-qr-code/src/Encoder/BlockPair.php
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Encoder;
|
||||
|
||||
use SplFixedArray;
|
||||
|
||||
/**
|
||||
* Block pair.
|
||||
*/
|
||||
final class BlockPair
|
||||
{
|
||||
/**
|
||||
* Creates a new block pair.
|
||||
*
|
||||
* @param SplFixedArray<int> $dataBytes Data bytes in the block.
|
||||
* @param SplFixedArray<int> $errorCorrectionBytes Error correction bytes in the block.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly SplFixedArray $dataBytes,
|
||||
private readonly SplFixedArray $errorCorrectionBytes
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data bytes.
|
||||
*
|
||||
* @return SplFixedArray<int>
|
||||
*/
|
||||
public function getDataBytes() : SplFixedArray
|
||||
{
|
||||
return $this->dataBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the error correction bytes.
|
||||
*
|
||||
* @return SplFixedArray<int>
|
||||
*/
|
||||
public function getErrorCorrectionBytes() : SplFixedArray
|
||||
{
|
||||
return $this->errorCorrectionBytes;
|
||||
}
|
||||
}
|
134
vendor/bacon/bacon-qr-code/src/Encoder/ByteMatrix.php
vendored
Normal file
134
vendor/bacon/bacon-qr-code/src/Encoder/ByteMatrix.php
vendored
Normal file
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Encoder;
|
||||
|
||||
use SplFixedArray;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Byte matrix.
|
||||
*/
|
||||
final class ByteMatrix
|
||||
{
|
||||
/**
|
||||
* Bytes in the matrix, represented as array.
|
||||
*
|
||||
* @var SplFixedArray<SplFixedArray<int>>
|
||||
*/
|
||||
private SplFixedArray $bytes;
|
||||
|
||||
public function __construct(private readonly int $width, private readonly int $height)
|
||||
{
|
||||
$this->bytes = new SplFixedArray($height);
|
||||
|
||||
for ($y = 0; $y < $height; ++$y) {
|
||||
$this->bytes[$y] = SplFixedArray::fromArray(array_fill(0, $width, 0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the width of the matrix.
|
||||
*/
|
||||
public function getWidth() : int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the height of the matrix.
|
||||
*/
|
||||
public function getHeight() : int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the internal representation of the matrix.
|
||||
*
|
||||
* @return SplFixedArray<SplFixedArray<int>>
|
||||
*/
|
||||
public function getArray() : SplFixedArray
|
||||
{
|
||||
return $this->bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Traversable<int>
|
||||
*/
|
||||
public function getBytes() : Traversable
|
||||
{
|
||||
foreach ($this->bytes as $row) {
|
||||
foreach ($row as $byte) {
|
||||
yield $byte;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the byte for a specific position.
|
||||
*/
|
||||
public function get(int $x, int $y) : int
|
||||
{
|
||||
return $this->bytes[$y][$x];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the byte for a specific position.
|
||||
*/
|
||||
public function set(int $x, int $y, int $value) : void
|
||||
{
|
||||
$this->bytes[$y][$x] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the matrix with a specific value.
|
||||
*/
|
||||
public function clear(int $value) : void
|
||||
{
|
||||
for ($y = 0; $y < $this->height; ++$y) {
|
||||
for ($x = 0; $x < $this->width; ++$x) {
|
||||
$this->bytes[$y][$x] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->bytes = clone $this->bytes;
|
||||
|
||||
foreach ($this->bytes as $index => $row) {
|
||||
$this->bytes[$index] = clone $row;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the matrix.
|
||||
*/
|
||||
public function __toString() : string
|
||||
{
|
||||
$result = '';
|
||||
|
||||
for ($y = 0; $y < $this->height; $y++) {
|
||||
for ($x = 0; $x < $this->width; $x++) {
|
||||
switch ($this->bytes[$y][$x]) {
|
||||
case 0:
|
||||
$result .= ' 0';
|
||||
break;
|
||||
|
||||
case 1:
|
||||
$result .= ' 1';
|
||||
break;
|
||||
|
||||
default:
|
||||
$result .= ' ';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$result .= "\n";
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
679
vendor/bacon/bacon-qr-code/src/Encoder/Encoder.php
vendored
Normal file
679
vendor/bacon/bacon-qr-code/src/Encoder/Encoder.php
vendored
Normal file
|
@ -0,0 +1,679 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Encoder;
|
||||
|
||||
use BaconQrCode\Common\BitArray;
|
||||
use BaconQrCode\Common\CharacterSetEci;
|
||||
use BaconQrCode\Common\ErrorCorrectionLevel;
|
||||
use BaconQrCode\Common\Mode;
|
||||
use BaconQrCode\Common\ReedSolomonCodec;
|
||||
use BaconQrCode\Common\Version;
|
||||
use BaconQrCode\Exception\WriterException;
|
||||
use SplFixedArray;
|
||||
|
||||
/**
|
||||
* Encoder.
|
||||
*/
|
||||
final class Encoder
|
||||
{
|
||||
/**
|
||||
* Default byte encoding.
|
||||
*/
|
||||
public const DEFAULT_BYTE_MODE_ENCODING = 'ISO-8859-1';
|
||||
|
||||
/** @deprecated use DEFAULT_BYTE_MODE_ENCODING */
|
||||
public const DEFAULT_BYTE_MODE_ECODING = self::DEFAULT_BYTE_MODE_ENCODING;
|
||||
|
||||
/**
|
||||
* The original table is defined in the table 5 of JISX0510:2004 (p.19).
|
||||
*/
|
||||
private const ALPHANUMERIC_TABLE = [
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x00-0x0f
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10-0x1f
|
||||
36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, // 0x20-0x2f
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, // 0x30-0x3f
|
||||
-1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 0x40-0x4f
|
||||
25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 0x50-0x5f
|
||||
];
|
||||
|
||||
/**
|
||||
* Codec cache.
|
||||
*
|
||||
* @var array<string,ReedSolomonCodec>
|
||||
*/
|
||||
private static array $codecs = [];
|
||||
|
||||
/**
|
||||
* Encodes "content" with the error correction level "ecLevel".
|
||||
*/
|
||||
public static function encode(
|
||||
string $content,
|
||||
ErrorCorrectionLevel $ecLevel,
|
||||
string $encoding = self::DEFAULT_BYTE_MODE_ENCODING,
|
||||
?Version $forcedVersion = null,
|
||||
// Barcode scanner might not be able to read the encoded message of the QR code with the prefix ECI of UTF-8
|
||||
bool $prefixEci = true
|
||||
) : QrCode {
|
||||
// Pick an encoding mode appropriate for the content. Note that this
|
||||
// will not attempt to use multiple modes / segments even if that were
|
||||
// more efficient. Would be nice.
|
||||
$mode = self::chooseMode($content, $encoding);
|
||||
|
||||
// This will store the header information, like mode and length, as well
|
||||
// as "header" segments like an ECI segment.
|
||||
$headerBits = new BitArray();
|
||||
|
||||
// Append ECI segment if applicable
|
||||
if ($prefixEci && Mode::BYTE() === $mode && self::DEFAULT_BYTE_MODE_ENCODING !== $encoding) {
|
||||
$eci = CharacterSetEci::getCharacterSetEciByName($encoding);
|
||||
|
||||
if (null !== $eci) {
|
||||
self::appendEci($eci, $headerBits);
|
||||
}
|
||||
}
|
||||
|
||||
// (With ECI in place,) Write the mode marker
|
||||
self::appendModeInfo($mode, $headerBits);
|
||||
|
||||
// Collect data within the main segment, separately, to count its size
|
||||
// if needed. Don't add it to main payload yet.
|
||||
$dataBits = new BitArray();
|
||||
self::appendBytes($content, $mode, $dataBits, $encoding);
|
||||
|
||||
// Hard part: need to know version to know how many bits length takes.
|
||||
// But need to know how many bits it takes to know version. First we
|
||||
// take a guess at version by assuming version will be the minimum, 1:
|
||||
$provisionalBitsNeeded = $headerBits->getSize()
|
||||
+ $mode->getCharacterCountBits(Version::getVersionForNumber(1))
|
||||
+ $dataBits->getSize();
|
||||
$provisionalVersion = self::chooseVersion($provisionalBitsNeeded, $ecLevel);
|
||||
|
||||
// Use that guess to calculate the right version. I am still not sure
|
||||
// this works in 100% of cases.
|
||||
$bitsNeeded = $headerBits->getSize()
|
||||
+ $mode->getCharacterCountBits($provisionalVersion)
|
||||
+ $dataBits->getSize();
|
||||
$version = self::chooseVersion($bitsNeeded, $ecLevel);
|
||||
|
||||
if (null !== $forcedVersion) {
|
||||
// Forced version check
|
||||
if ($version->getVersionNumber() <= $forcedVersion->getVersionNumber()) {
|
||||
// Calculated minimum version is same or equal as forced version
|
||||
$version = $forcedVersion;
|
||||
} else {
|
||||
throw new WriterException(
|
||||
'Invalid version! Calculated version: '
|
||||
. $version->getVersionNumber()
|
||||
. ', requested version: '
|
||||
. $forcedVersion->getVersionNumber()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$headerAndDataBits = new BitArray();
|
||||
$headerAndDataBits->appendBitArray($headerBits);
|
||||
|
||||
// Find "length" of main segment and write it.
|
||||
$numLetters = (Mode::BYTE() === $mode ? $dataBits->getSizeInBytes() : strlen($content));
|
||||
self::appendLengthInfo($numLetters, $version, $mode, $headerAndDataBits);
|
||||
|
||||
// Put data together into the overall payload.
|
||||
$headerAndDataBits->appendBitArray($dataBits);
|
||||
$ecBlocks = $version->getEcBlocksForLevel($ecLevel);
|
||||
$numDataBytes = $version->getTotalCodewords() - $ecBlocks->getTotalEcCodewords();
|
||||
|
||||
// Terminate the bits properly.
|
||||
self::terminateBits($numDataBytes, $headerAndDataBits);
|
||||
|
||||
// Interleave data bits with error correction code.
|
||||
$finalBits = self::interleaveWithEcBytes(
|
||||
$headerAndDataBits,
|
||||
$version->getTotalCodewords(),
|
||||
$numDataBytes,
|
||||
$ecBlocks->getNumBlocks()
|
||||
);
|
||||
|
||||
// Choose the mask pattern.
|
||||
$dimension = $version->getDimensionForVersion();
|
||||
$matrix = new ByteMatrix($dimension, $dimension);
|
||||
$maskPattern = self::chooseMaskPattern($finalBits, $ecLevel, $version, $matrix);
|
||||
|
||||
// Build the matrix.
|
||||
MatrixUtil::buildMatrix($finalBits, $ecLevel, $version, $maskPattern, $matrix);
|
||||
|
||||
return new QrCode($mode, $ecLevel, $version, $maskPattern, $matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the alphanumeric code for a byte.
|
||||
*/
|
||||
private static function getAlphanumericCode(int $code) : int
|
||||
{
|
||||
if (isset(self::ALPHANUMERIC_TABLE[$code])) {
|
||||
return self::ALPHANUMERIC_TABLE[$code];
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chooses the best mode for a given content.
|
||||
*/
|
||||
private static function chooseMode(string $content, ?string $encoding = null) : Mode
|
||||
{
|
||||
if (null !== $encoding && 0 === strcasecmp($encoding, 'SHIFT-JIS')) {
|
||||
return self::isOnlyDoubleByteKanji($content) ? Mode::KANJI() : Mode::BYTE();
|
||||
}
|
||||
|
||||
$hasNumeric = false;
|
||||
$hasAlphanumeric = false;
|
||||
$contentLength = strlen($content);
|
||||
|
||||
for ($i = 0; $i < $contentLength; ++$i) {
|
||||
$char = $content[$i];
|
||||
|
||||
if (ctype_digit($char)) {
|
||||
$hasNumeric = true;
|
||||
} elseif (-1 !== self::getAlphanumericCode(ord($char))) {
|
||||
$hasAlphanumeric = true;
|
||||
} else {
|
||||
return Mode::BYTE();
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasAlphanumeric) {
|
||||
return Mode::ALPHANUMERIC();
|
||||
} elseif ($hasNumeric) {
|
||||
return Mode::NUMERIC();
|
||||
}
|
||||
|
||||
return Mode::BYTE();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the mask penalty for a matrix.
|
||||
*/
|
||||
private static function calculateMaskPenalty(ByteMatrix $matrix) : int
|
||||
{
|
||||
return (
|
||||
MaskUtil::applyMaskPenaltyRule1($matrix)
|
||||
+ MaskUtil::applyMaskPenaltyRule2($matrix)
|
||||
+ MaskUtil::applyMaskPenaltyRule3($matrix)
|
||||
+ MaskUtil::applyMaskPenaltyRule4($matrix)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if content only consists of double-byte kanji characters.
|
||||
*/
|
||||
private static function isOnlyDoubleByteKanji(string $content) : bool
|
||||
{
|
||||
$bytes = @iconv('utf-8', 'SHIFT-JIS', $content);
|
||||
|
||||
if (false === $bytes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$length = strlen($bytes);
|
||||
|
||||
if (0 !== $length % 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $length; $i += 2) {
|
||||
$byte = ord($bytes[$i]) & 0xff;
|
||||
|
||||
if (($byte < 0x81 || $byte > 0x9f) && $byte < 0xe0 || $byte > 0xeb) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chooses the best mask pattern for a matrix.
|
||||
*/
|
||||
private static function chooseMaskPattern(
|
||||
BitArray $bits,
|
||||
ErrorCorrectionLevel $ecLevel,
|
||||
Version $version,
|
||||
ByteMatrix $matrix
|
||||
) : int {
|
||||
$minPenalty = PHP_INT_MAX;
|
||||
$bestMaskPattern = -1;
|
||||
|
||||
for ($maskPattern = 0; $maskPattern < QrCode::NUM_MASK_PATTERNS; ++$maskPattern) {
|
||||
MatrixUtil::buildMatrix($bits, $ecLevel, $version, $maskPattern, $matrix);
|
||||
$penalty = self::calculateMaskPenalty($matrix);
|
||||
|
||||
if ($penalty < $minPenalty) {
|
||||
$minPenalty = $penalty;
|
||||
$bestMaskPattern = $maskPattern;
|
||||
}
|
||||
}
|
||||
|
||||
return $bestMaskPattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chooses the best version for the input.
|
||||
*
|
||||
* @throws WriterException if data is too big
|
||||
*/
|
||||
private static function chooseVersion(int $numInputBits, ErrorCorrectionLevel $ecLevel) : Version
|
||||
{
|
||||
for ($versionNum = 1; $versionNum <= 40; ++$versionNum) {
|
||||
$version = Version::getVersionForNumber($versionNum);
|
||||
$numBytes = $version->getTotalCodewords();
|
||||
|
||||
$ecBlocks = $version->getEcBlocksForLevel($ecLevel);
|
||||
$numEcBytes = $ecBlocks->getTotalEcCodewords();
|
||||
|
||||
$numDataBytes = $numBytes - $numEcBytes;
|
||||
$totalInputBytes = intdiv($numInputBits + 8, 8);
|
||||
|
||||
if ($numDataBytes >= $totalInputBytes) {
|
||||
return $version;
|
||||
}
|
||||
}
|
||||
|
||||
throw new WriterException('Data too big');
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminates the bits in a bit array.
|
||||
*
|
||||
* @throws WriterException if data bits cannot fit in the QR code
|
||||
* @throws WriterException if bits size does not equal the capacity
|
||||
*/
|
||||
private static function terminateBits(int $numDataBytes, BitArray $bits) : void
|
||||
{
|
||||
$capacity = $numDataBytes << 3;
|
||||
|
||||
if ($bits->getSize() > $capacity) {
|
||||
throw new WriterException('Data bits cannot fit in the QR code');
|
||||
}
|
||||
|
||||
for ($i = 0; $i < 4 && $bits->getSize() < $capacity; ++$i) {
|
||||
$bits->appendBit(false);
|
||||
}
|
||||
|
||||
$numBitsInLastByte = $bits->getSize() & 0x7;
|
||||
|
||||
if ($numBitsInLastByte > 0) {
|
||||
for ($i = $numBitsInLastByte; $i < 8; ++$i) {
|
||||
$bits->appendBit(false);
|
||||
}
|
||||
}
|
||||
|
||||
$numPaddingBytes = $numDataBytes - $bits->getSizeInBytes();
|
||||
|
||||
for ($i = 0; $i < $numPaddingBytes; ++$i) {
|
||||
$bits->appendBits(0 === ($i & 0x1) ? 0xec : 0x11, 8);
|
||||
}
|
||||
|
||||
if ($bits->getSize() !== $capacity) {
|
||||
throw new WriterException('Bits size does not equal capacity');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets number of data- and EC bytes for a block ID.
|
||||
*
|
||||
* @return int[]
|
||||
* @throws WriterException if block ID is too large
|
||||
* @throws WriterException if EC bytes mismatch
|
||||
* @throws WriterException if RS blocks mismatch
|
||||
* @throws WriterException if total bytes mismatch
|
||||
*/
|
||||
private static function getNumDataBytesAndNumEcBytesForBlockId(
|
||||
int $numTotalBytes,
|
||||
int $numDataBytes,
|
||||
int $numRsBlocks,
|
||||
int $blockId
|
||||
) : array {
|
||||
if ($blockId >= $numRsBlocks) {
|
||||
throw new WriterException('Block ID too large');
|
||||
}
|
||||
|
||||
$numRsBlocksInGroup2 = $numTotalBytes % $numRsBlocks;
|
||||
$numRsBlocksInGroup1 = $numRsBlocks - $numRsBlocksInGroup2;
|
||||
$numTotalBytesInGroup1 = intdiv($numTotalBytes, $numRsBlocks);
|
||||
$numTotalBytesInGroup2 = $numTotalBytesInGroup1 + 1;
|
||||
$numDataBytesInGroup1 = intdiv($numDataBytes, $numRsBlocks);
|
||||
$numDataBytesInGroup2 = $numDataBytesInGroup1 + 1;
|
||||
$numEcBytesInGroup1 = $numTotalBytesInGroup1 - $numDataBytesInGroup1;
|
||||
$numEcBytesInGroup2 = $numTotalBytesInGroup2 - $numDataBytesInGroup2;
|
||||
|
||||
if ($numEcBytesInGroup1 !== $numEcBytesInGroup2) {
|
||||
throw new WriterException('EC bytes mismatch');
|
||||
}
|
||||
|
||||
if ($numRsBlocks !== $numRsBlocksInGroup1 + $numRsBlocksInGroup2) {
|
||||
throw new WriterException('RS blocks mismatch');
|
||||
}
|
||||
|
||||
if ($numTotalBytes !==
|
||||
(($numDataBytesInGroup1 + $numEcBytesInGroup1) * $numRsBlocksInGroup1)
|
||||
+ (($numDataBytesInGroup2 + $numEcBytesInGroup2) * $numRsBlocksInGroup2)
|
||||
) {
|
||||
throw new WriterException('Total bytes mismatch');
|
||||
}
|
||||
|
||||
if ($blockId < $numRsBlocksInGroup1) {
|
||||
return [$numDataBytesInGroup1, $numEcBytesInGroup1];
|
||||
} else {
|
||||
return [$numDataBytesInGroup2, $numEcBytesInGroup2];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interleaves data with EC bytes.
|
||||
*
|
||||
* @throws WriterException if number of bits and data bytes does not match
|
||||
* @throws WriterException if data bytes does not match offset
|
||||
* @throws WriterException if an interleaving error occurs
|
||||
*/
|
||||
private static function interleaveWithEcBytes(
|
||||
BitArray $bits,
|
||||
int $numTotalBytes,
|
||||
int $numDataBytes,
|
||||
int $numRsBlocks
|
||||
) : BitArray {
|
||||
if ($bits->getSizeInBytes() !== $numDataBytes) {
|
||||
throw new WriterException('Number of bits and data bytes does not match');
|
||||
}
|
||||
|
||||
$dataBytesOffset = 0;
|
||||
$maxNumDataBytes = 0;
|
||||
$maxNumEcBytes = 0;
|
||||
|
||||
$blocks = new SplFixedArray($numRsBlocks);
|
||||
|
||||
for ($i = 0; $i < $numRsBlocks; ++$i) {
|
||||
list($numDataBytesInBlock, $numEcBytesInBlock) = self::getNumDataBytesAndNumEcBytesForBlockId(
|
||||
$numTotalBytes,
|
||||
$numDataBytes,
|
||||
$numRsBlocks,
|
||||
$i
|
||||
);
|
||||
|
||||
$size = $numDataBytesInBlock;
|
||||
$dataBytes = $bits->toBytes(8 * $dataBytesOffset, $size);
|
||||
$ecBytes = self::generateEcBytes($dataBytes, $numEcBytesInBlock);
|
||||
$blocks[$i] = new BlockPair($dataBytes, $ecBytes);
|
||||
|
||||
$maxNumDataBytes = max($maxNumDataBytes, $size);
|
||||
$maxNumEcBytes = max($maxNumEcBytes, count($ecBytes));
|
||||
$dataBytesOffset += $numDataBytesInBlock;
|
||||
}
|
||||
|
||||
if ($numDataBytes !== $dataBytesOffset) {
|
||||
throw new WriterException('Data bytes does not match offset');
|
||||
}
|
||||
|
||||
$result = new BitArray();
|
||||
|
||||
for ($i = 0; $i < $maxNumDataBytes; ++$i) {
|
||||
foreach ($blocks as $block) {
|
||||
$dataBytes = $block->getDataBytes();
|
||||
|
||||
if ($i < count($dataBytes)) {
|
||||
$result->appendBits($dataBytes[$i], 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $maxNumEcBytes; ++$i) {
|
||||
foreach ($blocks as $block) {
|
||||
$ecBytes = $block->getErrorCorrectionBytes();
|
||||
|
||||
if ($i < count($ecBytes)) {
|
||||
$result->appendBits($ecBytes[$i], 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($numTotalBytes !== $result->getSizeInBytes()) {
|
||||
throw new WriterException(
|
||||
'Interleaving error: ' . $numTotalBytes . ' and ' . $result->getSizeInBytes() . ' differ'
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates EC bytes for given data.
|
||||
*
|
||||
* @param SplFixedArray<int> $dataBytes
|
||||
* @return SplFixedArray<int>
|
||||
*/
|
||||
private static function generateEcBytes(SplFixedArray $dataBytes, int $numEcBytesInBlock) : SplFixedArray
|
||||
{
|
||||
$numDataBytes = count($dataBytes);
|
||||
$toEncode = new SplFixedArray($numDataBytes + $numEcBytesInBlock);
|
||||
|
||||
for ($i = 0; $i < $numDataBytes; $i++) {
|
||||
$toEncode[$i] = $dataBytes[$i] & 0xff;
|
||||
}
|
||||
|
||||
$ecBytes = new SplFixedArray($numEcBytesInBlock);
|
||||
$codec = self::getCodec($numDataBytes, $numEcBytesInBlock);
|
||||
$codec->encode($toEncode, $ecBytes);
|
||||
|
||||
return $ecBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an RS codec and caches it.
|
||||
*/
|
||||
private static function getCodec(int $numDataBytes, int $numEcBytesInBlock) : ReedSolomonCodec
|
||||
{
|
||||
$cacheId = $numDataBytes . '-' . $numEcBytesInBlock;
|
||||
|
||||
if (isset(self::$codecs[$cacheId])) {
|
||||
return self::$codecs[$cacheId];
|
||||
}
|
||||
|
||||
return self::$codecs[$cacheId] = new ReedSolomonCodec(
|
||||
8,
|
||||
0x11d,
|
||||
0,
|
||||
1,
|
||||
$numEcBytesInBlock,
|
||||
255 - $numDataBytes - $numEcBytesInBlock
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends mode information to a bit array.
|
||||
*/
|
||||
private static function appendModeInfo(Mode $mode, BitArray $bits) : void
|
||||
{
|
||||
$bits->appendBits($mode->getBits(), 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends length information to a bit array.
|
||||
*
|
||||
* @throws WriterException if num letters is bigger than expected
|
||||
*/
|
||||
private static function appendLengthInfo(int $numLetters, Version $version, Mode $mode, BitArray $bits) : void
|
||||
{
|
||||
$numBits = $mode->getCharacterCountBits($version);
|
||||
|
||||
if ($numLetters >= (1 << $numBits)) {
|
||||
throw new WriterException($numLetters . ' is bigger than ' . ((1 << $numBits) - 1));
|
||||
}
|
||||
|
||||
$bits->appendBits($numLetters, $numBits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends bytes to a bit array in a specific mode.
|
||||
*
|
||||
* @throws WriterException if an invalid mode was supplied
|
||||
*/
|
||||
private static function appendBytes(string $content, Mode $mode, BitArray $bits, string $encoding) : void
|
||||
{
|
||||
switch ($mode) {
|
||||
case Mode::NUMERIC():
|
||||
self::appendNumericBytes($content, $bits);
|
||||
break;
|
||||
|
||||
case Mode::ALPHANUMERIC():
|
||||
self::appendAlphanumericBytes($content, $bits);
|
||||
break;
|
||||
|
||||
case Mode::BYTE():
|
||||
self::append8BitBytes($content, $bits, $encoding);
|
||||
break;
|
||||
|
||||
case Mode::KANJI():
|
||||
self::appendKanjiBytes($content, $bits);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new WriterException('Invalid mode: ' . $mode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends numeric bytes to a bit array.
|
||||
*/
|
||||
private static function appendNumericBytes(string $content, BitArray $bits) : void
|
||||
{
|
||||
$length = strlen($content);
|
||||
$i = 0;
|
||||
|
||||
while ($i < $length) {
|
||||
$num1 = (int) $content[$i];
|
||||
|
||||
if ($i + 2 < $length) {
|
||||
// Encode three numeric letters in ten bits.
|
||||
$num2 = (int) $content[$i + 1];
|
||||
$num3 = (int) $content[$i + 2];
|
||||
$bits->appendBits($num1 * 100 + $num2 * 10 + $num3, 10);
|
||||
$i += 3;
|
||||
} elseif ($i + 1 < $length) {
|
||||
// Encode two numeric letters in seven bits.
|
||||
$num2 = (int) $content[$i + 1];
|
||||
$bits->appendBits($num1 * 10 + $num2, 7);
|
||||
$i += 2;
|
||||
} else {
|
||||
// Encode one numeric letter in four bits.
|
||||
$bits->appendBits($num1, 4);
|
||||
++$i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends alpha-numeric bytes to a bit array.
|
||||
*
|
||||
* @throws WriterException if an invalid alphanumeric code was found
|
||||
*/
|
||||
private static function appendAlphanumericBytes(string $content, BitArray $bits) : void
|
||||
{
|
||||
$length = strlen($content);
|
||||
$i = 0;
|
||||
|
||||
while ($i < $length) {
|
||||
$code1 = self::getAlphanumericCode(ord($content[$i]));
|
||||
|
||||
if (-1 === $code1) {
|
||||
throw new WriterException('Invalid alphanumeric code');
|
||||
}
|
||||
|
||||
if ($i + 1 < $length) {
|
||||
$code2 = self::getAlphanumericCode(ord($content[$i + 1]));
|
||||
|
||||
if (-1 === $code2) {
|
||||
throw new WriterException('Invalid alphanumeric code');
|
||||
}
|
||||
|
||||
// Encode two alphanumeric letters in 11 bits.
|
||||
$bits->appendBits($code1 * 45 + $code2, 11);
|
||||
$i += 2;
|
||||
} else {
|
||||
// Encode one alphanumeric letter in six bits.
|
||||
$bits->appendBits($code1, 6);
|
||||
++$i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends regular 8-bit bytes to a bit array.
|
||||
*
|
||||
* @throws WriterException if content cannot be encoded to target encoding
|
||||
*/
|
||||
private static function append8BitBytes(string $content, BitArray $bits, string $encoding) : void
|
||||
{
|
||||
$bytes = @iconv('utf-8', $encoding, $content);
|
||||
|
||||
if (false === $bytes) {
|
||||
throw new WriterException('Could not encode content to ' . $encoding);
|
||||
}
|
||||
|
||||
$length = strlen($bytes);
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$bits->appendBits(ord($bytes[$i]), 8);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends KANJI bytes to a bit array.
|
||||
*
|
||||
* @throws WriterException if content does not seem to be encoded in SHIFT-JIS
|
||||
* @throws WriterException if an invalid byte sequence occurs
|
||||
*/
|
||||
private static function appendKanjiBytes(string $content, BitArray $bits) : void
|
||||
{
|
||||
$bytes = @iconv('utf-8', 'SHIFT-JIS', $content);
|
||||
|
||||
if (false === $bytes) {
|
||||
throw new WriterException('Content could not be converted to SHIFT-JIS');
|
||||
}
|
||||
|
||||
if (strlen($bytes) % 2 > 0) {
|
||||
// We just do a simple length check here. The for loop will check
|
||||
// individual characters.
|
||||
throw new WriterException('Content does not seem to be encoded in SHIFT-JIS');
|
||||
}
|
||||
|
||||
$length = strlen($bytes);
|
||||
|
||||
for ($i = 0; $i < $length; $i += 2) {
|
||||
$byte1 = ord($bytes[$i]) & 0xff;
|
||||
$byte2 = ord($bytes[$i + 1]) & 0xff;
|
||||
$code = ($byte1 << 8) | $byte2;
|
||||
|
||||
if ($code >= 0x8140 && $code <= 0x9ffc) {
|
||||
$subtracted = $code - 0x8140;
|
||||
} elseif ($code >= 0xe040 && $code <= 0xebbf) {
|
||||
$subtracted = $code - 0xc140;
|
||||
} else {
|
||||
throw new WriterException('Invalid byte sequence');
|
||||
}
|
||||
|
||||
$encoded = (($subtracted >> 8) * 0xc0) + ($subtracted & 0xff);
|
||||
|
||||
$bits->appendBits($encoded, 13);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends ECI information to a bit array.
|
||||
*/
|
||||
private static function appendEci(CharacterSetEci $eci, BitArray $bits) : void
|
||||
{
|
||||
$mode = Mode::ECI();
|
||||
$bits->appendBits($mode->getBits(), 4);
|
||||
$bits->appendBits($eci->getValue(), 8);
|
||||
}
|
||||
}
|
271
vendor/bacon/bacon-qr-code/src/Encoder/MaskUtil.php
vendored
Normal file
271
vendor/bacon/bacon-qr-code/src/Encoder/MaskUtil.php
vendored
Normal file
|
@ -0,0 +1,271 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Encoder;
|
||||
|
||||
use BaconQrCode\Common\BitUtils;
|
||||
use BaconQrCode\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Mask utility.
|
||||
*/
|
||||
final class MaskUtil
|
||||
{
|
||||
/**#@+
|
||||
* Penalty weights from section 6.8.2.1
|
||||
*/
|
||||
public const N1 = 3;
|
||||
public const N2 = 3;
|
||||
public const N3 = 40;
|
||||
public const N4 = 10;
|
||||
/**#@-*/
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies mask penalty rule 1 and returns the penalty.
|
||||
*
|
||||
* Finds repetitive cells with the same color and gives penalty to them.
|
||||
* Example: 00000 or 11111.
|
||||
*/
|
||||
public static function applyMaskPenaltyRule1(ByteMatrix $matrix) : int
|
||||
{
|
||||
return (
|
||||
self::applyMaskPenaltyRule1Internal($matrix, true)
|
||||
+ self::applyMaskPenaltyRule1Internal($matrix, false)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies mask penalty rule 2 and returns the penalty.
|
||||
*
|
||||
* Finds 2x2 blocks with the same color and gives penalty to them. This is
|
||||
* actually equivalent to the spec's rule, which is to find MxN blocks and
|
||||
* give a penalty proportional to (M-1)x(N-1), because this is the number of
|
||||
* 2x2 blocks inside such a block.
|
||||
*/
|
||||
public static function applyMaskPenaltyRule2(ByteMatrix $matrix) : int
|
||||
{
|
||||
$penalty = 0;
|
||||
$array = $matrix->getArray();
|
||||
$width = $matrix->getWidth();
|
||||
$height = $matrix->getHeight();
|
||||
|
||||
for ($y = 0; $y < $height - 1; ++$y) {
|
||||
for ($x = 0; $x < $width - 1; ++$x) {
|
||||
$value = $array[$y][$x];
|
||||
|
||||
if ($value === $array[$y][$x + 1]
|
||||
&& $value === $array[$y + 1][$x]
|
||||
&& $value === $array[$y + 1][$x + 1]
|
||||
) {
|
||||
++$penalty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::N2 * $penalty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies mask penalty rule 3 and returns the penalty.
|
||||
*
|
||||
* Finds consecutive cells of 00001011101 or 10111010000, and gives penalty
|
||||
* to them. If we find patterns like 000010111010000, we give penalties
|
||||
* twice (i.e. 40 * 2).
|
||||
*/
|
||||
public static function applyMaskPenaltyRule3(ByteMatrix $matrix) : int
|
||||
{
|
||||
$penalty = 0;
|
||||
$array = $matrix->getArray();
|
||||
$width = $matrix->getWidth();
|
||||
$height = $matrix->getHeight();
|
||||
|
||||
for ($y = 0; $y < $height; ++$y) {
|
||||
for ($x = 0; $x < $width; ++$x) {
|
||||
if ($x + 6 < $width
|
||||
&& 1 === $array[$y][$x]
|
||||
&& 0 === $array[$y][$x + 1]
|
||||
&& 1 === $array[$y][$x + 2]
|
||||
&& 1 === $array[$y][$x + 3]
|
||||
&& 1 === $array[$y][$x + 4]
|
||||
&& 0 === $array[$y][$x + 5]
|
||||
&& 1 === $array[$y][$x + 6]
|
||||
&& (
|
||||
(
|
||||
$x + 10 < $width
|
||||
&& 0 === $array[$y][$x + 7]
|
||||
&& 0 === $array[$y][$x + 8]
|
||||
&& 0 === $array[$y][$x + 9]
|
||||
&& 0 === $array[$y][$x + 10]
|
||||
)
|
||||
|| (
|
||||
$x - 4 >= 0
|
||||
&& 0 === $array[$y][$x - 1]
|
||||
&& 0 === $array[$y][$x - 2]
|
||||
&& 0 === $array[$y][$x - 3]
|
||||
&& 0 === $array[$y][$x - 4]
|
||||
)
|
||||
)
|
||||
) {
|
||||
$penalty += self::N3;
|
||||
}
|
||||
|
||||
if ($y + 6 < $height
|
||||
&& 1 === $array[$y][$x]
|
||||
&& 0 === $array[$y + 1][$x]
|
||||
&& 1 === $array[$y + 2][$x]
|
||||
&& 1 === $array[$y + 3][$x]
|
||||
&& 1 === $array[$y + 4][$x]
|
||||
&& 0 === $array[$y + 5][$x]
|
||||
&& 1 === $array[$y + 6][$x]
|
||||
&& (
|
||||
(
|
||||
$y + 10 < $height
|
||||
&& 0 === $array[$y + 7][$x]
|
||||
&& 0 === $array[$y + 8][$x]
|
||||
&& 0 === $array[$y + 9][$x]
|
||||
&& 0 === $array[$y + 10][$x]
|
||||
)
|
||||
|| (
|
||||
$y - 4 >= 0
|
||||
&& 0 === $array[$y - 1][$x]
|
||||
&& 0 === $array[$y - 2][$x]
|
||||
&& 0 === $array[$y - 3][$x]
|
||||
&& 0 === $array[$y - 4][$x]
|
||||
)
|
||||
)
|
||||
) {
|
||||
$penalty += self::N3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $penalty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies mask penalty rule 4 and returns the penalty.
|
||||
*
|
||||
* Calculates the ratio of dark cells and gives penalty if the ratio is far
|
||||
* from 50%. It gives 10 penalty for 5% distance.
|
||||
*/
|
||||
public static function applyMaskPenaltyRule4(ByteMatrix $matrix) : int
|
||||
{
|
||||
$numDarkCells = 0;
|
||||
|
||||
$array = $matrix->getArray();
|
||||
$width = $matrix->getWidth();
|
||||
$height = $matrix->getHeight();
|
||||
|
||||
for ($y = 0; $y < $height; ++$y) {
|
||||
$arrayY = $array[$y];
|
||||
|
||||
for ($x = 0; $x < $width; ++$x) {
|
||||
if (1 === $arrayY[$x]) {
|
||||
++$numDarkCells;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$numTotalCells = $height * $width;
|
||||
$darkRatio = $numDarkCells / $numTotalCells;
|
||||
$fixedPercentVariances = (int) (abs($darkRatio - 0.5) * 20);
|
||||
|
||||
return $fixedPercentVariances * self::N4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mask bit for "getMaskPattern" at "x" and "y".
|
||||
*
|
||||
* See 8.8 of JISX0510:2004 for mask pattern conditions.
|
||||
*
|
||||
* @throws InvalidArgumentException if an invalid mask pattern was supplied
|
||||
*/
|
||||
public static function getDataMaskBit(int $maskPattern, int $x, int $y) : bool
|
||||
{
|
||||
switch ($maskPattern) {
|
||||
case 0:
|
||||
$intermediate = ($y + $x) & 0x1;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
$intermediate = $y & 0x1;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$intermediate = $x % 3;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$intermediate = ($y + $x) % 3;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
$intermediate = (BitUtils::unsignedRightShift($y, 1) + (int) ($x / 3)) & 0x1;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
$temp = $y * $x;
|
||||
$intermediate = ($temp & 0x1) + ($temp % 3);
|
||||
break;
|
||||
|
||||
case 6:
|
||||
$temp = $y * $x;
|
||||
$intermediate = (($temp & 0x1) + ($temp % 3)) & 0x1;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
$temp = $y * $x;
|
||||
$intermediate = (($temp % 3) + (($y + $x) & 0x1)) & 0x1;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidArgumentException('Invalid mask pattern: ' . $maskPattern);
|
||||
}
|
||||
|
||||
return 0 == $intermediate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for applyMaskPenaltyRule1.
|
||||
*
|
||||
* We need this for doing this calculation in both vertical and horizontal
|
||||
* orders respectively.
|
||||
*/
|
||||
private static function applyMaskPenaltyRule1Internal(ByteMatrix $matrix, bool $isHorizontal) : int
|
||||
{
|
||||
$penalty = 0;
|
||||
$iLimit = $isHorizontal ? $matrix->getHeight() : $matrix->getWidth();
|
||||
$jLimit = $isHorizontal ? $matrix->getWidth() : $matrix->getHeight();
|
||||
$array = $matrix->getArray();
|
||||
|
||||
for ($i = 0; $i < $iLimit; ++$i) {
|
||||
$numSameBitCells = 0;
|
||||
$prevBit = -1;
|
||||
|
||||
for ($j = 0; $j < $jLimit; $j++) {
|
||||
$bit = $isHorizontal ? $array[$i][$j] : $array[$j][$i];
|
||||
|
||||
if ($bit === $prevBit) {
|
||||
++$numSameBitCells;
|
||||
} else {
|
||||
if ($numSameBitCells >= 5) {
|
||||
$penalty += self::N1 + ($numSameBitCells - 5);
|
||||
}
|
||||
|
||||
$numSameBitCells = 1;
|
||||
$prevBit = $bit;
|
||||
}
|
||||
}
|
||||
|
||||
if ($numSameBitCells >= 5) {
|
||||
$penalty += self::N1 + ($numSameBitCells - 5);
|
||||
}
|
||||
}
|
||||
|
||||
return $penalty;
|
||||
}
|
||||
}
|
513
vendor/bacon/bacon-qr-code/src/Encoder/MatrixUtil.php
vendored
Normal file
513
vendor/bacon/bacon-qr-code/src/Encoder/MatrixUtil.php
vendored
Normal file
|
@ -0,0 +1,513 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Encoder;
|
||||
|
||||
use BaconQrCode\Common\BitArray;
|
||||
use BaconQrCode\Common\ErrorCorrectionLevel;
|
||||
use BaconQrCode\Common\Version;
|
||||
use BaconQrCode\Exception\RuntimeException;
|
||||
use BaconQrCode\Exception\WriterException;
|
||||
|
||||
/**
|
||||
* Matrix utility.
|
||||
*/
|
||||
final class MatrixUtil
|
||||
{
|
||||
/**
|
||||
* Position detection pattern.
|
||||
*/
|
||||
private const POSITION_DETECTION_PATTERN = [
|
||||
[1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 0, 0, 1],
|
||||
[1, 0, 1, 1, 1, 0, 1],
|
||||
[1, 0, 1, 1, 1, 0, 1],
|
||||
[1, 0, 1, 1, 1, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1],
|
||||
];
|
||||
|
||||
/**
|
||||
* Position adjustment pattern.
|
||||
*/
|
||||
private const POSITION_ADJUSTMENT_PATTERN = [
|
||||
[1, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 1, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 1, 1, 1, 1],
|
||||
];
|
||||
|
||||
/**
|
||||
* Coordinates for position adjustment patterns for each version.
|
||||
*/
|
||||
private const POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE = [
|
||||
[null, null, null, null, null, null, null], // Version 1
|
||||
[ 6, 18, null, null, null, null, null], // Version 2
|
||||
[ 6, 22, null, null, null, null, null], // Version 3
|
||||
[ 6, 26, null, null, null, null, null], // Version 4
|
||||
[ 6, 30, null, null, null, null, null], // Version 5
|
||||
[ 6, 34, null, null, null, null, null], // Version 6
|
||||
[ 6, 22, 38, null, null, null, null], // Version 7
|
||||
[ 6, 24, 42, null, null, null, null], // Version 8
|
||||
[ 6, 26, 46, null, null, null, null], // Version 9
|
||||
[ 6, 28, 50, null, null, null, null], // Version 10
|
||||
[ 6, 30, 54, null, null, null, null], // Version 11
|
||||
[ 6, 32, 58, null, null, null, null], // Version 12
|
||||
[ 6, 34, 62, null, null, null, null], // Version 13
|
||||
[ 6, 26, 46, 66, null, null, null], // Version 14
|
||||
[ 6, 26, 48, 70, null, null, null], // Version 15
|
||||
[ 6, 26, 50, 74, null, null, null], // Version 16
|
||||
[ 6, 30, 54, 78, null, null, null], // Version 17
|
||||
[ 6, 30, 56, 82, null, null, null], // Version 18
|
||||
[ 6, 30, 58, 86, null, null, null], // Version 19
|
||||
[ 6, 34, 62, 90, null, null, null], // Version 20
|
||||
[ 6, 28, 50, 72, 94, null, null], // Version 21
|
||||
[ 6, 26, 50, 74, 98, null, null], // Version 22
|
||||
[ 6, 30, 54, 78, 102, null, null], // Version 23
|
||||
[ 6, 28, 54, 80, 106, null, null], // Version 24
|
||||
[ 6, 32, 58, 84, 110, null, null], // Version 25
|
||||
[ 6, 30, 58, 86, 114, null, null], // Version 26
|
||||
[ 6, 34, 62, 90, 118, null, null], // Version 27
|
||||
[ 6, 26, 50, 74, 98, 122, null], // Version 28
|
||||
[ 6, 30, 54, 78, 102, 126, null], // Version 29
|
||||
[ 6, 26, 52, 78, 104, 130, null], // Version 30
|
||||
[ 6, 30, 56, 82, 108, 134, null], // Version 31
|
||||
[ 6, 34, 60, 86, 112, 138, null], // Version 32
|
||||
[ 6, 30, 58, 86, 114, 142, null], // Version 33
|
||||
[ 6, 34, 62, 90, 118, 146, null], // Version 34
|
||||
[ 6, 30, 54, 78, 102, 126, 150], // Version 35
|
||||
[ 6, 24, 50, 76, 102, 128, 154], // Version 36
|
||||
[ 6, 28, 54, 80, 106, 132, 158], // Version 37
|
||||
[ 6, 32, 58, 84, 110, 136, 162], // Version 38
|
||||
[ 6, 26, 54, 82, 110, 138, 166], // Version 39
|
||||
[ 6, 30, 58, 86, 114, 142, 170], // Version 40
|
||||
];
|
||||
|
||||
/**
|
||||
* Type information coordinates.
|
||||
*/
|
||||
private const TYPE_INFO_COORDINATES = [
|
||||
[8, 0],
|
||||
[8, 1],
|
||||
[8, 2],
|
||||
[8, 3],
|
||||
[8, 4],
|
||||
[8, 5],
|
||||
[8, 7],
|
||||
[8, 8],
|
||||
[7, 8],
|
||||
[5, 8],
|
||||
[4, 8],
|
||||
[3, 8],
|
||||
[2, 8],
|
||||
[1, 8],
|
||||
[0, 8],
|
||||
];
|
||||
|
||||
/**
|
||||
* Version information polynomial.
|
||||
*/
|
||||
private const VERSION_INFO_POLY = 0x1f25;
|
||||
|
||||
/**
|
||||
* Type information polynomial.
|
||||
*/
|
||||
private const TYPE_INFO_POLY = 0x537;
|
||||
|
||||
/**
|
||||
* Type information mask pattern.
|
||||
*/
|
||||
private const TYPE_INFO_MASK_PATTERN = 0x5412;
|
||||
|
||||
/**
|
||||
* Clears a given matrix.
|
||||
*/
|
||||
public static function clearMatrix(ByteMatrix $matrix) : void
|
||||
{
|
||||
$matrix->clear(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a complete matrix.
|
||||
*/
|
||||
public static function buildMatrix(
|
||||
BitArray $dataBits,
|
||||
ErrorCorrectionLevel $level,
|
||||
Version $version,
|
||||
int $maskPattern,
|
||||
ByteMatrix $matrix
|
||||
) : void {
|
||||
self::clearMatrix($matrix);
|
||||
self::embedBasicPatterns($version, $matrix);
|
||||
self::embedTypeInfo($level, $maskPattern, $matrix);
|
||||
self::maybeEmbedVersionInfo($version, $matrix);
|
||||
self::embedDataBits($dataBits, $maskPattern, $matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the position detection patterns from a matrix.
|
||||
*
|
||||
* This can be useful if you need to render those patterns separately.
|
||||
*/
|
||||
public static function removePositionDetectionPatterns(ByteMatrix $matrix) : void
|
||||
{
|
||||
$pdpWidth = count(self::POSITION_DETECTION_PATTERN[0]);
|
||||
|
||||
self::removePositionDetectionPattern(0, 0, $matrix);
|
||||
self::removePositionDetectionPattern($matrix->getWidth() - $pdpWidth, 0, $matrix);
|
||||
self::removePositionDetectionPattern(0, $matrix->getWidth() - $pdpWidth, $matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds type information into a matrix.
|
||||
*/
|
||||
private static function embedTypeInfo(ErrorCorrectionLevel $level, int $maskPattern, ByteMatrix $matrix) : void
|
||||
{
|
||||
$typeInfoBits = new BitArray();
|
||||
self::makeTypeInfoBits($level, $maskPattern, $typeInfoBits);
|
||||
|
||||
$typeInfoBitsSize = $typeInfoBits->getSize();
|
||||
|
||||
for ($i = 0; $i < $typeInfoBitsSize; ++$i) {
|
||||
$bit = $typeInfoBits->get($typeInfoBitsSize - 1 - $i);
|
||||
|
||||
$x1 = self::TYPE_INFO_COORDINATES[$i][0];
|
||||
$y1 = self::TYPE_INFO_COORDINATES[$i][1];
|
||||
|
||||
$matrix->set($x1, $y1, (int) $bit);
|
||||
|
||||
if ($i < 8) {
|
||||
$x2 = $matrix->getWidth() - $i - 1;
|
||||
$y2 = 8;
|
||||
} else {
|
||||
$x2 = 8;
|
||||
$y2 = $matrix->getHeight() - 7 + ($i - 8);
|
||||
}
|
||||
|
||||
$matrix->set($x2, $y2, (int) $bit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates type information bits and appends them to a bit array.
|
||||
*
|
||||
* @throws RuntimeException if bit array resulted in invalid size
|
||||
*/
|
||||
private static function makeTypeInfoBits(ErrorCorrectionLevel $level, int $maskPattern, BitArray $bits) : void
|
||||
{
|
||||
$typeInfo = ($level->getBits() << 3) | $maskPattern;
|
||||
$bits->appendBits($typeInfo, 5);
|
||||
|
||||
$bchCode = self::calculateBchCode($typeInfo, self::TYPE_INFO_POLY);
|
||||
$bits->appendBits($bchCode, 10);
|
||||
|
||||
$maskBits = new BitArray();
|
||||
$maskBits->appendBits(self::TYPE_INFO_MASK_PATTERN, 15);
|
||||
$bits->xorBits($maskBits);
|
||||
|
||||
if (15 !== $bits->getSize()) {
|
||||
throw new RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds version information if required.
|
||||
*/
|
||||
private static function maybeEmbedVersionInfo(Version $version, ByteMatrix $matrix) : void
|
||||
{
|
||||
if ($version->getVersionNumber() < 7) {
|
||||
return;
|
||||
}
|
||||
|
||||
$versionInfoBits = new BitArray();
|
||||
self::makeVersionInfoBits($version, $versionInfoBits);
|
||||
|
||||
$bitIndex = 6 * 3 - 1;
|
||||
|
||||
for ($i = 0; $i < 6; ++$i) {
|
||||
for ($j = 0; $j < 3; ++$j) {
|
||||
$bit = $versionInfoBits->get($bitIndex);
|
||||
--$bitIndex;
|
||||
|
||||
$matrix->set($i, $matrix->getHeight() - 11 + $j, (int) $bit);
|
||||
$matrix->set($matrix->getHeight() - 11 + $j, $i, (int) $bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates version information bits and appends them to a bit array.
|
||||
*
|
||||
* @throws RuntimeException if bit array resulted in invalid size
|
||||
*/
|
||||
private static function makeVersionInfoBits(Version $version, BitArray $bits) : void
|
||||
{
|
||||
$bits->appendBits($version->getVersionNumber(), 6);
|
||||
|
||||
$bchCode = self::calculateBchCode($version->getVersionNumber(), self::VERSION_INFO_POLY);
|
||||
$bits->appendBits($bchCode, 12);
|
||||
|
||||
if (18 !== $bits->getSize()) {
|
||||
throw new RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the BCH code for a value and a polynomial.
|
||||
*/
|
||||
private static function calculateBchCode(int $value, int $poly) : int
|
||||
{
|
||||
$msbSetInPoly = self::findMsbSet($poly);
|
||||
$value <<= $msbSetInPoly - 1;
|
||||
|
||||
while (self::findMsbSet($value) >= $msbSetInPoly) {
|
||||
$value ^= $poly << (self::findMsbSet($value) - $msbSetInPoly);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and MSB set.
|
||||
*/
|
||||
private static function findMsbSet(int $value) : int
|
||||
{
|
||||
$numDigits = 0;
|
||||
|
||||
while (0 !== $value) {
|
||||
$value >>= 1;
|
||||
++$numDigits;
|
||||
}
|
||||
|
||||
return $numDigits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds basic patterns into a matrix.
|
||||
*/
|
||||
private static function embedBasicPatterns(Version $version, ByteMatrix $matrix) : void
|
||||
{
|
||||
self::embedPositionDetectionPatternsAndSeparators($matrix);
|
||||
self::embedDarkDotAtLeftBottomCorner($matrix);
|
||||
self::maybeEmbedPositionAdjustmentPatterns($version, $matrix);
|
||||
self::embedTimingPatterns($matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds position detection patterns and separators into a byte matrix.
|
||||
*/
|
||||
private static function embedPositionDetectionPatternsAndSeparators(ByteMatrix $matrix) : void
|
||||
{
|
||||
$pdpWidth = count(self::POSITION_DETECTION_PATTERN[0]);
|
||||
|
||||
self::embedPositionDetectionPattern(0, 0, $matrix);
|
||||
self::embedPositionDetectionPattern($matrix->getWidth() - $pdpWidth, 0, $matrix);
|
||||
self::embedPositionDetectionPattern(0, $matrix->getWidth() - $pdpWidth, $matrix);
|
||||
|
||||
$hspWidth = 8;
|
||||
|
||||
self::embedHorizontalSeparationPattern(0, $hspWidth - 1, $matrix);
|
||||
self::embedHorizontalSeparationPattern($matrix->getWidth() - $hspWidth, $hspWidth - 1, $matrix);
|
||||
self::embedHorizontalSeparationPattern(0, $matrix->getWidth() - $hspWidth, $matrix);
|
||||
|
||||
$vspSize = 7;
|
||||
|
||||
self::embedVerticalSeparationPattern($vspSize, 0, $matrix);
|
||||
self::embedVerticalSeparationPattern($matrix->getHeight() - $vspSize - 1, 0, $matrix);
|
||||
self::embedVerticalSeparationPattern($vspSize, $matrix->getHeight() - $vspSize, $matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds a single position detection pattern into a byte matrix.
|
||||
*/
|
||||
private static function embedPositionDetectionPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
|
||||
{
|
||||
for ($y = 0; $y < 7; ++$y) {
|
||||
for ($x = 0; $x < 7; ++$x) {
|
||||
$matrix->set($xStart + $x, $yStart + $y, self::POSITION_DETECTION_PATTERN[$y][$x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function removePositionDetectionPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
|
||||
{
|
||||
for ($y = 0; $y < 7; ++$y) {
|
||||
for ($x = 0; $x < 7; ++$x) {
|
||||
$matrix->set($xStart + $x, $yStart + $y, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds a single horizontal separation pattern.
|
||||
*
|
||||
* @throws RuntimeException if a byte was already set
|
||||
*/
|
||||
private static function embedHorizontalSeparationPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
|
||||
{
|
||||
for ($x = 0; $x < 8; $x++) {
|
||||
if (-1 !== $matrix->get($xStart + $x, $yStart)) {
|
||||
throw new RuntimeException('Byte already set');
|
||||
}
|
||||
|
||||
$matrix->set($xStart + $x, $yStart, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds a single vertical separation pattern.
|
||||
*
|
||||
* @throws RuntimeException if a byte was already set
|
||||
*/
|
||||
private static function embedVerticalSeparationPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
|
||||
{
|
||||
for ($y = 0; $y < 7; $y++) {
|
||||
if (-1 !== $matrix->get($xStart, $yStart + $y)) {
|
||||
throw new RuntimeException('Byte already set');
|
||||
}
|
||||
|
||||
$matrix->set($xStart, $yStart + $y, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds a dot at the left bottom corner.
|
||||
*
|
||||
* @throws RuntimeException if a byte was already set to 0
|
||||
*/
|
||||
private static function embedDarkDotAtLeftBottomCorner(ByteMatrix $matrix) : void
|
||||
{
|
||||
if (0 === $matrix->get(8, $matrix->getHeight() - 8)) {
|
||||
throw new RuntimeException('Byte already set to 0');
|
||||
}
|
||||
|
||||
$matrix->set(8, $matrix->getHeight() - 8, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds position adjustment patterns if required.
|
||||
*/
|
||||
private static function maybeEmbedPositionAdjustmentPatterns(Version $version, ByteMatrix $matrix) : void
|
||||
{
|
||||
if ($version->getVersionNumber() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
$index = $version->getVersionNumber() - 1;
|
||||
|
||||
$coordinates = self::POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[$index];
|
||||
$numCoordinates = count($coordinates);
|
||||
|
||||
for ($i = 0; $i < $numCoordinates; ++$i) {
|
||||
for ($j = 0; $j < $numCoordinates; ++$j) {
|
||||
$y = $coordinates[$i];
|
||||
$x = $coordinates[$j];
|
||||
|
||||
if (null === $x || null === $y) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (-1 === $matrix->get($x, $y)) {
|
||||
self::embedPositionAdjustmentPattern($x - 2, $y - 2, $matrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds a single position adjustment pattern.
|
||||
*/
|
||||
private static function embedPositionAdjustmentPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
|
||||
{
|
||||
for ($y = 0; $y < 5; $y++) {
|
||||
for ($x = 0; $x < 5; $x++) {
|
||||
$matrix->set($xStart + $x, $yStart + $y, self::POSITION_ADJUSTMENT_PATTERN[$y][$x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds timing patterns into a matrix.
|
||||
*/
|
||||
private static function embedTimingPatterns(ByteMatrix $matrix) : void
|
||||
{
|
||||
$matrixWidth = $matrix->getWidth();
|
||||
|
||||
for ($i = 8; $i < $matrixWidth - 8; ++$i) {
|
||||
$bit = ($i + 1) % 2;
|
||||
|
||||
if (-1 === $matrix->get($i, 6)) {
|
||||
$matrix->set($i, 6, $bit);
|
||||
}
|
||||
|
||||
if (-1 === $matrix->get(6, $i)) {
|
||||
$matrix->set(6, $i, $bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds "dataBits" using "getMaskPattern".
|
||||
*
|
||||
* For debugging purposes, it skips masking process if "getMaskPattern" is -1. See 8.7 of JISX0510:2004 (p.38) for
|
||||
* how to embed data bits.
|
||||
*
|
||||
* @throws WriterException if not all bits could be consumed
|
||||
*/
|
||||
private static function embedDataBits(BitArray $dataBits, int $maskPattern, ByteMatrix $matrix) : void
|
||||
{
|
||||
$bitIndex = 0;
|
||||
$direction = -1;
|
||||
|
||||
// Start from the right bottom cell.
|
||||
$x = $matrix->getWidth() - 1;
|
||||
$y = $matrix->getHeight() - 1;
|
||||
|
||||
while ($x > 0) {
|
||||
// Skip vertical timing pattern.
|
||||
if (6 === $x) {
|
||||
--$x;
|
||||
}
|
||||
|
||||
while ($y >= 0 && $y < $matrix->getHeight()) {
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$xx = $x - $i;
|
||||
|
||||
// Skip the cell if it's not empty.
|
||||
if (-1 !== $matrix->get($xx, $y)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($bitIndex < $dataBits->getSize()) {
|
||||
$bit = $dataBits->get($bitIndex);
|
||||
++$bitIndex;
|
||||
} else {
|
||||
// Padding bit. If there is no bit left, we'll fill the
|
||||
// left cells with 0, as described in 8.4.9 of
|
||||
// JISX0510:2004 (p. 24).
|
||||
$bit = false;
|
||||
}
|
||||
|
||||
// Skip masking if maskPattern is -1.
|
||||
if (-1 !== $maskPattern && MaskUtil::getDataMaskBit($maskPattern, $xx, $y)) {
|
||||
$bit = ! $bit;
|
||||
}
|
||||
|
||||
$matrix->set($xx, $y, (int) $bit);
|
||||
}
|
||||
|
||||
$y += $direction;
|
||||
}
|
||||
|
||||
$direction = -$direction;
|
||||
$y += $direction;
|
||||
$x -= 2;
|
||||
}
|
||||
|
||||
// All bits should be consumed
|
||||
if ($dataBits->getSize() !== $bitIndex) {
|
||||
throw new WriterException('Not all bits consumed (' . $bitIndex . ' out of ' . $dataBits->getSize() .')');
|
||||
}
|
||||
}
|
||||
}
|
108
vendor/bacon/bacon-qr-code/src/Encoder/QrCode.php
vendored
Normal file
108
vendor/bacon/bacon-qr-code/src/Encoder/QrCode.php
vendored
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Encoder;
|
||||
|
||||
use BaconQrCode\Common\ErrorCorrectionLevel;
|
||||
use BaconQrCode\Common\Mode;
|
||||
use BaconQrCode\Common\Version;
|
||||
|
||||
/**
|
||||
* QR code.
|
||||
*/
|
||||
final class QrCode
|
||||
{
|
||||
/**
|
||||
* Number of possible mask patterns.
|
||||
*/
|
||||
public const NUM_MASK_PATTERNS = 8;
|
||||
|
||||
/**
|
||||
* Mask pattern of the QR code.
|
||||
*/
|
||||
private int $maskPattern = -1;
|
||||
|
||||
/**
|
||||
* Matrix of the QR code.
|
||||
*/
|
||||
private ByteMatrix $matrix;
|
||||
|
||||
public function __construct(
|
||||
private readonly Mode $mode,
|
||||
private readonly ErrorCorrectionLevel $errorCorrectionLevel,
|
||||
private readonly Version $version,
|
||||
int $maskPattern,
|
||||
ByteMatrix $matrix
|
||||
) {
|
||||
$this->maskPattern = $maskPattern;
|
||||
$this->matrix = $matrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the mode.
|
||||
*/
|
||||
public function getMode() : Mode
|
||||
{
|
||||
return $this->mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the EC level.
|
||||
*/
|
||||
public function getErrorCorrectionLevel() : ErrorCorrectionLevel
|
||||
{
|
||||
return $this->errorCorrectionLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version.
|
||||
*/
|
||||
public function getVersion() : Version
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the mask pattern.
|
||||
*/
|
||||
public function getMaskPattern() : int
|
||||
{
|
||||
return $this->maskPattern;
|
||||
}
|
||||
|
||||
public function getMatrix(): ByteMatrix
|
||||
{
|
||||
return $this->matrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether a mask pattern is valid.
|
||||
*/
|
||||
public static function isValidMaskPattern(int $maskPattern) : bool
|
||||
{
|
||||
return $maskPattern > 0 && $maskPattern < self::NUM_MASK_PATTERNS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the QR code.
|
||||
*/
|
||||
public function __toString() : string
|
||||
{
|
||||
$result = "<<\n"
|
||||
. ' mode: ' . $this->mode . "\n"
|
||||
. ' ecLevel: ' . $this->errorCorrectionLevel . "\n"
|
||||
. ' version: ' . $this->version . "\n"
|
||||
. ' maskPattern: ' . $this->maskPattern . "\n";
|
||||
|
||||
if ($this->matrix === null) {
|
||||
$result .= " matrix: null\n";
|
||||
} else {
|
||||
$result .= " matrix:\n";
|
||||
$result .= $this->matrix;
|
||||
}
|
||||
|
||||
$result .= ">>\n";
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
10
vendor/bacon/bacon-qr-code/src/Exception/ExceptionInterface.php
vendored
Normal file
10
vendor/bacon/bacon-qr-code/src/Exception/ExceptionInterface.php
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Exception;
|
||||
|
||||
use Throwable;
|
||||
|
||||
interface ExceptionInterface extends Throwable
|
||||
{
|
||||
}
|
8
vendor/bacon/bacon-qr-code/src/Exception/InvalidArgumentException.php
vendored
Normal file
8
vendor/bacon/bacon-qr-code/src/Exception/InvalidArgumentException.php
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Exception;
|
||||
|
||||
final class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
}
|
8
vendor/bacon/bacon-qr-code/src/Exception/OutOfBoundsException.php
vendored
Normal file
8
vendor/bacon/bacon-qr-code/src/Exception/OutOfBoundsException.php
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Exception;
|
||||
|
||||
final class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface
|
||||
{
|
||||
}
|
8
vendor/bacon/bacon-qr-code/src/Exception/RuntimeException.php
vendored
Normal file
8
vendor/bacon/bacon-qr-code/src/Exception/RuntimeException.php
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Exception;
|
||||
|
||||
final class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
8
vendor/bacon/bacon-qr-code/src/Exception/UnexpectedValueException.php
vendored
Normal file
8
vendor/bacon/bacon-qr-code/src/Exception/UnexpectedValueException.php
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Exception;
|
||||
|
||||
final class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface
|
||||
{
|
||||
}
|
8
vendor/bacon/bacon-qr-code/src/Exception/WriterException.php
vendored
Normal file
8
vendor/bacon/bacon-qr-code/src/Exception/WriterException.php
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Exception;
|
||||
|
||||
final class WriterException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
44
vendor/bacon/bacon-qr-code/src/Renderer/Color/Alpha.php
vendored
Normal file
44
vendor/bacon/bacon-qr-code/src/Renderer/Color/Alpha.php
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Color;
|
||||
|
||||
use BaconQrCode\Exception;
|
||||
|
||||
final class Alpha implements ColorInterface
|
||||
{
|
||||
/**
|
||||
* @param int $alpha the alpha value, 0 to 100
|
||||
*/
|
||||
public function __construct(private readonly int $alpha, private readonly ColorInterface $baseColor)
|
||||
{
|
||||
if ($alpha < 0 || $alpha > 100) {
|
||||
throw new Exception\InvalidArgumentException('Alpha must be between 0 and 100');
|
||||
}
|
||||
}
|
||||
|
||||
public function getAlpha() : int
|
||||
{
|
||||
return $this->alpha;
|
||||
}
|
||||
|
||||
public function getBaseColor() : ColorInterface
|
||||
{
|
||||
return $this->baseColor;
|
||||
}
|
||||
|
||||
public function toRgb() : Rgb
|
||||
{
|
||||
return $this->baseColor->toRgb();
|
||||
}
|
||||
|
||||
public function toCmyk() : Cmyk
|
||||
{
|
||||
return $this->baseColor->toCmyk();
|
||||
}
|
||||
|
||||
public function toGray() : Gray
|
||||
{
|
||||
return $this->baseColor->toGray();
|
||||
}
|
||||
}
|
82
vendor/bacon/bacon-qr-code/src/Renderer/Color/Cmyk.php
vendored
Normal file
82
vendor/bacon/bacon-qr-code/src/Renderer/Color/Cmyk.php
vendored
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Color;
|
||||
|
||||
use BaconQrCode\Exception;
|
||||
|
||||
final class Cmyk implements ColorInterface
|
||||
{
|
||||
/**
|
||||
* @param int $cyan the cyan amount, 0 to 100
|
||||
* @param int $magenta the magenta amount, 0 to 100
|
||||
* @param int $yellow the yellow amount, 0 to 100
|
||||
* @param int $black the black amount, 0 to 100
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly int $cyan,
|
||||
private readonly int $magenta,
|
||||
private readonly int $yellow,
|
||||
private readonly int $black
|
||||
) {
|
||||
if ($cyan < 0 || $cyan > 100) {
|
||||
throw new Exception\InvalidArgumentException('Cyan must be between 0 and 100');
|
||||
}
|
||||
|
||||
if ($magenta < 0 || $magenta > 100) {
|
||||
throw new Exception\InvalidArgumentException('Magenta must be between 0 and 100');
|
||||
}
|
||||
|
||||
if ($yellow < 0 || $yellow > 100) {
|
||||
throw new Exception\InvalidArgumentException('Yellow must be between 0 and 100');
|
||||
}
|
||||
|
||||
if ($black < 0 || $black > 100) {
|
||||
throw new Exception\InvalidArgumentException('Black must be between 0 and 100');
|
||||
}
|
||||
}
|
||||
|
||||
public function getCyan() : int
|
||||
{
|
||||
return $this->cyan;
|
||||
}
|
||||
|
||||
public function getMagenta() : int
|
||||
{
|
||||
return $this->magenta;
|
||||
}
|
||||
|
||||
public function getYellow() : int
|
||||
{
|
||||
return $this->yellow;
|
||||
}
|
||||
|
||||
public function getBlack() : int
|
||||
{
|
||||
return $this->black;
|
||||
}
|
||||
|
||||
public function toRgb() : Rgb
|
||||
{
|
||||
$k = $this->black / 100;
|
||||
$c = (-$k * $this->cyan + $k * 100 + $this->cyan) / 100;
|
||||
$m = (-$k * $this->magenta + $k * 100 + $this->magenta) / 100;
|
||||
$y = (-$k * $this->yellow + $k * 100 + $this->yellow) / 100;
|
||||
|
||||
return new Rgb(
|
||||
(int) (-$c * 255 + 255),
|
||||
(int) (-$m * 255 + 255),
|
||||
(int) (-$y * 255 + 255)
|
||||
);
|
||||
}
|
||||
|
||||
public function toCmyk() : Cmyk
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toGray() : Gray
|
||||
{
|
||||
return $this->toRgb()->toGray();
|
||||
}
|
||||
}
|
22
vendor/bacon/bacon-qr-code/src/Renderer/Color/ColorInterface.php
vendored
Normal file
22
vendor/bacon/bacon-qr-code/src/Renderer/Color/ColorInterface.php
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Color;
|
||||
|
||||
interface ColorInterface
|
||||
{
|
||||
/**
|
||||
* Converts the color to RGB.
|
||||
*/
|
||||
public function toRgb() : Rgb;
|
||||
|
||||
/**
|
||||
* Converts the color to CMYK.
|
||||
*/
|
||||
public function toCmyk() : Cmyk;
|
||||
|
||||
/**
|
||||
* Converts the color to gray.
|
||||
*/
|
||||
public function toGray() : Gray;
|
||||
}
|
39
vendor/bacon/bacon-qr-code/src/Renderer/Color/Gray.php
vendored
Normal file
39
vendor/bacon/bacon-qr-code/src/Renderer/Color/Gray.php
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Color;
|
||||
|
||||
use BaconQrCode\Exception;
|
||||
|
||||
final class Gray implements ColorInterface
|
||||
{
|
||||
/**
|
||||
* @param int $gray the gray value between 0 (black) and 100 (white)
|
||||
*/
|
||||
public function __construct(private readonly int $gray)
|
||||
{
|
||||
if ($gray < 0 || $gray > 100) {
|
||||
throw new Exception\InvalidArgumentException('Gray must be between 0 and 100');
|
||||
}
|
||||
}
|
||||
|
||||
public function getGray() : int
|
||||
{
|
||||
return $this->gray;
|
||||
}
|
||||
|
||||
public function toRgb() : Rgb
|
||||
{
|
||||
return new Rgb((int) ($this->gray * 2.55), (int) ($this->gray * 2.55), (int) ($this->gray * 2.55));
|
||||
}
|
||||
|
||||
public function toCmyk() : Cmyk
|
||||
{
|
||||
return new Cmyk(0, 0, 0, 100 - $this->gray);
|
||||
}
|
||||
|
||||
public function toGray() : Gray
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
73
vendor/bacon/bacon-qr-code/src/Renderer/Color/Rgb.php
vendored
Normal file
73
vendor/bacon/bacon-qr-code/src/Renderer/Color/Rgb.php
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Color;
|
||||
|
||||
use BaconQrCode\Exception;
|
||||
|
||||
final class Rgb implements ColorInterface
|
||||
{
|
||||
/**
|
||||
* @param int $red the red amount of the color, 0 to 255
|
||||
* @param int $green the green amount of the color, 0 to 255
|
||||
* @param int $blue the blue amount of the color, 0 to 255
|
||||
*/
|
||||
public function __construct(private readonly int $red, private readonly int $green, private readonly int $blue)
|
||||
{
|
||||
if ($red < 0 || $red > 255) {
|
||||
throw new Exception\InvalidArgumentException('Red must be between 0 and 255');
|
||||
}
|
||||
|
||||
if ($green < 0 || $green > 255) {
|
||||
throw new Exception\InvalidArgumentException('Green must be between 0 and 255');
|
||||
}
|
||||
|
||||
if ($blue < 0 || $blue > 255) {
|
||||
throw new Exception\InvalidArgumentException('Blue must be between 0 and 255');
|
||||
}
|
||||
}
|
||||
|
||||
public function getRed() : int
|
||||
{
|
||||
return $this->red;
|
||||
}
|
||||
|
||||
public function getGreen() : int
|
||||
{
|
||||
return $this->green;
|
||||
}
|
||||
|
||||
public function getBlue() : int
|
||||
{
|
||||
return $this->blue;
|
||||
}
|
||||
|
||||
public function toRgb() : Rgb
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toCmyk() : Cmyk
|
||||
{
|
||||
$c = 1 - ($this->red / 255);
|
||||
$m = 1 - ($this->green / 255);
|
||||
$y = 1 - ($this->blue / 255);
|
||||
$k = min($c, $m, $y);
|
||||
|
||||
if ($k === 0) {
|
||||
return new Cmyk(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
return new Cmyk(
|
||||
(int) (100 * ($c - $k) / (1 - $k)),
|
||||
(int) (100 * ($m - $k) / (1 - $k)),
|
||||
(int) (100 * ($y - $k) / (1 - $k)),
|
||||
(int) (100 * $k)
|
||||
);
|
||||
}
|
||||
|
||||
public function toGray() : Gray
|
||||
{
|
||||
return new Gray((int) (($this->red * 0.21 + $this->green * 0.71 + $this->blue * 0.07) / 2.55));
|
||||
}
|
||||
}
|
26
vendor/bacon/bacon-qr-code/src/Renderer/Eye/CompositeEye.php
vendored
Normal file
26
vendor/bacon/bacon-qr-code/src/Renderer/Eye/CompositeEye.php
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Eye;
|
||||
|
||||
use BaconQrCode\Renderer\Path\Path;
|
||||
|
||||
/**
|
||||
* Combines the style of two different eyes.
|
||||
*/
|
||||
final class CompositeEye implements EyeInterface
|
||||
{
|
||||
public function __construct(private readonly EyeInterface $externalEye, private readonly EyeInterface $internalEye)
|
||||
{
|
||||
}
|
||||
|
||||
public function getExternalPath() : Path
|
||||
{
|
||||
return $this->externalEye->getExternalPath();
|
||||
}
|
||||
|
||||
public function getInternalPath() : Path
|
||||
{
|
||||
return $this->internalEye->getInternalPath();
|
||||
}
|
||||
}
|
26
vendor/bacon/bacon-qr-code/src/Renderer/Eye/EyeInterface.php
vendored
Normal file
26
vendor/bacon/bacon-qr-code/src/Renderer/Eye/EyeInterface.php
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Eye;
|
||||
|
||||
use BaconQrCode\Renderer\Path\Path;
|
||||
|
||||
/**
|
||||
* Interface for describing the look of an eye.
|
||||
*/
|
||||
interface EyeInterface
|
||||
{
|
||||
/**
|
||||
* Returns the path of the external eye element.
|
||||
*
|
||||
* The path origin point (0, 0) must be anchored at the middle of the path.
|
||||
*/
|
||||
public function getExternalPath() : Path;
|
||||
|
||||
/**
|
||||
* Returns the path of the internal eye element.
|
||||
*
|
||||
* The path origin point (0, 0) must be anchored at the middle of the path.
|
||||
*/
|
||||
public function getInternalPath() : Path;
|
||||
}
|
48
vendor/bacon/bacon-qr-code/src/Renderer/Eye/ModuleEye.php
vendored
Normal file
48
vendor/bacon/bacon-qr-code/src/Renderer/Eye/ModuleEye.php
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Eye;
|
||||
|
||||
use BaconQrCode\Encoder\ByteMatrix;
|
||||
use BaconQrCode\Renderer\Module\ModuleInterface;
|
||||
use BaconQrCode\Renderer\Path\Path;
|
||||
|
||||
/**
|
||||
* Renders an eye based on a module renderer.
|
||||
*/
|
||||
final class ModuleEye implements EyeInterface
|
||||
{
|
||||
public function __construct(private readonly ModuleInterface $module)
|
||||
{
|
||||
}
|
||||
|
||||
public function getExternalPath() : Path
|
||||
{
|
||||
$matrix = new ByteMatrix(7, 7);
|
||||
|
||||
for ($x = 0; $x < 7; ++$x) {
|
||||
$matrix->set($x, 0, 1);
|
||||
$matrix->set($x, 6, 1);
|
||||
}
|
||||
|
||||
for ($y = 1; $y < 6; ++$y) {
|
||||
$matrix->set(0, $y, 1);
|
||||
$matrix->set(6, $y, 1);
|
||||
}
|
||||
|
||||
return $this->module->createPath($matrix)->translate(-3.5, -3.5);
|
||||
}
|
||||
|
||||
public function getInternalPath() : Path
|
||||
{
|
||||
$matrix = new ByteMatrix(3, 3);
|
||||
|
||||
for ($x = 0; $x < 3; ++$x) {
|
||||
for ($y = 0; $y < 3; ++$y) {
|
||||
$matrix->set($x, $y, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->module->createPath($matrix)->translate(-1.5, -1.5);
|
||||
}
|
||||
}
|
56
vendor/bacon/bacon-qr-code/src/Renderer/Eye/PointyEye.php
vendored
Normal file
56
vendor/bacon/bacon-qr-code/src/Renderer/Eye/PointyEye.php
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Eye;
|
||||
|
||||
use BaconQrCode\Renderer\Path\Path;
|
||||
|
||||
/**
|
||||
* Renders the outer eye as solid with a curved corner and inner eye as a circle.
|
||||
*/
|
||||
final class PointyEye implements EyeInterface
|
||||
{
|
||||
/**
|
||||
* @var self|null
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function instance() : self
|
||||
{
|
||||
return self::$instance ?: self::$instance = new self();
|
||||
}
|
||||
|
||||
public function getExternalPath() : Path
|
||||
{
|
||||
return (new Path())
|
||||
->move(-3.5, 3.5)
|
||||
->line(-3.5, 0)
|
||||
->ellipticArc(3.5, 3.5, 0, false, true, 0, -3.5)
|
||||
->line(3.5, -3.5)
|
||||
->line(3.5, 3.5)
|
||||
->close()
|
||||
->move(2.5, 0)
|
||||
->ellipticArc(2.5, 2.5, 0, false, true, 0, 2.5)
|
||||
->ellipticArc(2.5, 2.5, 0, false, true, -2.5, 0)
|
||||
->ellipticArc(2.5, 2.5, 0, false, true, 0, -2.5)
|
||||
->ellipticArc(2.5, 2.5, 0, false, true, 2.5, 0)
|
||||
->close()
|
||||
;
|
||||
}
|
||||
|
||||
public function getInternalPath() : Path
|
||||
{
|
||||
return (new Path())
|
||||
->move(1.5, 0)
|
||||
->ellipticArc(1.5, 1.5, 0., false, true, 0., 1.5)
|
||||
->ellipticArc(1.5, 1.5, 0., false, true, -1.5, 0.)
|
||||
->ellipticArc(1.5, 1.5, 0., false, true, 0., -1.5)
|
||||
->ellipticArc(1.5, 1.5, 0., false, true, 1.5, 0.)
|
||||
->close()
|
||||
;
|
||||
}
|
||||
}
|
51
vendor/bacon/bacon-qr-code/src/Renderer/Eye/SimpleCircleEye.php
vendored
Normal file
51
vendor/bacon/bacon-qr-code/src/Renderer/Eye/SimpleCircleEye.php
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Eye;
|
||||
|
||||
use BaconQrCode\Renderer\Path\Path;
|
||||
|
||||
/**
|
||||
* Renders the inner eye as a circle.
|
||||
*/
|
||||
final class SimpleCircleEye implements EyeInterface
|
||||
{
|
||||
private static ?SimpleCircleEye $instance = null;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function instance() : self
|
||||
{
|
||||
return self::$instance ?: self::$instance = new self();
|
||||
}
|
||||
|
||||
public function getExternalPath() : Path
|
||||
{
|
||||
return (new Path())
|
||||
->move(-3.5, -3.5)
|
||||
->line(3.5, -3.5)
|
||||
->line(3.5, 3.5)
|
||||
->line(-3.5, 3.5)
|
||||
->close()
|
||||
->move(-2.5, -2.5)
|
||||
->line(-2.5, 2.5)
|
||||
->line(2.5, 2.5)
|
||||
->line(2.5, -2.5)
|
||||
->close()
|
||||
;
|
||||
}
|
||||
|
||||
public function getInternalPath() : Path
|
||||
{
|
||||
return (new Path())
|
||||
->move(1.5, 0)
|
||||
->ellipticArc(1.5, 1.5, 0., false, true, 0., 1.5)
|
||||
->ellipticArc(1.5, 1.5, 0., false, true, -1.5, 0.)
|
||||
->ellipticArc(1.5, 1.5, 0., false, true, 0., -1.5)
|
||||
->ellipticArc(1.5, 1.5, 0., false, true, 1.5, 0.)
|
||||
->close()
|
||||
;
|
||||
}
|
||||
}
|
50
vendor/bacon/bacon-qr-code/src/Renderer/Eye/SquareEye.php
vendored
Normal file
50
vendor/bacon/bacon-qr-code/src/Renderer/Eye/SquareEye.php
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Eye;
|
||||
|
||||
use BaconQrCode\Renderer\Path\Path;
|
||||
|
||||
/**
|
||||
* Renders the eyes in their default square shape.
|
||||
*/
|
||||
final class SquareEye implements EyeInterface
|
||||
{
|
||||
private static ?SquareEye $instance = null;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function instance() : self
|
||||
{
|
||||
return self::$instance ?: self::$instance = new self();
|
||||
}
|
||||
|
||||
public function getExternalPath() : Path
|
||||
{
|
||||
return (new Path())
|
||||
->move(-3.5, -3.5)
|
||||
->line(3.5, -3.5)
|
||||
->line(3.5, 3.5)
|
||||
->line(-3.5, 3.5)
|
||||
->close()
|
||||
->move(-2.5, -2.5)
|
||||
->line(-2.5, 2.5)
|
||||
->line(2.5, 2.5)
|
||||
->line(2.5, -2.5)
|
||||
->close()
|
||||
;
|
||||
}
|
||||
|
||||
public function getInternalPath() : Path
|
||||
{
|
||||
return (new Path())
|
||||
->move(-1.5, -1.5)
|
||||
->line(1.5, -1.5)
|
||||
->line(1.5, 1.5)
|
||||
->line(-1.5, 1.5)
|
||||
->close()
|
||||
;
|
||||
}
|
||||
}
|
238
vendor/bacon/bacon-qr-code/src/Renderer/GDLibRenderer.php
vendored
Normal file
238
vendor/bacon/bacon-qr-code/src/Renderer/GDLibRenderer.php
vendored
Normal file
|
@ -0,0 +1,238 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BaconQrCode\Renderer;
|
||||
|
||||
use BaconQrCode\Encoder\ByteMatrix;
|
||||
use BaconQrCode\Encoder\MatrixUtil;
|
||||
use BaconQrCode\Encoder\QrCode;
|
||||
use BaconQrCode\Exception\InvalidArgumentException;
|
||||
use BaconQrCode\Exception\RuntimeException;
|
||||
use BaconQrCode\Renderer\Color\Alpha;
|
||||
use BaconQrCode\Renderer\Color\ColorInterface;
|
||||
use BaconQrCode\Renderer\RendererStyle\EyeFill;
|
||||
use BaconQrCode\Renderer\RendererStyle\Fill;
|
||||
use GdImage;
|
||||
|
||||
final class GDLibRenderer implements RendererInterface
|
||||
{
|
||||
private ?GdImage $image;
|
||||
|
||||
/**
|
||||
* @var array<string, int>
|
||||
*/
|
||||
private array $colors;
|
||||
|
||||
public function __construct(
|
||||
private int $size,
|
||||
private int $margin = 4,
|
||||
private string $imageFormat = 'png',
|
||||
private int $compressionQuality = 9,
|
||||
private ?Fill $fill = null
|
||||
) {
|
||||
if (! extension_loaded('gd') || ! function_exists('gd_info')) {
|
||||
throw new RuntimeException('You need to install the GD extension to use this back end');
|
||||
}
|
||||
|
||||
if ($this->fill === null) {
|
||||
$this->fill = Fill::default();
|
||||
}
|
||||
if ($this->fill->hasGradientFill()) {
|
||||
throw new InvalidArgumentException('GDLibRenderer does not support gradients');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if matrix width doesn't match height
|
||||
*/
|
||||
public function render(QrCode $qrCode): string
|
||||
{
|
||||
$matrix = $qrCode->getMatrix();
|
||||
$matrixSize = $matrix->getWidth();
|
||||
|
||||
if ($matrixSize !== $matrix->getHeight()) {
|
||||
throw new InvalidArgumentException('Matrix must have the same width and height');
|
||||
}
|
||||
|
||||
MatrixUtil::removePositionDetectionPatterns($matrix);
|
||||
$this->newImage();
|
||||
$this->draw($matrix);
|
||||
|
||||
return $this->renderImage();
|
||||
}
|
||||
|
||||
private function newImage(): void
|
||||
{
|
||||
$img = imagecreatetruecolor($this->size, $this->size);
|
||||
if ($img === false) {
|
||||
throw new RuntimeException('Failed to create image of that size');
|
||||
}
|
||||
|
||||
$this->image = $img;
|
||||
imagealphablending($this->image, false);
|
||||
imagesavealpha($this->image, true);
|
||||
|
||||
|
||||
$bg = $this->getColor($this->fill->getBackgroundColor());
|
||||
imagefilledrectangle($this->image, 0, 0, $this->size, $this->size, $bg);
|
||||
imagealphablending($this->image, true);
|
||||
}
|
||||
|
||||
private function draw(ByteMatrix $matrix): void
|
||||
{
|
||||
$matrixSize = $matrix->getWidth();
|
||||
|
||||
$pointsOnSide = $matrix->getWidth() + $this->margin * 2;
|
||||
$pointInPx = $this->size / $pointsOnSide;
|
||||
|
||||
$this->drawEye(0, 0, $pointInPx, $this->fill->getTopLeftEyeFill());
|
||||
$this->drawEye($matrixSize - 7, 0, $pointInPx, $this->fill->getTopRightEyeFill());
|
||||
$this->drawEye(0, $matrixSize - 7, $pointInPx, $this->fill->getBottomLeftEyeFill());
|
||||
|
||||
$rows = $matrix->getArray()->toArray();
|
||||
$color = $this->getColor($this->fill->getForegroundColor());
|
||||
for ($y = 0; $y < $matrixSize; $y += 1) {
|
||||
for ($x = 0; $x < $matrixSize; $x += 1) {
|
||||
if (! $rows[$y][$x]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$points = $this->normalizePoints([
|
||||
($this->margin + $x) * $pointInPx, ($this->margin + $y) * $pointInPx,
|
||||
($this->margin + $x + 1) * $pointInPx, ($this->margin + $y) * $pointInPx,
|
||||
($this->margin + $x + 1) * $pointInPx, ($this->margin + $y + 1) * $pointInPx,
|
||||
($this->margin + $x) * $pointInPx, ($this->margin + $y + 1) * $pointInPx,
|
||||
]);
|
||||
imagefilledpolygon($this->image, $points, $color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function drawEye(int $xOffset, int $yOffset, float $pointInPx, EyeFill $eyeFill): void
|
||||
{
|
||||
$internalColor = $this->getColor($eyeFill->inheritsInternalColor()
|
||||
? $this->fill->getForegroundColor()
|
||||
: $eyeFill->getInternalColor());
|
||||
|
||||
$externalColor = $this->getColor($eyeFill->inheritsExternalColor()
|
||||
? $this->fill->getForegroundColor()
|
||||
: $eyeFill->getExternalColor());
|
||||
|
||||
for ($y = 0; $y < 7; $y += 1) {
|
||||
for ($x = 0; $x < 7; $x += 1) {
|
||||
if ((($y === 1 || $y === 5) && $x > 0 && $x < 6) || (($x === 1 || $x === 5) && $y > 0 && $y < 6)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$points = $this->normalizePoints([
|
||||
($this->margin + $x + $xOffset) * $pointInPx, ($this->margin + $y + $yOffset) * $pointInPx,
|
||||
($this->margin + $x + $xOffset + 1) * $pointInPx, ($this->margin + $y + $yOffset) * $pointInPx,
|
||||
($this->margin + $x + $xOffset + 1) * $pointInPx, ($this->margin + $y + $yOffset + 1) * $pointInPx,
|
||||
($this->margin + $x + $xOffset) * $pointInPx, ($this->margin + $y + $yOffset + 1) * $pointInPx,
|
||||
]);
|
||||
|
||||
if ($y > 1 && $y < 5 && $x > 1 && $x < 5) {
|
||||
imagefilledpolygon($this->image, $points, $internalColor);
|
||||
} else {
|
||||
imagefilledpolygon($this->image, $points, $externalColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize points will trim right and bottom line by 1 pixel.
|
||||
* Otherwise pixels of neighbors are overlapping which leads to issue with transparency and small QR codes.
|
||||
*/
|
||||
private function normalizePoints(array $points): array
|
||||
{
|
||||
$maxX = $maxY = 0;
|
||||
for ($i = 0; $i < count($points); $i += 2) {
|
||||
// Do manual round as GD just removes decimal part
|
||||
$points[$i] = $newX = round($points[$i]);
|
||||
$points[$i + 1] = $newY = round($points[$i + 1]);
|
||||
|
||||
$maxX = max($maxX, $newX);
|
||||
$maxY = max($maxY, $newY);
|
||||
}
|
||||
|
||||
// Do trimming only if there are 4 points (8 coordinates), assumes this is square.
|
||||
|
||||
for ($i = 0; $i < count($points); $i += 2) {
|
||||
$points[$i] = min($points[$i], $maxX - 1);
|
||||
$points[$i + 1] = min($points[$i + 1], $maxY - 1);
|
||||
}
|
||||
|
||||
return $points;
|
||||
}
|
||||
|
||||
private function renderImage(): string
|
||||
{
|
||||
ob_start();
|
||||
$quality = $this->compressionQuality;
|
||||
switch ($this->imageFormat) {
|
||||
case 'png':
|
||||
if ($quality > 9 || $quality < 0) {
|
||||
$quality = 9;
|
||||
}
|
||||
imagepng($this->image, null, $quality);
|
||||
break;
|
||||
|
||||
case 'gif':
|
||||
imagegif($this->image, null);
|
||||
break;
|
||||
|
||||
case 'jpeg':
|
||||
case 'jpg':
|
||||
if ($quality > 100 || $quality < 0) {
|
||||
$quality = 85;
|
||||
}
|
||||
imagejpeg($this->image, null, $quality);
|
||||
break;
|
||||
default:
|
||||
ob_end_clean();
|
||||
throw new InvalidArgumentException(
|
||||
'Supported image formats are jpeg, png and gif, got: ' . $this->imageFormat
|
||||
);
|
||||
}
|
||||
|
||||
imagedestroy($this->image);
|
||||
$this->colors = [];
|
||||
$this->image = null;
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
private function getColor(ColorInterface $color): int
|
||||
{
|
||||
$alpha = 100;
|
||||
|
||||
if ($color instanceof Alpha) {
|
||||
$alpha = $color->getAlpha();
|
||||
$color = $color->getBaseColor();
|
||||
}
|
||||
|
||||
$rgb = $color->toRgb();
|
||||
|
||||
$colorKey = sprintf('%02X%02X%02X%02X', $rgb->getRed(), $rgb->getGreen(), $rgb->getBlue(), $alpha);
|
||||
|
||||
if (! isset($this->colors[$colorKey])) {
|
||||
$colorId = imagecolorallocatealpha(
|
||||
$this->image,
|
||||
$rgb->getRed(),
|
||||
$rgb->getGreen(),
|
||||
$rgb->getBlue(),
|
||||
(int)((100 - $alpha) / 100 * 127) // Alpha for GD is in range 0 (opaque) - 127 (transparent)
|
||||
);
|
||||
|
||||
if ($colorId === false) {
|
||||
throw new RuntimeException('Failed to create color: #' . $colorKey);
|
||||
}
|
||||
|
||||
$this->colors[$colorKey] = $colorId;
|
||||
}
|
||||
|
||||
return $this->colors[$colorKey];
|
||||
}
|
||||
}
|
373
vendor/bacon/bacon-qr-code/src/Renderer/Image/EpsImageBackEnd.php
vendored
Normal file
373
vendor/bacon/bacon-qr-code/src/Renderer/Image/EpsImageBackEnd.php
vendored
Normal file
|
@ -0,0 +1,373 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Image;
|
||||
|
||||
use BaconQrCode\Exception\RuntimeException;
|
||||
use BaconQrCode\Renderer\Color\Alpha;
|
||||
use BaconQrCode\Renderer\Color\Cmyk;
|
||||
use BaconQrCode\Renderer\Color\ColorInterface;
|
||||
use BaconQrCode\Renderer\Color\Gray;
|
||||
use BaconQrCode\Renderer\Color\Rgb;
|
||||
use BaconQrCode\Renderer\Path\Close;
|
||||
use BaconQrCode\Renderer\Path\Curve;
|
||||
use BaconQrCode\Renderer\Path\EllipticArc;
|
||||
use BaconQrCode\Renderer\Path\Line;
|
||||
use BaconQrCode\Renderer\Path\Move;
|
||||
use BaconQrCode\Renderer\Path\Path;
|
||||
use BaconQrCode\Renderer\RendererStyle\Gradient;
|
||||
use BaconQrCode\Renderer\RendererStyle\GradientType;
|
||||
|
||||
final class EpsImageBackEnd implements ImageBackEndInterface
|
||||
{
|
||||
private const PRECISION = 3;
|
||||
|
||||
private ?string $eps;
|
||||
|
||||
public function new(int $size, ColorInterface $backgroundColor) : void
|
||||
{
|
||||
$this->eps = "%!PS-Adobe-3.0 EPSF-3.0\n"
|
||||
. "%%Creator: BaconQrCode\n"
|
||||
. sprintf("%%%%BoundingBox: 0 0 %d %d \n", $size, $size)
|
||||
. "%%BeginProlog\n"
|
||||
. "save\n"
|
||||
. "50 dict begin\n"
|
||||
. "/q { gsave } bind def\n"
|
||||
. "/Q { grestore } bind def\n"
|
||||
. "/s { scale } bind def\n"
|
||||
. "/t { translate } bind def\n"
|
||||
. "/r { rotate } bind def\n"
|
||||
. "/n { newpath } bind def\n"
|
||||
. "/m { moveto } bind def\n"
|
||||
. "/l { lineto } bind def\n"
|
||||
. "/c { curveto } bind def\n"
|
||||
. "/z { closepath } bind def\n"
|
||||
. "/f { eofill } bind def\n"
|
||||
. "/rgb { setrgbcolor } bind def\n"
|
||||
. "/cmyk { setcmykcolor } bind def\n"
|
||||
. "/gray { setgray } bind def\n"
|
||||
. "%%EndProlog\n"
|
||||
. "1 -1 s\n"
|
||||
. sprintf("0 -%d t\n", $size);
|
||||
|
||||
if ($backgroundColor instanceof Alpha && 0 === $backgroundColor->getAlpha()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->eps .= wordwrap(
|
||||
'0 0 m'
|
||||
. sprintf(' %s 0 l', (string) $size)
|
||||
. sprintf(' %s %s l', (string) $size, (string) $size)
|
||||
. sprintf(' 0 %s l', (string) $size)
|
||||
. ' z'
|
||||
. ' ' .$this->getColorSetString($backgroundColor) . " f\n",
|
||||
75,
|
||||
"\n "
|
||||
);
|
||||
}
|
||||
|
||||
public function scale(float $size) : void
|
||||
{
|
||||
if (null === $this->eps) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$this->eps .= sprintf("%1\$s %1\$s s\n", round($size, self::PRECISION));
|
||||
}
|
||||
|
||||
public function translate(float $x, float $y) : void
|
||||
{
|
||||
if (null === $this->eps) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$this->eps .= sprintf("%s %s t\n", round($x, self::PRECISION), round($y, self::PRECISION));
|
||||
}
|
||||
|
||||
public function rotate(int $degrees) : void
|
||||
{
|
||||
if (null === $this->eps) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$this->eps .= sprintf("%d r\n", $degrees);
|
||||
}
|
||||
|
||||
public function push() : void
|
||||
{
|
||||
if (null === $this->eps) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$this->eps .= "q\n";
|
||||
}
|
||||
|
||||
public function pop() : void
|
||||
{
|
||||
if (null === $this->eps) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$this->eps .= "Q\n";
|
||||
}
|
||||
|
||||
public function drawPathWithColor(Path $path, ColorInterface $color) : void
|
||||
{
|
||||
if (null === $this->eps) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$fromX = 0;
|
||||
$fromY = 0;
|
||||
$this->eps .= wordwrap(
|
||||
'n '
|
||||
. $this->drawPathOperations($path, $fromX, $fromY)
|
||||
. ' ' . $this->getColorSetString($color) . " f\n",
|
||||
75,
|
||||
"\n "
|
||||
);
|
||||
}
|
||||
|
||||
public function drawPathWithGradient(
|
||||
Path $path,
|
||||
Gradient $gradient,
|
||||
float $x,
|
||||
float $y,
|
||||
float $width,
|
||||
float $height
|
||||
) : void {
|
||||
if (null === $this->eps) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$fromX = 0;
|
||||
$fromY = 0;
|
||||
$this->eps .= wordwrap(
|
||||
'q n ' . $this->drawPathOperations($path, $fromX, $fromY) . "\n",
|
||||
75,
|
||||
"\n "
|
||||
);
|
||||
|
||||
$this->createGradientFill($gradient, $x, $y, $width, $height);
|
||||
}
|
||||
|
||||
public function done() : string
|
||||
{
|
||||
if (null === $this->eps) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$this->eps .= "%%TRAILER\nend restore\n%%EOF";
|
||||
$blob = $this->eps;
|
||||
$this->eps = null;
|
||||
|
||||
return $blob;
|
||||
}
|
||||
|
||||
private function drawPathOperations(Iterable $ops, &$fromX, &$fromY) : string
|
||||
{
|
||||
$pathData = [];
|
||||
|
||||
foreach ($ops as $op) {
|
||||
switch (true) {
|
||||
case $op instanceof Move:
|
||||
$fromX = $toX = round($op->getX(), self::PRECISION);
|
||||
$fromY = $toY = round($op->getY(), self::PRECISION);
|
||||
$pathData[] = sprintf('%s %s m', $toX, $toY);
|
||||
break;
|
||||
|
||||
case $op instanceof Line:
|
||||
$fromX = $toX = round($op->getX(), self::PRECISION);
|
||||
$fromY = $toY = round($op->getY(), self::PRECISION);
|
||||
$pathData[] = sprintf('%s %s l', $toX, $toY);
|
||||
break;
|
||||
|
||||
case $op instanceof EllipticArc:
|
||||
$pathData[] = $this->drawPathOperations($op->toCurves($fromX, $fromY), $fromX, $fromY);
|
||||
break;
|
||||
|
||||
case $op instanceof Curve:
|
||||
$x1 = round($op->getX1(), self::PRECISION);
|
||||
$y1 = round($op->getY1(), self::PRECISION);
|
||||
$x2 = round($op->getX2(), self::PRECISION);
|
||||
$y2 = round($op->getY2(), self::PRECISION);
|
||||
$fromX = $x3 = round($op->getX3(), self::PRECISION);
|
||||
$fromY = $y3 = round($op->getY3(), self::PRECISION);
|
||||
$pathData[] = sprintf('%s %s %s %s %s %s c', $x1, $y1, $x2, $y2, $x3, $y3);
|
||||
break;
|
||||
|
||||
case $op instanceof Close:
|
||||
$pathData[] = 'z';
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new RuntimeException('Unexpected draw operation: ' . get_class($op));
|
||||
}
|
||||
}
|
||||
|
||||
return implode(' ', $pathData);
|
||||
}
|
||||
|
||||
private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : void
|
||||
{
|
||||
$startColor = $gradient->getStartColor();
|
||||
$endColor = $gradient->getEndColor();
|
||||
|
||||
if ($startColor instanceof Alpha) {
|
||||
$startColor = $startColor->getBaseColor();
|
||||
}
|
||||
|
||||
$startColorType = get_class($startColor);
|
||||
|
||||
if (! in_array($startColorType, [Rgb::class, Cmyk::class, Gray::class])) {
|
||||
$startColorType = Cmyk::class;
|
||||
$startColor = $startColor->toCmyk();
|
||||
}
|
||||
|
||||
if (get_class($endColor) !== $startColorType) {
|
||||
switch ($startColorType) {
|
||||
case Cmyk::class:
|
||||
$endColor = $endColor->toCmyk();
|
||||
break;
|
||||
|
||||
case Rgb::class:
|
||||
$endColor = $endColor->toRgb();
|
||||
break;
|
||||
|
||||
case Gray::class:
|
||||
$endColor = $endColor->toGray();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->eps .= "eoclip\n<<\n";
|
||||
|
||||
if ($gradient->getType() === GradientType::RADIAL()) {
|
||||
$this->eps .= " /ShadingType 3\n";
|
||||
} else {
|
||||
$this->eps .= " /ShadingType 2\n";
|
||||
}
|
||||
|
||||
$this->eps .= " /Extend [ true true ]\n"
|
||||
. " /AntiAlias true\n";
|
||||
|
||||
switch ($startColorType) {
|
||||
case Cmyk::class:
|
||||
$this->eps .= " /ColorSpace /DeviceCMYK\n";
|
||||
break;
|
||||
|
||||
case Rgb::class:
|
||||
$this->eps .= " /ColorSpace /DeviceRGB\n";
|
||||
break;
|
||||
|
||||
case Gray::class:
|
||||
$this->eps .= " /ColorSpace /DeviceGray\n";
|
||||
break;
|
||||
}
|
||||
|
||||
switch ($gradient->getType()) {
|
||||
case GradientType::HORIZONTAL():
|
||||
$this->eps .= sprintf(
|
||||
" /Coords [ %s %s %s %s ]\n",
|
||||
round($x, self::PRECISION),
|
||||
round($y, self::PRECISION),
|
||||
round($x + $width, self::PRECISION),
|
||||
round($y, self::PRECISION)
|
||||
);
|
||||
break;
|
||||
|
||||
case GradientType::VERTICAL():
|
||||
$this->eps .= sprintf(
|
||||
" /Coords [ %s %s %s %s ]\n",
|
||||
round($x, self::PRECISION),
|
||||
round($y, self::PRECISION),
|
||||
round($x, self::PRECISION),
|
||||
round($y + $height, self::PRECISION)
|
||||
);
|
||||
break;
|
||||
|
||||
case GradientType::DIAGONAL():
|
||||
$this->eps .= sprintf(
|
||||
" /Coords [ %s %s %s %s ]\n",
|
||||
round($x, self::PRECISION),
|
||||
round($y, self::PRECISION),
|
||||
round($x + $width, self::PRECISION),
|
||||
round($y + $height, self::PRECISION)
|
||||
);
|
||||
break;
|
||||
|
||||
case GradientType::INVERSE_DIAGONAL():
|
||||
$this->eps .= sprintf(
|
||||
" /Coords [ %s %s %s %s ]\n",
|
||||
round($x, self::PRECISION),
|
||||
round($y + $height, self::PRECISION),
|
||||
round($x + $width, self::PRECISION),
|
||||
round($y, self::PRECISION)
|
||||
);
|
||||
break;
|
||||
|
||||
case GradientType::RADIAL():
|
||||
$centerX = ($x + $width) / 2;
|
||||
$centerY = ($y + $height) / 2;
|
||||
|
||||
$this->eps .= sprintf(
|
||||
" /Coords [ %s %s 0 %s %s %s ]\n",
|
||||
round($centerX, self::PRECISION),
|
||||
round($centerY, self::PRECISION),
|
||||
round($centerX, self::PRECISION),
|
||||
round($centerY, self::PRECISION),
|
||||
round(max($width, $height) / 2, self::PRECISION)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
$this->eps .= " /Function\n"
|
||||
. " <<\n"
|
||||
. " /FunctionType 2\n"
|
||||
. " /Domain [ 0 1 ]\n"
|
||||
. sprintf(" /C0 [ %s ]\n", $this->getColorString($startColor))
|
||||
. sprintf(" /C1 [ %s ]\n", $this->getColorString($endColor))
|
||||
. " /N 1\n"
|
||||
. " >>\n>>\nshfill\nQ\n";
|
||||
}
|
||||
|
||||
private function getColorSetString(ColorInterface $color) : string
|
||||
{
|
||||
if ($color instanceof Rgb) {
|
||||
return $this->getColorString($color) . ' rgb';
|
||||
}
|
||||
|
||||
if ($color instanceof Cmyk) {
|
||||
return $this->getColorString($color) . ' cmyk';
|
||||
}
|
||||
|
||||
if ($color instanceof Gray) {
|
||||
return $this->getColorString($color) . ' gray';
|
||||
}
|
||||
|
||||
return $this->getColorSetString($color->toCmyk());
|
||||
}
|
||||
|
||||
private function getColorString(ColorInterface $color) : string
|
||||
{
|
||||
if ($color instanceof Rgb) {
|
||||
return sprintf('%s %s %s', $color->getRed() / 255, $color->getGreen() / 255, $color->getBlue() / 255);
|
||||
}
|
||||
|
||||
if ($color instanceof Cmyk) {
|
||||
return sprintf(
|
||||
'%s %s %s %s',
|
||||
$color->getCyan() / 100,
|
||||
$color->getMagenta() / 100,
|
||||
$color->getYellow() / 100,
|
||||
$color->getBlack() / 100
|
||||
);
|
||||
}
|
||||
|
||||
if ($color instanceof Gray) {
|
||||
return sprintf('%s', $color->getGray() / 100);
|
||||
}
|
||||
|
||||
return $this->getColorString($color->toCmyk());
|
||||
}
|
||||
}
|
87
vendor/bacon/bacon-qr-code/src/Renderer/Image/ImageBackEndInterface.php
vendored
Normal file
87
vendor/bacon/bacon-qr-code/src/Renderer/Image/ImageBackEndInterface.php
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Image;
|
||||
|
||||
use BaconQrCode\Exception\RuntimeException;
|
||||
use BaconQrCode\Renderer\Color\ColorInterface;
|
||||
use BaconQrCode\Renderer\Path\Path;
|
||||
use BaconQrCode\Renderer\RendererStyle\Gradient;
|
||||
|
||||
/**
|
||||
* Interface for back ends able to to produce path based images.
|
||||
*/
|
||||
interface ImageBackEndInterface
|
||||
{
|
||||
/**
|
||||
* Starts a new image.
|
||||
*
|
||||
* If a previous image was already started, previous data get erased.
|
||||
*/
|
||||
public function new(int $size, ColorInterface $backgroundColor) : void;
|
||||
|
||||
/**
|
||||
* Transforms all following drawing operation coordinates by scaling them by a given factor.
|
||||
*
|
||||
* @throws RuntimeException if no image was started yet.
|
||||
*/
|
||||
public function scale(float $size) : void;
|
||||
|
||||
/**
|
||||
* Transforms all following drawing operation coordinates by translating them by a given amount.
|
||||
*
|
||||
* @throws RuntimeException if no image was started yet.
|
||||
*/
|
||||
public function translate(float $x, float $y) : void;
|
||||
|
||||
/**
|
||||
* Transforms all following drawing operation coordinates by rotating them by a given amount.
|
||||
*
|
||||
* @throws RuntimeException if no image was started yet.
|
||||
*/
|
||||
public function rotate(int $degrees) : void;
|
||||
|
||||
/**
|
||||
* Pushes the current coordinate transformation onto a stack.
|
||||
*
|
||||
* @throws RuntimeException if no image was started yet.
|
||||
*/
|
||||
public function push() : void;
|
||||
|
||||
/**
|
||||
* Pops the last coordinate transformation from a stack.
|
||||
*
|
||||
* @throws RuntimeException if no image was started yet.
|
||||
*/
|
||||
public function pop() : void;
|
||||
|
||||
/**
|
||||
* Draws a path with a given color.
|
||||
*
|
||||
* @throws RuntimeException if no image was started yet.
|
||||
*/
|
||||
public function drawPathWithColor(Path $path, ColorInterface $color) : void;
|
||||
|
||||
/**
|
||||
* Draws a path with a given gradient which spans the box described by the position and size.
|
||||
*
|
||||
* @throws RuntimeException if no image was started yet.
|
||||
*/
|
||||
public function drawPathWithGradient(
|
||||
Path $path,
|
||||
Gradient $gradient,
|
||||
float $x,
|
||||
float $y,
|
||||
float $width,
|
||||
float $height
|
||||
) : void;
|
||||
|
||||
/**
|
||||
* Ends the image drawing operation and returns the resulting blob.
|
||||
*
|
||||
* This should reset the state of the back end and thus this method should only be callable once per image.
|
||||
*
|
||||
* @throws RuntimeException if no image was started yet.
|
||||
*/
|
||||
public function done() : string;
|
||||
}
|
318
vendor/bacon/bacon-qr-code/src/Renderer/Image/ImagickImageBackEnd.php
vendored
Normal file
318
vendor/bacon/bacon-qr-code/src/Renderer/Image/ImagickImageBackEnd.php
vendored
Normal file
|
@ -0,0 +1,318 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Image;
|
||||
|
||||
use BaconQrCode\Exception\RuntimeException;
|
||||
use BaconQrCode\Renderer\Color\Alpha;
|
||||
use BaconQrCode\Renderer\Color\Cmyk;
|
||||
use BaconQrCode\Renderer\Color\ColorInterface;
|
||||
use BaconQrCode\Renderer\Color\Gray;
|
||||
use BaconQrCode\Renderer\Color\Rgb;
|
||||
use BaconQrCode\Renderer\Path\Close;
|
||||
use BaconQrCode\Renderer\Path\Curve;
|
||||
use BaconQrCode\Renderer\Path\EllipticArc;
|
||||
use BaconQrCode\Renderer\Path\Line;
|
||||
use BaconQrCode\Renderer\Path\Move;
|
||||
use BaconQrCode\Renderer\Path\Path;
|
||||
use BaconQrCode\Renderer\RendererStyle\Gradient;
|
||||
use BaconQrCode\Renderer\RendererStyle\GradientType;
|
||||
use Imagick;
|
||||
use ImagickDraw;
|
||||
use ImagickPixel;
|
||||
|
||||
final class ImagickImageBackEnd implements ImageBackEndInterface
|
||||
{
|
||||
private string $imageFormat;
|
||||
|
||||
private int $compressionQuality;
|
||||
|
||||
private ?Imagick $image;
|
||||
|
||||
private ?ImagickDraw $draw;
|
||||
|
||||
private ?int $gradientCount;
|
||||
|
||||
/**
|
||||
* @var TransformationMatrix[]|null
|
||||
*/
|
||||
private ?array $matrices;
|
||||
|
||||
private ?int $matrixIndex;
|
||||
|
||||
public function __construct(string $imageFormat = 'png', int $compressionQuality = 100)
|
||||
{
|
||||
if (! class_exists(Imagick::class)) {
|
||||
throw new RuntimeException('You need to install the imagick extension to use this back end');
|
||||
}
|
||||
|
||||
$this->imageFormat = $imageFormat;
|
||||
$this->compressionQuality = $compressionQuality;
|
||||
}
|
||||
|
||||
public function new(int $size, ColorInterface $backgroundColor) : void
|
||||
{
|
||||
$this->image = new Imagick();
|
||||
$this->image->newImage($size, $size, $this->getColorPixel($backgroundColor));
|
||||
$this->image->setImageFormat($this->imageFormat);
|
||||
$this->image->setCompressionQuality($this->compressionQuality);
|
||||
$this->draw = new ImagickDraw();
|
||||
$this->gradientCount = 0;
|
||||
$this->matrices = [new TransformationMatrix()];
|
||||
$this->matrixIndex = 0;
|
||||
}
|
||||
|
||||
public function scale(float $size) : void
|
||||
{
|
||||
if (null === $this->draw) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$this->draw->scale($size, $size);
|
||||
$this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex]
|
||||
->multiply(TransformationMatrix::scale($size));
|
||||
}
|
||||
|
||||
public function translate(float $x, float $y) : void
|
||||
{
|
||||
if (null === $this->draw) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$this->draw->translate($x, $y);
|
||||
$this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex]
|
||||
->multiply(TransformationMatrix::translate($x, $y));
|
||||
}
|
||||
|
||||
public function rotate(int $degrees) : void
|
||||
{
|
||||
if (null === $this->draw) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$this->draw->rotate($degrees);
|
||||
$this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex]
|
||||
->multiply(TransformationMatrix::rotate($degrees));
|
||||
}
|
||||
|
||||
public function push() : void
|
||||
{
|
||||
if (null === $this->draw) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$this->draw->push();
|
||||
$this->matrices[++$this->matrixIndex] = $this->matrices[$this->matrixIndex - 1];
|
||||
}
|
||||
|
||||
public function pop() : void
|
||||
{
|
||||
if (null === $this->draw) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$this->draw->pop();
|
||||
unset($this->matrices[$this->matrixIndex--]);
|
||||
}
|
||||
|
||||
public function drawPathWithColor(Path $path, ColorInterface $color) : void
|
||||
{
|
||||
if (null === $this->draw) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$this->draw->setFillColor($this->getColorPixel($color));
|
||||
$this->drawPath($path);
|
||||
}
|
||||
|
||||
public function drawPathWithGradient(
|
||||
Path $path,
|
||||
Gradient $gradient,
|
||||
float $x,
|
||||
float $y,
|
||||
float $width,
|
||||
float $height
|
||||
) : void {
|
||||
if (null === $this->draw) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$this->draw->setFillPatternURL('#' . $this->createGradientFill($gradient, $x, $y, $width, $height));
|
||||
$this->drawPath($path);
|
||||
}
|
||||
|
||||
public function done() : string
|
||||
{
|
||||
if (null === $this->draw) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$this->image->drawImage($this->draw);
|
||||
$blob = $this->image->getImageBlob();
|
||||
$this->draw->clear();
|
||||
$this->image->clear();
|
||||
$this->draw = null;
|
||||
$this->image = null;
|
||||
$this->gradientCount = null;
|
||||
|
||||
return $blob;
|
||||
}
|
||||
|
||||
private function drawPath(Path $path) : void
|
||||
{
|
||||
$this->draw->pathStart();
|
||||
|
||||
foreach ($path as $op) {
|
||||
switch (true) {
|
||||
case $op instanceof Move:
|
||||
$this->draw->pathMoveToAbsolute($op->getX(), $op->getY());
|
||||
break;
|
||||
|
||||
case $op instanceof Line:
|
||||
$this->draw->pathLineToAbsolute($op->getX(), $op->getY());
|
||||
break;
|
||||
|
||||
case $op instanceof EllipticArc:
|
||||
$this->draw->pathEllipticArcAbsolute(
|
||||
$op->getXRadius(),
|
||||
$op->getYRadius(),
|
||||
$op->getXAxisAngle(),
|
||||
$op->isLargeArc(),
|
||||
$op->isSweep(),
|
||||
$op->getX(),
|
||||
$op->getY()
|
||||
);
|
||||
break;
|
||||
|
||||
case $op instanceof Curve:
|
||||
$this->draw->pathCurveToAbsolute(
|
||||
$op->getX1(),
|
||||
$op->getY1(),
|
||||
$op->getX2(),
|
||||
$op->getY2(),
|
||||
$op->getX3(),
|
||||
$op->getY3()
|
||||
);
|
||||
break;
|
||||
|
||||
case $op instanceof Close:
|
||||
$this->draw->pathClose();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new RuntimeException('Unexpected draw operation: ' . get_class($op));
|
||||
}
|
||||
}
|
||||
|
||||
$this->draw->pathFinish();
|
||||
}
|
||||
|
||||
private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : string
|
||||
{
|
||||
list($width, $height) = $this->matrices[$this->matrixIndex]->apply($width, $height);
|
||||
|
||||
$startColor = $this->getColorPixel($gradient->getStartColor())->getColorAsString();
|
||||
$endColor = $this->getColorPixel($gradient->getEndColor())->getColorAsString();
|
||||
$gradientImage = new Imagick();
|
||||
|
||||
switch ($gradient->getType()) {
|
||||
case GradientType::HORIZONTAL():
|
||||
$gradientImage->newPseudoImage((int) $height, (int) $width, sprintf(
|
||||
'gradient:%s-%s',
|
||||
$startColor,
|
||||
$endColor
|
||||
));
|
||||
$gradientImage->rotateImage('transparent', -90);
|
||||
break;
|
||||
|
||||
case GradientType::VERTICAL():
|
||||
$gradientImage->newPseudoImage((int) $width, (int) $height, sprintf(
|
||||
'gradient:%s-%s',
|
||||
$startColor,
|
||||
$endColor
|
||||
));
|
||||
break;
|
||||
|
||||
case GradientType::DIAGONAL():
|
||||
case GradientType::INVERSE_DIAGONAL():
|
||||
$gradientImage->newPseudoImage((int) ($width * sqrt(2)), (int) ($height * sqrt(2)), sprintf(
|
||||
'gradient:%s-%s',
|
||||
$startColor,
|
||||
$endColor
|
||||
));
|
||||
|
||||
if (GradientType::DIAGONAL() === $gradient->getType()) {
|
||||
$gradientImage->rotateImage('transparent', -45);
|
||||
} else {
|
||||
$gradientImage->rotateImage('transparent', -135);
|
||||
}
|
||||
|
||||
$rotatedWidth = $gradientImage->getImageWidth();
|
||||
$rotatedHeight = $gradientImage->getImageHeight();
|
||||
|
||||
$gradientImage->setImagePage($rotatedWidth, $rotatedHeight, 0, 0);
|
||||
$gradientImage->cropImage(
|
||||
intdiv($rotatedWidth, 2) - 2,
|
||||
intdiv($rotatedHeight, 2) - 2,
|
||||
intdiv($rotatedWidth, 4) + 1,
|
||||
intdiv($rotatedWidth, 4) + 1
|
||||
);
|
||||
break;
|
||||
|
||||
case GradientType::RADIAL():
|
||||
$gradientImage->newPseudoImage((int) $width, (int) $height, sprintf(
|
||||
'radial-gradient:%s-%s',
|
||||
$startColor,
|
||||
$endColor
|
||||
));
|
||||
break;
|
||||
}
|
||||
|
||||
$id = sprintf('g%d', ++$this->gradientCount);
|
||||
$this->draw->pushPattern($id, 0, 0, $width, $height);
|
||||
$this->draw->composite(Imagick::COMPOSITE_COPY, 0, 0, $width, $height, $gradientImage);
|
||||
$this->draw->popPattern();
|
||||
return $id;
|
||||
}
|
||||
|
||||
private function getColorPixel(ColorInterface $color) : ImagickPixel
|
||||
{
|
||||
$alpha = 100;
|
||||
|
||||
if ($color instanceof Alpha) {
|
||||
$alpha = $color->getAlpha();
|
||||
$color = $color->getBaseColor();
|
||||
}
|
||||
|
||||
if ($color instanceof Rgb) {
|
||||
return new ImagickPixel(sprintf(
|
||||
'rgba(%d, %d, %d, %F)',
|
||||
$color->getRed(),
|
||||
$color->getGreen(),
|
||||
$color->getBlue(),
|
||||
$alpha / 100
|
||||
));
|
||||
}
|
||||
|
||||
if ($color instanceof Cmyk) {
|
||||
return new ImagickPixel(sprintf(
|
||||
'cmyka(%d, %d, %d, %d, %F)',
|
||||
$color->getCyan(),
|
||||
$color->getMagenta(),
|
||||
$color->getYellow(),
|
||||
$color->getBlack(),
|
||||
$alpha / 100
|
||||
));
|
||||
}
|
||||
|
||||
if ($color instanceof Gray) {
|
||||
return new ImagickPixel(sprintf(
|
||||
'graya(%d%%, %F)',
|
||||
$color->getGray(),
|
||||
$alpha / 100
|
||||
));
|
||||
}
|
||||
|
||||
return $this->getColorPixel(new Alpha($alpha, $color->toRgb()));
|
||||
}
|
||||
}
|
363
vendor/bacon/bacon-qr-code/src/Renderer/Image/SvgImageBackEnd.php
vendored
Normal file
363
vendor/bacon/bacon-qr-code/src/Renderer/Image/SvgImageBackEnd.php
vendored
Normal file
|
@ -0,0 +1,363 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Image;
|
||||
|
||||
use BaconQrCode\Exception\RuntimeException;
|
||||
use BaconQrCode\Renderer\Color\Alpha;
|
||||
use BaconQrCode\Renderer\Color\ColorInterface;
|
||||
use BaconQrCode\Renderer\Path\Close;
|
||||
use BaconQrCode\Renderer\Path\Curve;
|
||||
use BaconQrCode\Renderer\Path\EllipticArc;
|
||||
use BaconQrCode\Renderer\Path\Line;
|
||||
use BaconQrCode\Renderer\Path\Move;
|
||||
use BaconQrCode\Renderer\Path\Path;
|
||||
use BaconQrCode\Renderer\RendererStyle\Gradient;
|
||||
use BaconQrCode\Renderer\RendererStyle\GradientType;
|
||||
use XMLWriter;
|
||||
|
||||
final class SvgImageBackEnd implements ImageBackEndInterface
|
||||
{
|
||||
private const PRECISION = 3;
|
||||
private const SCALE_FORMAT = 'scale(%.' . self::PRECISION . 'F)';
|
||||
private const TRANSLATE_FORMAT = 'translate(%.' . self::PRECISION . 'F,%.' . self::PRECISION . 'F)';
|
||||
|
||||
private ?XMLWriter $xmlWriter;
|
||||
|
||||
private ?array $stack;
|
||||
|
||||
private ?int $currentStack;
|
||||
|
||||
private ?int $gradientCount;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (! class_exists(XMLWriter::class)) {
|
||||
throw new RuntimeException('You need to install the libxml extension to use this back end');
|
||||
}
|
||||
}
|
||||
|
||||
public function new(int $size, ColorInterface $backgroundColor) : void
|
||||
{
|
||||
$this->xmlWriter = new XMLWriter();
|
||||
$this->xmlWriter->openMemory();
|
||||
|
||||
$this->xmlWriter->startDocument('1.0', 'UTF-8');
|
||||
$this->xmlWriter->startElement('svg');
|
||||
$this->xmlWriter->writeAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
||||
$this->xmlWriter->writeAttribute('version', '1.1');
|
||||
$this->xmlWriter->writeAttribute('width', (string) $size);
|
||||
$this->xmlWriter->writeAttribute('height', (string) $size);
|
||||
$this->xmlWriter->writeAttribute('viewBox', '0 0 '. $size . ' ' . $size);
|
||||
|
||||
$this->gradientCount = 0;
|
||||
$this->currentStack = 0;
|
||||
$this->stack[0] = 0;
|
||||
|
||||
$alpha = 1;
|
||||
|
||||
if ($backgroundColor instanceof Alpha) {
|
||||
$alpha = $backgroundColor->getAlpha() / 100;
|
||||
}
|
||||
|
||||
if (0 === $alpha) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->xmlWriter->startElement('rect');
|
||||
$this->xmlWriter->writeAttribute('x', '0');
|
||||
$this->xmlWriter->writeAttribute('y', '0');
|
||||
$this->xmlWriter->writeAttribute('width', (string) $size);
|
||||
$this->xmlWriter->writeAttribute('height', (string) $size);
|
||||
$this->xmlWriter->writeAttribute('fill', $this->getColorString($backgroundColor));
|
||||
|
||||
if ($alpha < 1) {
|
||||
$this->xmlWriter->writeAttribute('fill-opacity', (string) $alpha);
|
||||
}
|
||||
|
||||
$this->xmlWriter->endElement();
|
||||
}
|
||||
|
||||
public function scale(float $size) : void
|
||||
{
|
||||
if (null === $this->xmlWriter) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$this->xmlWriter->startElement('g');
|
||||
$this->xmlWriter->writeAttribute(
|
||||
'transform',
|
||||
sprintf(self::SCALE_FORMAT, round($size, self::PRECISION))
|
||||
);
|
||||
++$this->stack[$this->currentStack];
|
||||
}
|
||||
|
||||
public function translate(float $x, float $y) : void
|
||||
{
|
||||
if (null === $this->xmlWriter) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$this->xmlWriter->startElement('g');
|
||||
$this->xmlWriter->writeAttribute(
|
||||
'transform',
|
||||
sprintf(self::TRANSLATE_FORMAT, round($x, self::PRECISION), round($y, self::PRECISION))
|
||||
);
|
||||
++$this->stack[$this->currentStack];
|
||||
}
|
||||
|
||||
public function rotate(int $degrees) : void
|
||||
{
|
||||
if (null === $this->xmlWriter) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$this->xmlWriter->startElement('g');
|
||||
$this->xmlWriter->writeAttribute('transform', sprintf('rotate(%d)', $degrees));
|
||||
++$this->stack[$this->currentStack];
|
||||
}
|
||||
|
||||
public function push() : void
|
||||
{
|
||||
if (null === $this->xmlWriter) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$this->xmlWriter->startElement('g');
|
||||
$this->stack[] = 1;
|
||||
++$this->currentStack;
|
||||
}
|
||||
|
||||
public function pop() : void
|
||||
{
|
||||
if (null === $this->xmlWriter) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $this->stack[$this->currentStack]; ++$i) {
|
||||
$this->xmlWriter->endElement();
|
||||
}
|
||||
|
||||
array_pop($this->stack);
|
||||
--$this->currentStack;
|
||||
}
|
||||
|
||||
public function drawPathWithColor(Path $path, ColorInterface $color) : void
|
||||
{
|
||||
if (null === $this->xmlWriter) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$alpha = 1;
|
||||
|
||||
if ($color instanceof Alpha) {
|
||||
$alpha = $color->getAlpha() / 100;
|
||||
}
|
||||
|
||||
$this->startPathElement($path);
|
||||
$this->xmlWriter->writeAttribute('fill', $this->getColorString($color));
|
||||
|
||||
if ($alpha < 1) {
|
||||
$this->xmlWriter->writeAttribute('fill-opacity', (string) $alpha);
|
||||
}
|
||||
|
||||
$this->xmlWriter->endElement();
|
||||
}
|
||||
|
||||
public function drawPathWithGradient(
|
||||
Path $path,
|
||||
Gradient $gradient,
|
||||
float $x,
|
||||
float $y,
|
||||
float $width,
|
||||
float $height
|
||||
) : void {
|
||||
if (null === $this->xmlWriter) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
$gradientId = $this->createGradientFill($gradient, $x, $y, $width, $height);
|
||||
$this->startPathElement($path);
|
||||
$this->xmlWriter->writeAttribute('fill', 'url(#' . $gradientId . ')');
|
||||
$this->xmlWriter->endElement();
|
||||
}
|
||||
|
||||
public function done() : string
|
||||
{
|
||||
if (null === $this->xmlWriter) {
|
||||
throw new RuntimeException('No image has been started');
|
||||
}
|
||||
|
||||
foreach ($this->stack as $openElements) {
|
||||
for ($i = $openElements; $i > 0; --$i) {
|
||||
$this->xmlWriter->endElement();
|
||||
}
|
||||
}
|
||||
|
||||
$this->xmlWriter->endDocument();
|
||||
$blob = $this->xmlWriter->outputMemory(true);
|
||||
$this->xmlWriter = null;
|
||||
$this->stack = null;
|
||||
$this->currentStack = null;
|
||||
$this->gradientCount = null;
|
||||
|
||||
return $blob;
|
||||
}
|
||||
|
||||
private function startPathElement(Path $path) : void
|
||||
{
|
||||
$pathData = [];
|
||||
|
||||
foreach ($path as $op) {
|
||||
switch (true) {
|
||||
case $op instanceof Move:
|
||||
$pathData[] = sprintf(
|
||||
'M%s %s',
|
||||
round($op->getX(), self::PRECISION),
|
||||
round($op->getY(), self::PRECISION)
|
||||
);
|
||||
break;
|
||||
|
||||
case $op instanceof Line:
|
||||
$pathData[] = sprintf(
|
||||
'L%s %s',
|
||||
round($op->getX(), self::PRECISION),
|
||||
round($op->getY(), self::PRECISION)
|
||||
);
|
||||
break;
|
||||
|
||||
case $op instanceof EllipticArc:
|
||||
$pathData[] = sprintf(
|
||||
'A%s %s %s %u %u %s %s',
|
||||
round($op->getXRadius(), self::PRECISION),
|
||||
round($op->getYRadius(), self::PRECISION),
|
||||
round($op->getXAxisAngle(), self::PRECISION),
|
||||
$op->isLargeArc(),
|
||||
$op->isSweep(),
|
||||
round($op->getX(), self::PRECISION),
|
||||
round($op->getY(), self::PRECISION)
|
||||
);
|
||||
break;
|
||||
|
||||
case $op instanceof Curve:
|
||||
$pathData[] = sprintf(
|
||||
'C%s %s %s %s %s %s',
|
||||
round($op->getX1(), self::PRECISION),
|
||||
round($op->getY1(), self::PRECISION),
|
||||
round($op->getX2(), self::PRECISION),
|
||||
round($op->getY2(), self::PRECISION),
|
||||
round($op->getX3(), self::PRECISION),
|
||||
round($op->getY3(), self::PRECISION)
|
||||
);
|
||||
break;
|
||||
|
||||
case $op instanceof Close:
|
||||
$pathData[] = 'Z';
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new RuntimeException('Unexpected draw operation: ' . get_class($op));
|
||||
}
|
||||
}
|
||||
|
||||
$this->xmlWriter->startElement('path');
|
||||
$this->xmlWriter->writeAttribute('fill-rule', 'evenodd');
|
||||
$this->xmlWriter->writeAttribute('d', implode('', $pathData));
|
||||
}
|
||||
|
||||
private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : string
|
||||
{
|
||||
$this->xmlWriter->startElement('defs');
|
||||
|
||||
$startColor = $gradient->getStartColor();
|
||||
$endColor = $gradient->getEndColor();
|
||||
|
||||
if ($gradient->getType() === GradientType::RADIAL()) {
|
||||
$this->xmlWriter->startElement('radialGradient');
|
||||
} else {
|
||||
$this->xmlWriter->startElement('linearGradient');
|
||||
}
|
||||
|
||||
$this->xmlWriter->writeAttribute('gradientUnits', 'userSpaceOnUse');
|
||||
|
||||
switch ($gradient->getType()) {
|
||||
case GradientType::HORIZONTAL():
|
||||
$this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
|
||||
$this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION));
|
||||
$this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION));
|
||||
$this->xmlWriter->writeAttribute('y2', (string) round($y, self::PRECISION));
|
||||
break;
|
||||
|
||||
case GradientType::VERTICAL():
|
||||
$this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
|
||||
$this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION));
|
||||
$this->xmlWriter->writeAttribute('x2', (string) round($x, self::PRECISION));
|
||||
$this->xmlWriter->writeAttribute('y2', (string) round($y + $height, self::PRECISION));
|
||||
break;
|
||||
|
||||
case GradientType::DIAGONAL():
|
||||
$this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
|
||||
$this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION));
|
||||
$this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION));
|
||||
$this->xmlWriter->writeAttribute('y2', (string) round($y + $height, self::PRECISION));
|
||||
break;
|
||||
|
||||
case GradientType::INVERSE_DIAGONAL():
|
||||
$this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
|
||||
$this->xmlWriter->writeAttribute('y1', (string) round($y + $height, self::PRECISION));
|
||||
$this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION));
|
||||
$this->xmlWriter->writeAttribute('y2', (string) round($y, self::PRECISION));
|
||||
break;
|
||||
|
||||
case GradientType::RADIAL():
|
||||
$this->xmlWriter->writeAttribute('cx', (string) round(($x + $width) / 2, self::PRECISION));
|
||||
$this->xmlWriter->writeAttribute('cy', (string) round(($y + $height) / 2, self::PRECISION));
|
||||
$this->xmlWriter->writeAttribute('r', (string) round(max($width, $height) / 2, self::PRECISION));
|
||||
break;
|
||||
}
|
||||
|
||||
$toBeHashed = $this->getColorString($startColor) . $this->getColorString($endColor) . $gradient->getType();
|
||||
if ($startColor instanceof Alpha) {
|
||||
$toBeHashed .= (string) $startColor->getAlpha();
|
||||
}
|
||||
$id = sprintf('g%d-%s', ++$this->gradientCount, hash('xxh64', $toBeHashed));
|
||||
$this->xmlWriter->writeAttribute('id', $id);
|
||||
|
||||
$this->xmlWriter->startElement('stop');
|
||||
$this->xmlWriter->writeAttribute('offset', '0%');
|
||||
$this->xmlWriter->writeAttribute('stop-color', $this->getColorString($startColor));
|
||||
|
||||
if ($startColor instanceof Alpha) {
|
||||
$this->xmlWriter->writeAttribute('stop-opacity', (string) $startColor->getAlpha());
|
||||
}
|
||||
|
||||
$this->xmlWriter->endElement();
|
||||
|
||||
$this->xmlWriter->startElement('stop');
|
||||
$this->xmlWriter->writeAttribute('offset', '100%');
|
||||
$this->xmlWriter->writeAttribute('stop-color', $this->getColorString($endColor));
|
||||
|
||||
if ($endColor instanceof Alpha) {
|
||||
$this->xmlWriter->writeAttribute('stop-opacity', (string) $endColor->getAlpha());
|
||||
}
|
||||
|
||||
$this->xmlWriter->endElement();
|
||||
|
||||
$this->xmlWriter->endElement();
|
||||
$this->xmlWriter->endElement();
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
private function getColorString(ColorInterface $color) : string
|
||||
{
|
||||
$color = $color->toRgb();
|
||||
|
||||
return sprintf(
|
||||
'#%02x%02x%02x',
|
||||
$color->getRed(),
|
||||
$color->getGreen(),
|
||||
$color->getBlue()
|
||||
);
|
||||
}
|
||||
}
|
68
vendor/bacon/bacon-qr-code/src/Renderer/Image/TransformationMatrix.php
vendored
Normal file
68
vendor/bacon/bacon-qr-code/src/Renderer/Image/TransformationMatrix.php
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Image;
|
||||
|
||||
final class TransformationMatrix
|
||||
{
|
||||
/**
|
||||
* @var float[]
|
||||
*/
|
||||
private array $values;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->values = [1, 0, 0, 1, 0, 0];
|
||||
}
|
||||
|
||||
public function multiply(self $other) : self
|
||||
{
|
||||
$matrix = new self();
|
||||
$matrix->values[0] = $this->values[0] * $other->values[0] + $this->values[2] * $other->values[1];
|
||||
$matrix->values[1] = $this->values[1] * $other->values[0] + $this->values[3] * $other->values[1];
|
||||
$matrix->values[2] = $this->values[0] * $other->values[2] + $this->values[2] * $other->values[3];
|
||||
$matrix->values[3] = $this->values[1] * $other->values[2] + $this->values[3] * $other->values[3];
|
||||
$matrix->values[4] = $this->values[0] * $other->values[4] + $this->values[2] * $other->values[5]
|
||||
+ $this->values[4];
|
||||
$matrix->values[5] = $this->values[1] * $other->values[4] + $this->values[3] * $other->values[5]
|
||||
+ $this->values[5];
|
||||
|
||||
return $matrix;
|
||||
}
|
||||
|
||||
public static function scale(float $size) : self
|
||||
{
|
||||
$matrix = new self();
|
||||
$matrix->values = [$size, 0, 0, $size, 0, 0];
|
||||
return $matrix;
|
||||
}
|
||||
|
||||
public static function translate(float $x, float $y) : self
|
||||
{
|
||||
$matrix = new self();
|
||||
$matrix->values = [1, 0, 0, 1, $x, $y];
|
||||
return $matrix;
|
||||
}
|
||||
|
||||
public static function rotate(int $degrees) : self
|
||||
{
|
||||
$matrix = new self();
|
||||
$rad = deg2rad($degrees);
|
||||
$matrix->values = [cos($rad), sin($rad), -sin($rad), cos($rad), 0, 0];
|
||||
return $matrix;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Applies this matrix onto a point and returns the resulting viewport point.
|
||||
*
|
||||
* @return float[]
|
||||
*/
|
||||
public function apply(float $x, float $y) : array
|
||||
{
|
||||
return [
|
||||
$x * $this->values[0] + $y * $this->values[2] + $this->values[4],
|
||||
$x * $this->values[1] + $y * $this->values[3] + $this->values[5],
|
||||
];
|
||||
}
|
||||
}
|
150
vendor/bacon/bacon-qr-code/src/Renderer/ImageRenderer.php
vendored
Normal file
150
vendor/bacon/bacon-qr-code/src/Renderer/ImageRenderer.php
vendored
Normal file
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer;
|
||||
|
||||
use BaconQrCode\Encoder\MatrixUtil;
|
||||
use BaconQrCode\Encoder\QrCode;
|
||||
use BaconQrCode\Exception\InvalidArgumentException;
|
||||
use BaconQrCode\Renderer\Image\ImageBackEndInterface;
|
||||
use BaconQrCode\Renderer\Path\Path;
|
||||
use BaconQrCode\Renderer\RendererStyle\EyeFill;
|
||||
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
|
||||
|
||||
final class ImageRenderer implements RendererInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly RendererStyle $rendererStyle,
|
||||
private readonly ImageBackEndInterface $imageBackEnd
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if matrix width doesn't match height
|
||||
*/
|
||||
public function render(QrCode $qrCode) : string
|
||||
{
|
||||
$size = $this->rendererStyle->getSize();
|
||||
$margin = $this->rendererStyle->getMargin();
|
||||
$matrix = $qrCode->getMatrix();
|
||||
$matrixSize = $matrix->getWidth();
|
||||
|
||||
if ($matrixSize !== $matrix->getHeight()) {
|
||||
throw new InvalidArgumentException('Matrix must have the same width and height');
|
||||
}
|
||||
|
||||
$totalSize = $matrixSize + ($margin * 2);
|
||||
$moduleSize = $size / $totalSize;
|
||||
$fill = $this->rendererStyle->getFill();
|
||||
|
||||
$this->imageBackEnd->new($size, $fill->getBackgroundColor());
|
||||
$this->imageBackEnd->scale((float) $moduleSize);
|
||||
$this->imageBackEnd->translate((float) $margin, (float) $margin);
|
||||
|
||||
$module = $this->rendererStyle->getModule();
|
||||
$moduleMatrix = clone $matrix;
|
||||
MatrixUtil::removePositionDetectionPatterns($moduleMatrix);
|
||||
$modulePath = $this->drawEyes($matrixSize, $module->createPath($moduleMatrix));
|
||||
|
||||
if ($fill->hasGradientFill()) {
|
||||
$this->imageBackEnd->drawPathWithGradient(
|
||||
$modulePath,
|
||||
$fill->getForegroundGradient(),
|
||||
0,
|
||||
0,
|
||||
$matrixSize,
|
||||
$matrixSize
|
||||
);
|
||||
} else {
|
||||
$this->imageBackEnd->drawPathWithColor($modulePath, $fill->getForegroundColor());
|
||||
}
|
||||
|
||||
return $this->imageBackEnd->done();
|
||||
}
|
||||
|
||||
private function drawEyes(int $matrixSize, Path $modulePath) : Path
|
||||
{
|
||||
$fill = $this->rendererStyle->getFill();
|
||||
|
||||
$eye = $this->rendererStyle->getEye();
|
||||
$externalPath = $eye->getExternalPath();
|
||||
$internalPath = $eye->getInternalPath();
|
||||
|
||||
$modulePath = $this->drawEye(
|
||||
$externalPath,
|
||||
$internalPath,
|
||||
$fill->getTopLeftEyeFill(),
|
||||
3.5,
|
||||
3.5,
|
||||
0,
|
||||
$modulePath
|
||||
);
|
||||
$modulePath = $this->drawEye(
|
||||
$externalPath,
|
||||
$internalPath,
|
||||
$fill->getTopRightEyeFill(),
|
||||
$matrixSize - 3.5,
|
||||
3.5,
|
||||
90,
|
||||
$modulePath
|
||||
);
|
||||
$modulePath = $this->drawEye(
|
||||
$externalPath,
|
||||
$internalPath,
|
||||
$fill->getBottomLeftEyeFill(),
|
||||
3.5,
|
||||
$matrixSize - 3.5,
|
||||
-90,
|
||||
$modulePath
|
||||
);
|
||||
|
||||
return $modulePath;
|
||||
}
|
||||
|
||||
private function drawEye(
|
||||
Path $externalPath,
|
||||
Path $internalPath,
|
||||
EyeFill $fill,
|
||||
float $xTranslation,
|
||||
float $yTranslation,
|
||||
int $rotation,
|
||||
Path $modulePath
|
||||
) : Path {
|
||||
if ($fill->inheritsBothColors()) {
|
||||
return $modulePath
|
||||
->append(
|
||||
$externalPath->rotate($rotation)->translate($xTranslation, $yTranslation)
|
||||
)
|
||||
->append(
|
||||
$internalPath->rotate($rotation)->translate($xTranslation, $yTranslation)
|
||||
);
|
||||
}
|
||||
|
||||
$this->imageBackEnd->push();
|
||||
$this->imageBackEnd->translate($xTranslation, $yTranslation);
|
||||
|
||||
if (0 !== $rotation) {
|
||||
$this->imageBackEnd->rotate($rotation);
|
||||
}
|
||||
|
||||
if ($fill->inheritsExternalColor()) {
|
||||
$modulePath = $modulePath->append(
|
||||
$externalPath->rotate($rotation)->translate($xTranslation, $yTranslation)
|
||||
);
|
||||
} else {
|
||||
$this->imageBackEnd->drawPathWithColor($externalPath, $fill->getExternalColor());
|
||||
}
|
||||
|
||||
if ($fill->inheritsInternalColor()) {
|
||||
$modulePath = $modulePath->append(
|
||||
$internalPath->rotate($rotation)->translate($xTranslation, $yTranslation)
|
||||
);
|
||||
} else {
|
||||
$this->imageBackEnd->drawPathWithColor($internalPath, $fill->getInternalColor());
|
||||
}
|
||||
|
||||
$this->imageBackEnd->pop();
|
||||
|
||||
return $modulePath;
|
||||
}
|
||||
}
|
56
vendor/bacon/bacon-qr-code/src/Renderer/Module/DotsModule.php
vendored
Normal file
56
vendor/bacon/bacon-qr-code/src/Renderer/Module/DotsModule.php
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Module;
|
||||
|
||||
use BaconQrCode\Encoder\ByteMatrix;
|
||||
use BaconQrCode\Exception\InvalidArgumentException;
|
||||
use BaconQrCode\Renderer\Path\Path;
|
||||
|
||||
/**
|
||||
* Renders individual modules as dots.
|
||||
*/
|
||||
final class DotsModule implements ModuleInterface
|
||||
{
|
||||
public const LARGE = 1;
|
||||
public const MEDIUM = .8;
|
||||
public const SMALL = .6;
|
||||
|
||||
public function __construct(private readonly float $size)
|
||||
{
|
||||
if ($size <= 0 || $size > 1) {
|
||||
throw new InvalidArgumentException('Size must between 0 (exclusive) and 1 (inclusive)');
|
||||
}
|
||||
}
|
||||
|
||||
public function createPath(ByteMatrix $matrix) : Path
|
||||
{
|
||||
$width = $matrix->getWidth();
|
||||
$height = $matrix->getHeight();
|
||||
$path = new Path();
|
||||
$halfSize = $this->size / 2;
|
||||
$margin = (1 - $this->size) / 2;
|
||||
|
||||
for ($y = 0; $y < $height; ++$y) {
|
||||
for ($x = 0; $x < $width; ++$x) {
|
||||
if (! $matrix->get($x, $y)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$pathX = $x + $margin;
|
||||
$pathY = $y + $margin;
|
||||
|
||||
$path = $path
|
||||
->move($pathX + $this->size, $pathY + $halfSize)
|
||||
->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX + $halfSize, $pathY + $this->size)
|
||||
->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX, $pathY + $halfSize)
|
||||
->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX + $halfSize, $pathY)
|
||||
->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX + $this->size, $pathY + $halfSize)
|
||||
->close()
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
}
|
82
vendor/bacon/bacon-qr-code/src/Renderer/Module/EdgeIterator/Edge.php
vendored
Normal file
82
vendor/bacon/bacon-qr-code/src/Renderer/Module/EdgeIterator/Edge.php
vendored
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Module\EdgeIterator;
|
||||
|
||||
final class Edge
|
||||
{
|
||||
/**
|
||||
* @var array<int[]>
|
||||
*/
|
||||
private array $points = [];
|
||||
|
||||
/**
|
||||
* @var array<int[]>|null
|
||||
*/
|
||||
private ?array $simplifiedPoints = null;
|
||||
|
||||
private int $minX = PHP_INT_MAX;
|
||||
|
||||
private int $minY = PHP_INT_MAX;
|
||||
|
||||
private int $maxX = -1;
|
||||
|
||||
private int $maxY = -1;
|
||||
|
||||
public function __construct(private readonly bool $positive)
|
||||
{
|
||||
}
|
||||
|
||||
public function addPoint(int $x, int $y) : void
|
||||
{
|
||||
$this->points[] = [$x, $y];
|
||||
$this->minX = min($this->minX, $x);
|
||||
$this->minY = min($this->minY, $y);
|
||||
$this->maxX = max($this->maxX, $x);
|
||||
$this->maxY = max($this->maxY, $y);
|
||||
}
|
||||
|
||||
public function isPositive() : bool
|
||||
{
|
||||
return $this->positive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int[]>
|
||||
*/
|
||||
public function getPoints() : array
|
||||
{
|
||||
return $this->points;
|
||||
}
|
||||
|
||||
public function getMaxX() : int
|
||||
{
|
||||
return $this->maxX;
|
||||
}
|
||||
|
||||
public function getSimplifiedPoints() : array
|
||||
{
|
||||
if (null !== $this->simplifiedPoints) {
|
||||
return $this->simplifiedPoints;
|
||||
}
|
||||
|
||||
$points = [];
|
||||
$length = count($this->points);
|
||||
|
||||
for ($i = 0; $i < $length; ++$i) {
|
||||
$previousPoint = $this->points[(0 === $i ? $length : $i) - 1];
|
||||
$nextPoint = $this->points[($length - 1 === $i ? -1 : $i) + 1];
|
||||
$currentPoint = $this->points[$i];
|
||||
|
||||
if (($previousPoint[0] === $currentPoint[0] && $currentPoint[0] === $nextPoint[0])
|
||||
|| ($previousPoint[1] === $currentPoint[1] && $currentPoint[1] === $nextPoint[1])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$points[] = $currentPoint;
|
||||
}
|
||||
|
||||
return $this->simplifiedPoints = $points;
|
||||
}
|
||||
}
|
160
vendor/bacon/bacon-qr-code/src/Renderer/Module/EdgeIterator/EdgeIterator.php
vendored
Normal file
160
vendor/bacon/bacon-qr-code/src/Renderer/Module/EdgeIterator/EdgeIterator.php
vendored
Normal file
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Module\EdgeIterator;
|
||||
|
||||
use BaconQrCode\Encoder\ByteMatrix;
|
||||
use IteratorAggregate;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Edge iterator based on potrace.
|
||||
*/
|
||||
final class EdgeIterator implements IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
private array $bytes = [];
|
||||
|
||||
private ?int $size;
|
||||
|
||||
private int $width;
|
||||
|
||||
private int $height;
|
||||
|
||||
public function __construct(ByteMatrix $matrix)
|
||||
{
|
||||
$this->bytes = iterator_to_array($matrix->getBytes());
|
||||
$this->size = count($this->bytes);
|
||||
$this->width = $matrix->getWidth();
|
||||
$this->height = $matrix->getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Traversable<Edge>
|
||||
*/
|
||||
public function getIterator() : Traversable
|
||||
{
|
||||
$originalBytes = $this->bytes;
|
||||
$point = $this->findNext(0, 0);
|
||||
|
||||
while (null !== $point) {
|
||||
$edge = $this->findEdge($point[0], $point[1]);
|
||||
$this->xorEdge($edge);
|
||||
|
||||
yield $edge;
|
||||
|
||||
$point = $this->findNext($point[0], $point[1]);
|
||||
}
|
||||
|
||||
$this->bytes = $originalBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]|null
|
||||
*/
|
||||
private function findNext(int $x, int $y) : ?array
|
||||
{
|
||||
$i = $this->width * $y + $x;
|
||||
|
||||
while ($i < $this->size && 1 !== $this->bytes[$i]) {
|
||||
++$i;
|
||||
}
|
||||
|
||||
if ($i < $this->size) {
|
||||
return $this->pointOf($i);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function findEdge(int $x, int $y) : Edge
|
||||
{
|
||||
$edge = new Edge($this->isSet($x, $y));
|
||||
$startX = $x;
|
||||
$startY = $y;
|
||||
$dirX = 0;
|
||||
$dirY = 1;
|
||||
|
||||
while (true) {
|
||||
$edge->addPoint($x, $y);
|
||||
$x += $dirX;
|
||||
$y += $dirY;
|
||||
|
||||
if ($x === $startX && $y === $startY) {
|
||||
break;
|
||||
}
|
||||
|
||||
$left = $this->isSet($x + ($dirX + $dirY - 1 ) / 2, $y + ($dirY - $dirX - 1) / 2);
|
||||
$right = $this->isSet($x + ($dirX - $dirY - 1) / 2, $y + ($dirY + $dirX - 1) / 2);
|
||||
|
||||
if ($right && ! $left) {
|
||||
$tmp = $dirX;
|
||||
$dirX = -$dirY;
|
||||
$dirY = $tmp;
|
||||
} elseif ($right) {
|
||||
$tmp = $dirX;
|
||||
$dirX = -$dirY;
|
||||
$dirY = $tmp;
|
||||
} elseif (! $left) {
|
||||
$tmp = $dirX;
|
||||
$dirX = $dirY;
|
||||
$dirY = -$tmp;
|
||||
}
|
||||
}
|
||||
|
||||
return $edge;
|
||||
}
|
||||
|
||||
private function xorEdge(Edge $path) : void
|
||||
{
|
||||
$points = $path->getPoints();
|
||||
$y1 = $points[0][1];
|
||||
$length = count($points);
|
||||
$maxX = $path->getMaxX();
|
||||
|
||||
for ($i = 1; $i < $length; ++$i) {
|
||||
$y = $points[$i][1];
|
||||
|
||||
if ($y === $y1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$x = $points[$i][0];
|
||||
$minY = min($y1, $y);
|
||||
|
||||
for ($j = $x; $j < $maxX; ++$j) {
|
||||
$this->flip($j, $minY);
|
||||
}
|
||||
|
||||
$y1 = $y;
|
||||
}
|
||||
}
|
||||
|
||||
private function isSet(int $x, int $y) : bool
|
||||
{
|
||||
return (
|
||||
$x >= 0
|
||||
&& $x < $this->width
|
||||
&& $y >= 0
|
||||
&& $y < $this->height
|
||||
) && 1 === $this->bytes[$this->width * $y + $x];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
private function pointOf(int $i) : array
|
||||
{
|
||||
$y = intdiv($i, $this->width);
|
||||
return [$i - $y * $this->width, $y];
|
||||
}
|
||||
|
||||
private function flip(int $x, int $y) : void
|
||||
{
|
||||
$this->bytes[$this->width * $y + $x] = (
|
||||
$this->isSet($x, $y) ? 0 : 1
|
||||
);
|
||||
}
|
||||
}
|
18
vendor/bacon/bacon-qr-code/src/Renderer/Module/ModuleInterface.php
vendored
Normal file
18
vendor/bacon/bacon-qr-code/src/Renderer/Module/ModuleInterface.php
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Module;
|
||||
|
||||
use BaconQrCode\Encoder\ByteMatrix;
|
||||
use BaconQrCode\Renderer\Path\Path;
|
||||
|
||||
/**
|
||||
* Interface describing how modules should be rendered.
|
||||
*
|
||||
* A module always receives a byte matrix (with values either being 1 or 0). It returns a path, where the origin
|
||||
* coordinate (0, 0) equals the top left corner of the first matrix value.
|
||||
*/
|
||||
interface ModuleInterface
|
||||
{
|
||||
public function createPath(ByteMatrix $matrix) : Path;
|
||||
}
|
124
vendor/bacon/bacon-qr-code/src/Renderer/Module/RoundnessModule.php
vendored
Normal file
124
vendor/bacon/bacon-qr-code/src/Renderer/Module/RoundnessModule.php
vendored
Normal file
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Module;
|
||||
|
||||
use BaconQrCode\Encoder\ByteMatrix;
|
||||
use BaconQrCode\Exception\InvalidArgumentException;
|
||||
use BaconQrCode\Renderer\Module\EdgeIterator\EdgeIterator;
|
||||
use BaconQrCode\Renderer\Path\Path;
|
||||
|
||||
/**
|
||||
* Rounds the corners of module groups.
|
||||
*/
|
||||
final class RoundnessModule implements ModuleInterface
|
||||
{
|
||||
public const STRONG = 1;
|
||||
public const MEDIUM = .5;
|
||||
public const SOFT = .25;
|
||||
|
||||
public function __construct(private float $intensity)
|
||||
{
|
||||
if ($intensity <= 0 || $intensity > 1) {
|
||||
throw new InvalidArgumentException('Intensity must between 0 (exclusive) and 1 (inclusive)');
|
||||
}
|
||||
|
||||
$this->intensity = $intensity / 2;
|
||||
}
|
||||
|
||||
public function createPath(ByteMatrix $matrix) : Path
|
||||
{
|
||||
$path = new Path();
|
||||
|
||||
foreach (new EdgeIterator($matrix) as $edge) {
|
||||
$points = $edge->getSimplifiedPoints();
|
||||
$length = count($points);
|
||||
|
||||
$currentPoint = $points[0];
|
||||
$nextPoint = $points[1];
|
||||
$horizontal = ($currentPoint[1] === $nextPoint[1]);
|
||||
|
||||
if ($horizontal) {
|
||||
$right = $nextPoint[0] > $currentPoint[0];
|
||||
$path = $path->move(
|
||||
$currentPoint[0] + ($right ? $this->intensity : -$this->intensity),
|
||||
$currentPoint[1]
|
||||
);
|
||||
} else {
|
||||
$up = $nextPoint[0] < $currentPoint[0];
|
||||
$path = $path->move(
|
||||
$currentPoint[0],
|
||||
$currentPoint[1] + ($up ? -$this->intensity : $this->intensity)
|
||||
);
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= $length; ++$i) {
|
||||
if ($i === $length) {
|
||||
$previousPoint = $points[$length - 1];
|
||||
$currentPoint = $points[0];
|
||||
$nextPoint = $points[1];
|
||||
} else {
|
||||
$previousPoint = $points[(0 === $i ? $length : $i) - 1];
|
||||
$currentPoint = $points[$i];
|
||||
$nextPoint = $points[($length - 1 === $i ? -1 : $i) + 1];
|
||||
}
|
||||
|
||||
$horizontal = ($previousPoint[1] === $currentPoint[1]);
|
||||
|
||||
if ($horizontal) {
|
||||
$right = $previousPoint[0] < $currentPoint[0];
|
||||
$up = $nextPoint[1] < $currentPoint[1];
|
||||
$sweep = ($up xor $right);
|
||||
|
||||
if ($this->intensity < 0.5
|
||||
|| ($right && $previousPoint[0] !== $currentPoint[0] - 1)
|
||||
|| (! $right && $previousPoint[0] - 1 !== $currentPoint[0])
|
||||
) {
|
||||
$path = $path->line(
|
||||
$currentPoint[0] + ($right ? -$this->intensity : $this->intensity),
|
||||
$currentPoint[1]
|
||||
);
|
||||
}
|
||||
|
||||
$path = $path->ellipticArc(
|
||||
$this->intensity,
|
||||
$this->intensity,
|
||||
0,
|
||||
false,
|
||||
$sweep,
|
||||
$currentPoint[0],
|
||||
$currentPoint[1] + ($up ? -$this->intensity : $this->intensity)
|
||||
);
|
||||
} else {
|
||||
$up = $previousPoint[1] > $currentPoint[1];
|
||||
$right = $nextPoint[0] > $currentPoint[0];
|
||||
$sweep = ! ($up xor $right);
|
||||
|
||||
if ($this->intensity < 0.5
|
||||
|| ($up && $previousPoint[1] !== $currentPoint[1] + 1)
|
||||
|| (! $up && $previousPoint[0] + 1 !== $currentPoint[0])
|
||||
) {
|
||||
$path = $path->line(
|
||||
$currentPoint[0],
|
||||
$currentPoint[1] + ($up ? $this->intensity : -$this->intensity)
|
||||
);
|
||||
}
|
||||
|
||||
$path = $path->ellipticArc(
|
||||
$this->intensity,
|
||||
$this->intensity,
|
||||
0,
|
||||
false,
|
||||
$sweep,
|
||||
$currentPoint[0] + ($right ? $this->intensity : -$this->intensity),
|
||||
$currentPoint[1]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$path = $path->close();
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
}
|
44
vendor/bacon/bacon-qr-code/src/Renderer/Module/SquareModule.php
vendored
Normal file
44
vendor/bacon/bacon-qr-code/src/Renderer/Module/SquareModule.php
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Module;
|
||||
|
||||
use BaconQrCode\Encoder\ByteMatrix;
|
||||
use BaconQrCode\Renderer\Module\EdgeIterator\EdgeIterator;
|
||||
use BaconQrCode\Renderer\Path\Path;
|
||||
|
||||
/**
|
||||
* Groups modules together to a single path.
|
||||
*/
|
||||
final class SquareModule implements ModuleInterface
|
||||
{
|
||||
private static ?SquareModule $instance = null;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function instance() : self
|
||||
{
|
||||
return self::$instance ?: self::$instance = new self();
|
||||
}
|
||||
|
||||
public function createPath(ByteMatrix $matrix) : Path
|
||||
{
|
||||
$path = new Path();
|
||||
|
||||
foreach (new EdgeIterator($matrix) as $edge) {
|
||||
$points = $edge->getSimplifiedPoints();
|
||||
$length = count($points);
|
||||
$path = $path->move($points[0][0], $points[0][1]);
|
||||
|
||||
for ($i = 1; $i < $length; ++$i) {
|
||||
$path = $path->line($points[$i][0], $points[$i][1]);
|
||||
}
|
||||
|
||||
$path = $path->close();
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
}
|
34
vendor/bacon/bacon-qr-code/src/Renderer/Path/Close.php
vendored
Normal file
34
vendor/bacon/bacon-qr-code/src/Renderer/Path/Close.php
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Path;
|
||||
|
||||
final class Close implements OperationInterface
|
||||
{
|
||||
private static ?Close $instance = null;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function instance() : self
|
||||
{
|
||||
return self::$instance ?: self::$instance = new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
public function translate(float $x, float $y) : OperationInterface
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
public function rotate(int $degrees) : OperationInterface
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
86
vendor/bacon/bacon-qr-code/src/Renderer/Path/Curve.php
vendored
Normal file
86
vendor/bacon/bacon-qr-code/src/Renderer/Path/Curve.php
vendored
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Path;
|
||||
|
||||
final class Curve implements OperationInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly float $x1,
|
||||
private readonly float $y1,
|
||||
private readonly float $x2,
|
||||
private readonly float $y2,
|
||||
private readonly float $x3,
|
||||
private readonly float $y3
|
||||
) {
|
||||
}
|
||||
|
||||
public function getX1() : float
|
||||
{
|
||||
return $this->x1;
|
||||
}
|
||||
|
||||
public function getY1() : float
|
||||
{
|
||||
return $this->y1;
|
||||
}
|
||||
|
||||
public function getX2() : float
|
||||
{
|
||||
return $this->x2;
|
||||
}
|
||||
|
||||
public function getY2() : float
|
||||
{
|
||||
return $this->y2;
|
||||
}
|
||||
|
||||
public function getX3() : float
|
||||
{
|
||||
return $this->x3;
|
||||
}
|
||||
|
||||
public function getY3() : float
|
||||
{
|
||||
return $this->y3;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
public function translate(float $x, float $y) : OperationInterface
|
||||
{
|
||||
return new self(
|
||||
$this->x1 + $x,
|
||||
$this->y1 + $y,
|
||||
$this->x2 + $x,
|
||||
$this->y2 + $y,
|
||||
$this->x3 + $x,
|
||||
$this->y3 + $y
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
public function rotate(int $degrees) : OperationInterface
|
||||
{
|
||||
$radians = deg2rad($degrees);
|
||||
$sin = sin($radians);
|
||||
$cos = cos($radians);
|
||||
$x1r = $this->x1 * $cos - $this->y1 * $sin;
|
||||
$y1r = $this->x1 * $sin + $this->y1 * $cos;
|
||||
$x2r = $this->x2 * $cos - $this->y2 * $sin;
|
||||
$y2r = $this->x2 * $sin + $this->y2 * $cos;
|
||||
$x3r = $this->x3 * $cos - $this->y3 * $sin;
|
||||
$y3r = $this->x3 * $sin + $this->y3 * $cos;
|
||||
return new self(
|
||||
$x1r,
|
||||
$y1r,
|
||||
$x2r,
|
||||
$y2r,
|
||||
$x3r,
|
||||
$y3r
|
||||
);
|
||||
}
|
||||
}
|
264
vendor/bacon/bacon-qr-code/src/Renderer/Path/EllipticArc.php
vendored
Normal file
264
vendor/bacon/bacon-qr-code/src/Renderer/Path/EllipticArc.php
vendored
Normal file
|
@ -0,0 +1,264 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Path;
|
||||
|
||||
final class EllipticArc implements OperationInterface
|
||||
{
|
||||
private const ZERO_TOLERANCE = 1e-05;
|
||||
|
||||
private float $xRadius;
|
||||
private float $yRadius;
|
||||
private float $xAxisAngle;
|
||||
|
||||
public function __construct(
|
||||
float $xRadius,
|
||||
float $yRadius,
|
||||
float $xAxisAngle,
|
||||
private readonly bool $largeArc,
|
||||
private readonly bool $sweep,
|
||||
private readonly float $x,
|
||||
private readonly float $y
|
||||
) {
|
||||
$this->xRadius = abs($xRadius);
|
||||
$this->yRadius = abs($yRadius);
|
||||
$this->xAxisAngle = $xAxisAngle % 360;
|
||||
}
|
||||
|
||||
public function getXRadius() : float
|
||||
{
|
||||
return $this->xRadius;
|
||||
}
|
||||
|
||||
public function getYRadius() : float
|
||||
{
|
||||
return $this->yRadius;
|
||||
}
|
||||
|
||||
public function getXAxisAngle() : float
|
||||
{
|
||||
return $this->xAxisAngle;
|
||||
}
|
||||
|
||||
public function isLargeArc() : bool
|
||||
{
|
||||
return $this->largeArc;
|
||||
}
|
||||
|
||||
public function isSweep() : bool
|
||||
{
|
||||
return $this->sweep;
|
||||
}
|
||||
|
||||
public function getX() : float
|
||||
{
|
||||
return $this->x;
|
||||
}
|
||||
|
||||
public function getY() : float
|
||||
{
|
||||
return $this->y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
public function translate(float $x, float $y) : OperationInterface
|
||||
{
|
||||
return new self(
|
||||
$this->xRadius,
|
||||
$this->yRadius,
|
||||
$this->xAxisAngle,
|
||||
$this->largeArc,
|
||||
$this->sweep,
|
||||
$this->x + $x,
|
||||
$this->y + $y
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
public function rotate(int $degrees) : OperationInterface
|
||||
{
|
||||
$radians = deg2rad($degrees);
|
||||
$sin = sin($radians);
|
||||
$cos = cos($radians);
|
||||
$xr = $this->x * $cos - $this->y * $sin;
|
||||
$yr = $this->x * $sin + $this->y * $cos;
|
||||
return new self(
|
||||
$this->xRadius,
|
||||
$this->yRadius,
|
||||
$this->xAxisAngle,
|
||||
$this->largeArc,
|
||||
$this->sweep,
|
||||
$xr,
|
||||
$yr
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the elliptic arc to multiple curves.
|
||||
*
|
||||
* Since not all image back ends support elliptic arcs, this method allows to convert the arc into multiple curves
|
||||
* resembling the same result.
|
||||
*
|
||||
* @see https://mortoray.com/2017/02/16/rendering-an-svg-elliptical-arc-as-bezier-curves/
|
||||
* @return array<Curve|Line>
|
||||
*/
|
||||
public function toCurves(float $fromX, float $fromY) : array
|
||||
{
|
||||
if (sqrt(($fromX - $this->x) ** 2 + ($fromY - $this->y) ** 2) < self::ZERO_TOLERANCE) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($this->xRadius < self::ZERO_TOLERANCE || $this->yRadius < self::ZERO_TOLERANCE) {
|
||||
return [new Line($this->x, $this->y)];
|
||||
}
|
||||
|
||||
return $this->createCurves($fromX, $fromY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Curve[]
|
||||
*/
|
||||
private function createCurves(float $fromX, float $fromY) : array
|
||||
{
|
||||
$xAngle = deg2rad($this->xAxisAngle);
|
||||
list($centerX, $centerY, $radiusX, $radiusY, $startAngle, $deltaAngle) =
|
||||
$this->calculateCenterPointParameters($fromX, $fromY, $xAngle);
|
||||
|
||||
$s = $startAngle;
|
||||
$e = $s + $deltaAngle;
|
||||
$sign = ($e < $s) ? -1 : 1;
|
||||
$remain = abs($e - $s);
|
||||
$p1 = self::point($centerX, $centerY, $radiusX, $radiusY, $xAngle, $s);
|
||||
$curves = [];
|
||||
|
||||
while ($remain > self::ZERO_TOLERANCE) {
|
||||
$step = min($remain, pi() / 2);
|
||||
$signStep = $step * $sign;
|
||||
$p2 = self::point($centerX, $centerY, $radiusX, $radiusY, $xAngle, $s + $signStep);
|
||||
|
||||
$alphaT = tan($signStep / 2);
|
||||
$alpha = sin($signStep) * (sqrt(4 + 3 * $alphaT ** 2) - 1) / 3;
|
||||
$d1 = self::derivative($radiusX, $radiusY, $xAngle, $s);
|
||||
$d2 = self::derivative($radiusX, $radiusY, $xAngle, $s + $signStep);
|
||||
|
||||
$curves[] = new Curve(
|
||||
$p1[0] + $alpha * $d1[0],
|
||||
$p1[1] + $alpha * $d1[1],
|
||||
$p2[0] - $alpha * $d2[0],
|
||||
$p2[1] - $alpha * $d2[1],
|
||||
$p2[0],
|
||||
$p2[1]
|
||||
);
|
||||
|
||||
$s += $signStep;
|
||||
$remain -= $step;
|
||||
$p1 = $p2;
|
||||
}
|
||||
|
||||
return $curves;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float[]
|
||||
*/
|
||||
private function calculateCenterPointParameters(float $fromX, float $fromY, float $xAngle): array
|
||||
{
|
||||
$rX = $this->xRadius;
|
||||
$rY = $this->yRadius;
|
||||
|
||||
// F.6.5.1
|
||||
$dx2 = ($fromX - $this->x) / 2;
|
||||
$dy2 = ($fromY - $this->y) / 2;
|
||||
$x1p = cos($xAngle) * $dx2 + sin($xAngle) * $dy2;
|
||||
$y1p = -sin($xAngle) * $dx2 + cos($xAngle) * $dy2;
|
||||
|
||||
// F.6.5.2
|
||||
$rxs = $rX ** 2;
|
||||
$rys = $rY ** 2;
|
||||
$x1ps = $x1p ** 2;
|
||||
$y1ps = $y1p ** 2;
|
||||
$cr = $x1ps / $rxs + $y1ps / $rys;
|
||||
|
||||
if ($cr > 1) {
|
||||
$s = sqrt($cr);
|
||||
$rX *= $s;
|
||||
$rY *= $s;
|
||||
$rxs = $rX ** 2;
|
||||
$rys = $rY ** 2;
|
||||
}
|
||||
|
||||
$dq = ($rxs * $y1ps + $rys * $x1ps);
|
||||
$pq = ($rxs * $rys - $dq) / $dq;
|
||||
$q = sqrt(max(0, $pq));
|
||||
|
||||
if ($this->largeArc === $this->sweep) {
|
||||
$q = -$q;
|
||||
}
|
||||
|
||||
$cxp = $q * $rX * $y1p / $rY;
|
||||
$cyp = -$q * $rY * $x1p / $rX;
|
||||
|
||||
// F.6.5.3
|
||||
$cx = cos($xAngle) * $cxp - sin($xAngle) * $cyp + ($fromX + $this->x) / 2;
|
||||
$cy = sin($xAngle) * $cxp + cos($xAngle) * $cyp + ($fromY + $this->y) / 2;
|
||||
|
||||
// F.6.5.5
|
||||
$theta = self::angle(1, 0, ($x1p - $cxp) / $rX, ($y1p - $cyp) / $rY);
|
||||
|
||||
// F.6.5.6
|
||||
$delta = self::angle(($x1p - $cxp) / $rX, ($y1p - $cyp) / $rY, (-$x1p - $cxp) / $rX, (-$y1p - $cyp) / $rY);
|
||||
$delta = fmod($delta, pi() * 2);
|
||||
|
||||
if (! $this->sweep) {
|
||||
$delta -= 2 * pi();
|
||||
}
|
||||
|
||||
return [$cx, $cy, $rX, $rY, $theta, $delta];
|
||||
}
|
||||
|
||||
private static function angle(float $ux, float $uy, float $vx, float $vy) : float
|
||||
{
|
||||
// F.6.5.4
|
||||
$dot = $ux * $vx + $uy * $vy;
|
||||
$length = sqrt($ux ** 2 + $uy ** 2) * sqrt($vx ** 2 + $vy ** 2);
|
||||
$angle = acos(min(1, max(-1, $dot / $length)));
|
||||
|
||||
if (($ux * $vy - $uy * $vx) < 0) {
|
||||
return -$angle;
|
||||
}
|
||||
|
||||
return $angle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float[]
|
||||
*/
|
||||
private static function point(
|
||||
float $centerX,
|
||||
float $centerY,
|
||||
float $radiusX,
|
||||
float $radiusY,
|
||||
float $xAngle,
|
||||
float $angle
|
||||
) : array {
|
||||
return [
|
||||
$centerX + $radiusX * cos($xAngle) * cos($angle) - $radiusY * sin($xAngle) * sin($angle),
|
||||
$centerY + $radiusX * sin($xAngle) * cos($angle) + $radiusY * cos($xAngle) * sin($angle),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float[]
|
||||
*/
|
||||
private static function derivative(float $radiusX, float $radiusY, float $xAngle, float $angle) : array
|
||||
{
|
||||
return [
|
||||
-$radiusX * cos($xAngle) * sin($angle) - $radiusY * sin($xAngle) * cos($angle),
|
||||
-$radiusX * sin($xAngle) * sin($angle) + $radiusY * cos($xAngle) * cos($angle),
|
||||
];
|
||||
}
|
||||
}
|
42
vendor/bacon/bacon-qr-code/src/Renderer/Path/Line.php
vendored
Normal file
42
vendor/bacon/bacon-qr-code/src/Renderer/Path/Line.php
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Path;
|
||||
|
||||
final class Line implements OperationInterface
|
||||
{
|
||||
public function __construct(private readonly float $x, private readonly float $y)
|
||||
{
|
||||
}
|
||||
|
||||
public function getX() : float
|
||||
{
|
||||
return $this->x;
|
||||
}
|
||||
|
||||
public function getY() : float
|
||||
{
|
||||
return $this->y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
public function translate(float $x, float $y) : OperationInterface
|
||||
{
|
||||
return new self($this->x + $x, $this->y + $y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
public function rotate(int $degrees) : OperationInterface
|
||||
{
|
||||
$radians = deg2rad($degrees);
|
||||
$sin = sin($radians);
|
||||
$cos = cos($radians);
|
||||
$xr = $this->x * $cos - $this->y * $sin;
|
||||
$yr = $this->x * $sin + $this->y * $cos;
|
||||
return new self($xr, $yr);
|
||||
}
|
||||
}
|
42
vendor/bacon/bacon-qr-code/src/Renderer/Path/Move.php
vendored
Normal file
42
vendor/bacon/bacon-qr-code/src/Renderer/Path/Move.php
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Path;
|
||||
|
||||
final class Move implements OperationInterface
|
||||
{
|
||||
public function __construct(private readonly float $x, private readonly float $y)
|
||||
{
|
||||
}
|
||||
|
||||
public function getX() : float
|
||||
{
|
||||
return $this->x;
|
||||
}
|
||||
|
||||
public function getY() : float
|
||||
{
|
||||
return $this->y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
public function translate(float $x, float $y) : OperationInterface
|
||||
{
|
||||
return new self($this->x + $x, $this->y + $y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
public function rotate(int $degrees) : OperationInterface
|
||||
{
|
||||
$radians = deg2rad($degrees);
|
||||
$sin = sin($radians);
|
||||
$cos = cos($radians);
|
||||
$xr = $this->x * $cos - $this->y * $sin;
|
||||
$yr = $this->x * $sin + $this->y * $cos;
|
||||
return new self($xr, $yr);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue