Remove tests files

This commit is contained in:
infosave2007
2025-11-07 13:36:54 +03:00
parent a33af60f2d
commit 5d039484a1
5 changed files with 0 additions and 959 deletions
-135
View File
@@ -1,135 +0,0 @@
# Changelog
All notable changes to Amnezia VPN Web Panel will be documented in this file.
## [1.1.0] - 2025-11-06
### Added
- **Traffic Statistics Tracking**
- Real-time bandwidth monitoring (upload/download)
- Last handshake tracking
- Online/offline status detection
- Formatted stats display (B, KB, MB, GB, TB)
- Manual sync stats button on server and client pages
- Batch stats sync for all clients on server
- **Client Access Control**
- Revoke client access (temporary disable)
- Restore revoked clients
- Improved delete with proper cleanup
- Status badges (Active/Disabled)
- **Enhanced UI**
- Traffic columns in client list table
- Last seen timestamp display
- Revoke/Restore/Delete action buttons
- Real-time stats refresh (AJAX)
- Online status indicator (green dot)
- Improved client view page with stats panel
- **API Endpoints**
- `GET /api/clients/{id}` - Get client with stats
- `POST /api/clients/{id}/revoke` - Revoke client access
- `POST /api/clients/{id}/restore` - Restore client access
- `GET /api/servers/{id}/clients` - Get all clients with stats
- `POST /servers/{id}/sync-stats` - Batch sync server clients
- `POST /clients/{id}/sync-stats` - Sync single client stats
### Technical
- Database migration `002_add_traffic_stats.sql`
- New columns: `bytes_sent`, `bytes_received`, `last_handshake`, `last_sync_at`
- WireGuard stats parsing from `wg show` output
- Peer removal from wg0.conf using sed
- `wg syncconf` for live config updates
### Documentation
- Created `TRAFFIC_STATS.md` - Complete traffic stats guide
- API usage examples
- Troubleshooting section
## [1.0.0] - 2024-11-05
### Added
- Initial release of Amnezia VPN Web Management Panel
- Full VPN server deployment via SSH
- AmneziaWG container management on remote servers
- Client configuration creation and management
- QR code generation compatible with Amnezia VPN apps
- User authentication system (login/register/logout)
- Role-based access control (admin/user)
- Modern responsive UI with Tailwind CSS
- Dashboard with server and client overview
- Server CRUD operations (Create, Read, Update, Delete)
- Client management with download and QR code features
- Docker Compose deployment setup
- Database migrations system
- Twig template engine integration
- REST API foundation for future Telegram bot integration
### Technical Details
- PHP 8.2 backend
- MySQL 8.0 database
- Endroid QR Code library v5.x integration
- Qt/QDataStream compatible QR encoding (tested with Amnezia apps)
- AWG obfuscation parameters support (Jc, Jmin, Jmax, S1, S2, H1-H4)
- Secure password hashing with bcrypt
- PDO prepared statements for SQL injection prevention
- XSS protection via Twig auto-escaping
### Security
- Default admin account: admin@amnez.ia / admin123 (change immediately!)
- Bcrypt password hashing
- SQL injection prevention
- XSS protection
- Session-based authentication
### Known Issues
- QR code library updated to v5.x with API compatibility fixes
- Server deletion not yet removing Docker containers from remote servers
- Client deletion not yet updating server wg0.conf file
- API authentication (JWT) not yet implemented
- Rate limiting not yet implemented
### Infrastructure
- Docker container with PHP 8.2 Apache
- MySQL 8.0 container
- Docker Compose orchestration
- Volume persistence for database
- Composer dependency management
## [Unreleased]
### Planned Features
- JWT authentication for REST API
- Complete Telegram bot integration
- Server monitoring and health checks
- Bandwidth usage statistics
- Client traffic analysis
- Email notifications
- Two-factor authentication (2FA)
- Multi-language support
- Dark mode UI theme
- Automated backups
- Rate limiting for API endpoints
- Export/import configurations
- Server templates for quick deployment
- Client groups and tagging
- Advanced logging and audit trail
- User management admin panel
### Improvements Planned
- Better error handling and user feedback
- Real-time deployment progress updates
- Server resource monitoring (CPU, RAM, bandwidth)
- Client connection status tracking
- Automatic Let's Encrypt SSL setup
- Database connection pooling
- Caching layer (Redis)
- WebSocket support for real-time updates
- Mobile-responsive improvements
- Accessibility enhancements
---
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-345
View File
@@ -1,345 +0,0 @@
# Testing Guide
This document describes how to test the Amnezia VPN Web Panel.
## Prerequisites
- Docker and Docker Compose installed
- Test VPS server with SSH access (for full deployment testing)
- Amnezia VPN mobile app (Android/iOS) for QR code testing
## Quick Test Setup
### 1. Start the Application
```bash
cd amnezia-web-panel
docker compose up -d
```
### 2. Access the Panel
Open browser: `http://localhost:8082`
### 3. Login
Default credentials:
- Email: `admin@amnez.ia`
- Password: `admin123`
## Unit Tests
### Test QR Code Generation
```bash
docker compose exec web php test_qr.php
```
Expected output:
```
✅ Success! QR code generation working correctly.
```
This creates `test_qr.png` in the project root.
### Verify QR Code Payload
```bash
# Compare payload with original implementation
php /tmp/test_compare_qr.php
```
The payload should match exactly with the original Amnezia QR format.
## Integration Tests
### Test 1: User Registration
1. Logout from admin account
2. Click "Register"
3. Fill in:
- Name: "Test User"
- Email: "test@example.com"
- Password: "testpass123"
4. Click "Register"
5. ✅ Should redirect to dashboard
### Test 2: Server Creation (Without Deployment)
1. Go to "Servers" → "Add Server"
2. Fill in:
- Name: "Test Server"
- Host: "192.168.1.100"
- Port: 22
- Username: "root"
- Password: "dummy"
3. Click "Add Server" (will fail at deployment, but server record created)
4. ✅ Should see server in list with "pending" status
### Test 3: Full Server Deployment (Requires Real VPS)
**Prerequisites**: Remote Linux server with SSH access
1. Go to "Servers" → "Add Server"
2. Fill in real server credentials:
- Name: "Production Server 1"
- Host: "your.server.ip"
- Port: 22
- Username: "root"
- Password: "your_ssh_password"
3. Click "Add Server"
4. Wait for deployment (5-10 minutes)
5. ✅ Server status should change to "active"
6. ✅ Server should show public key and VPN port
### Test 4: Client Creation
**Prerequisites**: Active server from Test 3
1. Click on active server
2. In "Create Client" section, enter name: "test-client-1"
3. Click "Create"
4. ✅ Should redirect to client view page
5. ✅ Should see QR code displayed
6. ✅ "Download Config" button should work
### Test 5: QR Code Scanning
**Prerequisites**: Amnezia VPN app installed on phone
1. Create a client (Test 4)
2. Open Amnezia VPN app
3. Tap "Add server" → "Scan QR code"
4. Scan the QR code from web panel
5. ✅ Configuration should be imported successfully
6. ✅ Connect to VPN should work
7. ✅ Check IP address changed (e.g., whatismyip.com)
### Test 6: Configuration Download
1. Go to client details page
2. Click "Download Config"
3. ✅ Should download `.conf` file
4. Open file in text editor
5. ✅ Should contain valid WireGuard config with:
- [Interface] section with PrivateKey, Address, DNS
- AWG parameters (Jc, Jmin, Jmax, S1, S2, H1-H4)
- [Peer] section with PublicKey, PresharedKey, Endpoint
6. Import manually into Amnezia VPN app
7. ✅ Should work same as QR code
### Test 7: Multiple Clients
1. Create 5 clients on same server
2. ✅ Each should get unique IP (10.8.1.2, 10.8.1.3, etc.)
3. ✅ Each should have unique keys
4. ✅ All QR codes should scan successfully
5. Test connections from multiple devices
6. ✅ All should connect simultaneously
### Test 8: Client Deletion
1. Go to client details
2. Click "Delete"
3. ✅ Client should be removed from database
4. ⚠️ **Known Issue**: Not yet removed from server wg0.conf
### Test 9: Server Deletion
1. Go to server list
2. Click "Delete" on a server
3. ✅ Server should be removed from database
4. ⚠️ **Known Issue**: Docker container not removed from remote server
### Test 10: Access Control
1. Create new user account
2. Login as new user
3. Create a server
4. Logout and login as admin
5. ✅ Admin should see all servers (including user's)
6. Login as regular user
7. ✅ Regular user should only see their own servers
## Security Tests
### Test 11: SQL Injection Protection
Try creating server with malicious name:
```
Name: Test'; DROP TABLE vpn_servers; --
```
✅ Should be safely escaped, no SQL error
### Test 12: XSS Protection
Try creating client with script tag:
```
Name: <script>alert('XSS')</script>
```
✅ Should be HTML-escaped in output
### Test 13: Authentication
1. Logout
2. Try accessing `/dashboard` directly
3. ✅ Should redirect to login page
### Test 14: Password Security
1. Check database:
```bash
docker compose exec db mysql -u amnezia -pamnezia123 amnezia_panel
SELECT password FROM users LIMIT 1;
```
✅ Password should be bcrypt hash, not plaintext
## Performance Tests
### Test 15: Multiple Concurrent Requests
```bash
# Install Apache Bench
sudo apt install apache2-utils
# Test login endpoint
ab -n 100 -c 10 -p login.txt -T application/x-www-form-urlencoded http://localhost:8082/login
```
✅ Should handle 100 requests without errors
### Test 16: Database Connection Pooling
Create 10 clients rapidly:
```bash
for i in {1..10}; do
curl -X POST http://localhost:8082/servers/1/clients/create \
-d "name=client$i" \
-b cookies.txt
done
```
✅ Should complete without connection errors
## Browser Compatibility
Test in:
- ✅ Chrome/Edge (Chromium)
- ✅ Firefox
- ✅ Safari
- ✅ Mobile browsers (iOS Safari, Chrome Android)
## Docker Tests
### Test 17: Container Health
```bash
docker compose ps
```
✅ Both containers should be "Up" and healthy
### Test 18: Volume Persistence
```bash
# Stop containers
docker compose down
# Start again
docker compose up -d
# Login
```
✅ All data should persist (servers, clients, users)
### Test 19: Logs
```bash
docker compose logs -f web
docker compose logs -f db
```
✅ No errors in logs during normal operation
## Troubleshooting
### QR Code Not Displaying
Check:
```bash
docker compose exec web php test_qr.php
```
If fails, check:
- GD extension installed: `php -m | grep gd`
- Composer dependencies: `composer show endroid/qr-code`
### Can't Connect to Database
Check:
```bash
docker compose exec web php -r "
\$pdo = new PDO('mysql:host=db;dbname=amnezia_panel', 'amnezia', 'amnezia123');
echo 'Connected successfully';
"
```
### SSH Deployment Fails
Test SSH manually:
```bash
sshpass -p 'yourpassword' ssh -o StrictHostKeyChecking=no root@server.ip 'echo OK'
```
## Test Checklist
Before releasing or deploying:
- [ ] All unit tests pass
- [ ] QR code generation works
- [ ] Server deployment works on real VPS
- [ ] Client creation works
- [ ] QR codes scan in Amnezia app
- [ ] VPN connection works
- [ ] Multiple clients work simultaneously
- [ ] Authentication works
- [ ] Access control works (user/admin)
- [ ] SQL injection protected
- [ ] XSS protected
- [ ] CSRF protection (if implemented)
- [ ] Password hashing verified
- [ ] All browsers work
- [ ] Mobile responsive
- [ ] Docker containers healthy
- [ ] Data persists after restart
- [ ] No errors in logs
- [ ] README instructions accurate
- [ ] Default password changed
## Automated Testing (Future)
Consider implementing:
- PHPUnit for unit tests
- Selenium for browser automation
- GitHub Actions for CI/CD
- Code coverage reports
- Automated security scanning
## Reporting Issues
When reporting bugs, include:
1. Steps to reproduce
2. Expected behavior
3. Actual behavior
4. Docker logs: `docker compose logs`
5. Browser console errors
6. PHP version: `docker compose exec web php -v`
7. MySQL version: `docker compose exec db mysql -V`
---
Happy Testing! 🧪
-278
View File
@@ -1,278 +0,0 @@
# Traffic Statistics & Client Management Features
## New Features Added (2025-11-06)
### 1. Traffic Statistics Tracking
**Database Changes:**
- Added `bytes_sent` - Total bytes uploaded by client
- Added `bytes_received` - Total bytes downloaded by client
- Added `last_handshake` - Last successful WireGuard connection time
- Added `last_sync_at` - Last time stats were synced from server
**Backend Methods:**
- `VpnClient->syncStats()` - Sync single client statistics from server
- `VpnClient::syncAllStatsForServer($serverId)` - Sync all clients on a server
- `VpnClient->getFormattedStats()` - Get human-readable stats (KB, MB, GB)
- `VpnClient::getClientStatsFromServer()` - Parse `wg show` output
**How it works:**
1. Connects to server via SSH
2. Runs `wg show wg0 dump` inside Docker container
3. Parses output to extract transfer statistics
4. Updates database with latest stats
5. Calculates "last seen" based on handshake time
### 2. Client Access Control
**Revoke Access:**
- Temporarily disable client without deleting
- Removes peer from server WireGuard config
- Keeps client record in database with status='disabled'
- Can be restored later
**Restore Access:**
- Re-enable previously revoked client
- Re-adds peer to server WireGuard config
- Changes status back to 'active'
**Delete Client:**
- Permanently removes client
- First revokes access (removes from server)
- Then deletes from database
**Backend Methods:**
- `VpnClient->revoke()` - Disable client access
- `VpnClient->restore()` - Re-enable client access
- `VpnClient->delete()` - Permanently delete client
- `VpnClient::removeClientFromServer()` - Remove peer from wg0.conf
- `VpnClient::removeFromClientsTable()` - Remove from clientsTable JSON
### 3. Web Interface Updates
**Server View Page (`/servers/{id}`):**
- Added "Sync Stats" button - refreshes all client stats
- Enhanced client table with:
- Status badge (Active/Disabled)
- Traffic columns (Upload/Download)
- Last seen timestamp
- Action buttons (View, Revoke/Restore, Delete)
**Client View Page (`/clients/{id}`):**
- Added traffic statistics panel
- Shows uploaded/downloaded/total bytes
- Displays last handshake time
- "Refresh" button to sync latest stats
- Real-time status indicator (Online if handshake < 5 min)
- Revoke/Restore button based on current status
### 4. API Endpoints
All endpoints require authentication (session-based, JWT planned).
**GET `/api/clients/{id}`**
```json
{
"success": true,
"client": {
"id": 1,
"name": "client1",
"server_id": 1,
"client_ip": "10.8.1.2",
"status": "active",
"created_at": "2025-11-06 12:00:00",
"stats": {
"sent": "1.5 GB",
"received": "3.2 GB",
"total": "4.7 GB",
"last_seen": "Online",
"is_online": true
},
"bytes_sent": 1610612736,
"bytes_received": 3435973836,
"last_handshake": "2025-11-06 14:30:00"
}
}
```
**POST `/api/clients/{id}/revoke`**
```json
{
"success": true,
"message": "Client revoked"
}
```
**POST `/api/clients/{id}/restore`**
```json
{
"success": true,
"message": "Client restored"
}
```
**GET `/api/servers/{id}/clients`**
Returns all clients with synced stats for a server.
```json
{
"success": true,
"clients": [
{
"id": 1,
"name": "client1",
"client_ip": "10.8.1.2",
"status": "active",
"stats": {...},
...
}
]
}
```
**POST `/servers/{id}/sync-stats`**
```json
{
"success": true,
"synced": 5
}
```
**POST `/clients/{id}/sync-stats`**
```json
{
"success": true,
"stats": {
"sent": "1.5 GB",
"received": "3.2 GB",
"total": "4.7 GB",
"last_seen": "Online"
}
}
```
## Usage Examples
### Web Interface
1. **View Client Statistics:**
- Go to server page
- Click "Sync Stats" to refresh all clients
- View traffic in table or click client for details
2. **Revoke Client Access:**
- In server client list, click "Revoke" next to active client
- Confirm action
- Client status changes to "Disabled"
- Client can no longer connect to VPN
3. **Restore Client Access:**
- Find disabled client in list
- Click "Restore"
- Client status changes to "Active"
- Client can connect again
### API Usage (for Telegram Bot)
```php
// Get client with stats
$response = $api->get('/api/clients/1');
$client = $response['client'];
echo "Traffic: {$client['stats']['total']}\n";
echo "Status: {$client['stats']['last_seen']}\n";
// Revoke access
$api->post('/api/clients/1/revoke');
// Restore access
$api->post('/api/clients/1/restore');
// Get all server clients with stats
$response = $api->get('/api/servers/1/clients');
foreach ($response['clients'] as $client) {
echo "{$client['name']}: {$client['stats']['total']}\n";
}
```
## Technical Details
### WireGuard Stats Format
The `wg show wg0 dump` command returns:
```
private_key public_key preshared_key endpoint allowed_ips latest_handshake transfer_rx transfer_tx persistent_keepalive
```
We parse:
- `latest_handshake` - Unix timestamp of last handshake
- `transfer_rx` - Bytes received by server (client sent)
- `transfer_tx` - Bytes sent by server (client received)
### Peer Removal
Removing peer from `wg0.conf`:
```bash
# Find and delete [Peer] block with matching PublicKey
sed -i '/^\[Peer\]/,/^$/{/PublicKey = <key>/,/^$/d}' /opt/amnezia/awg/wg0.conf
# Apply changes without restart
wg syncconf wg0 <(wg-quick strip /opt/amnezia/awg/wg0.conf)
```
### Client Status Logic
- **Online:** Last handshake < 5 minutes ago
- **Recently seen:** Last handshake < 1 hour ago
- **Offline:** Last handshake > 1 hour ago
- **Never connected:** No handshake recorded
## Database Migration
Migration file: `migrations/002_add_traffic_stats.sql`
To apply manually:
```bash
docker compose exec -T db mysql -u root -prootpassword amnezia_panel < migrations/002_add_traffic_stats.sql
```
## Performance Considerations
- Stats sync requires SSH connection to server
- Each sync runs `wg show wg0 dump` command
- For many clients, use batch sync: `VpnClient::syncAllStatsForServer()`
- Consider caching stats and refreshing periodically (e.g., every 5 minutes)
- Stats updates are logged in `last_sync_at` column
## Future Enhancements
- [ ] Automatic periodic stats sync (cron job)
- [ ] Traffic usage alerts (email/Telegram)
- [ ] Bandwidth limits per client
- [ ] Historical traffic graphs
- [ ] Export stats to CSV
- [ ] Real-time WebSocket updates
- [ ] Client connection notifications
## Troubleshooting
**Stats not syncing:**
1. Check server SSH connection
2. Verify Docker container is running: `docker ps | grep awg`
3. Check `wg show wg0` output inside container
4. Review error logs
**Client still connecting after revoke:**
1. Check if peer was removed from wg0.conf
2. Verify `wg syncconf` was executed
3. Restart WireGuard: `docker exec <container> wg-quick down wg0 && wg-quick up wg0`
**Last handshake not updating:**
1. Ensure client is actually connected
2. Check WireGuard keepalive settings (should be 25 seconds)
3. Verify server time is synchronized (NTP)
---
**Last Updated:** 2025-11-06
**Version:** 1.1.0
-142
View File
@@ -1,142 +0,0 @@
# Translation Management
## Database Reset & Setup
All translations have been reset and reloaded with complete English keys (79 total).
### Migration Applied
- `migrations/006_full_translations.sql` - Complete translation reset with all 79 English keys
## Translation Keys Summary
### Categories:
- **Authentication** (5 keys): email, login, name, password, register
- **Clients** (17 keys): actions, add, delete, download_config, ip, etc.
- **Dashboard** (5 keys): active_clients, title, total_clients, etc.
- **Forms** (6 keys): cancel, close, loading, processing, save, submit
- **Menu** (6 keys): clients, dashboard, logout, servers, settings, users
- **Messages** (6 keys): confirm, deleted, deployed, error, saved, success
- **Servers** (12 keys): actions, add, clients, delete, deploy, etc.
- **Settings** (17 keys): api_keys, auto_translate, translations, etc.
- **Status** (5 keys): active, deploying, disabled, error, inactive
**Total: 79 keys**
## How to Translate All Languages
### Option 1: Via Web Interface
1. Login as admin: http://localhost:8082/login
2. Go to Settings: http://localhost:8082/settings
3. Add your OpenRouter API key
4. Click "Auto-translate" button for each language
### Option 2: Via Command Line (Recommended)
```bash
# First, add your OpenRouter API key via Settings page
# Then run the auto-translation script
docker compose exec app php bin/translate_all.php
```
This will automatically translate all 5 languages:
- 🇷🇺 Russian (ru)
- 🇪🇸 Spanish (es)
- 🇩🇪 German (de)
- 🇫🇷 French (fr)
- 🇨🇳 Chinese (zh)
### Option 3: Translate Single Language
```bash
# Translate only Russian
docker compose exec app php bin/translate.php ru
# Translate only Spanish
docker compose exec app php bin/translate.php es
```
## Current Status
After migration:
```
+---------------+-------+
| language_code | count |
+---------------+-------+
| en | 79 |
+---------------+-------+
```
After auto-translation (expected):
```
+---------------+-------+
| language_code | count |
+---------------+-------+
| de | 79 |
| en | 79 |
| es | 79 |
| fr | 79 |
| ru | 79 |
| zh | 79 |
+---------------+-------+
```
## API Rate Limits
OpenRouter free models have rate limits:
- **gemini-2.0-flash-exp:free** - Primary model
- **meta-llama/llama-3.2-3b-instruct:free** - Fallback 1
- **google/gemini-flash-1.5** - Fallback 2
The translation script includes:
- Automatic retries with exponential backoff
- Model fallback on rate limits
- 5-second delay between languages
- Batch translation for efficiency
## Troubleshooting
### Error: "OpenRouter API key not found"
Add your API key via Settings page first:
1. Go to http://localhost:8082/settings
2. Enter your OpenRouter API key (format: `sk-or-v1-...`)
3. Click Save
### Error: "Rate limit exceeded"
Wait a few minutes and try again, or:
- Use the web interface (slower but more controlled)
- Increase delay in `bin/translate_all.php`
- Get a paid OpenRouter API key
### Check Translation Progress
```bash
docker compose exec db sh -c 'mysql -u root -p"$MYSQL_ROOT_PASSWORD" amnezia_panel -e "
SELECT
l.code,
l.name,
COUNT(t.id) as translated,
(SELECT COUNT(*) FROM translations WHERE language_code = \"en\") as total
FROM languages l
LEFT JOIN translations t ON l.code = t.language_code
GROUP BY l.code
ORDER BY l.code;
"'
```
## Manual Export/Import
### Export translations to JSON
```bash
docker compose exec app php -r "
require 'vendor/autoload.php';
require 'inc/Config.php';
require 'inc/DB.php';
require 'inc/Translator.php';
Config::load('.env');
DB::conn();
echo Translator::exportToJson('ru');
" > translations_ru.json
```
### Import from JSON
```php
Translator::importFromJson('ru', file_get_contents('translations_ru.json'));
```
-59
View File
@@ -1,59 +0,0 @@
<?php
/**
* Test QR code generation with real config
*/
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/inc/QrUtil.php';
// Sample AmneziaWG config
$config = <<<EOT
[Interface]
PrivateKey = YBtGKhj6pCKHBh8GtTaLV9WqVDCK4EWqIwXqP3vCg1M=
Address = 10.8.1.2/32
DNS = 1.1.1.1, 1.0.0.1
Jc = 3
Jmin = 40
Jmax = 70
S1 = 86
S2 = 3
H1 = 1
H2 = 2
H3 = 3
H4 = 4
[Peer]
PublicKey = ZVfqrLBQs3MpZjGMqOmDk0nIVMmVqRWnZHQqbOoJwXk=
PresharedKey = uOPELRGBqTqnJPqEGCPRSLVjMW3RGFOQOhMDqmWLBBo=
Endpoint = 123.45.67.89:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
EOT;
try {
echo "Testing QR code generation...\n\n";
// Generate payload
$payloadOld = QrUtil::encodeOldPayloadFromConf($config);
echo "Payload (URL-safe base64): " . substr($payloadOld, 0, 100) . "...\n\n";
// Generate QR code PNG
$dataUri = QrUtil::pngBase64($payloadOld);
echo "QR Code Data URI: " . substr($dataUri, 0, 100) . "...\n\n";
// Save to file for inspection
if (preg_match('/^data:image\/(png|svg\+xml);base64,(.+)$/', $dataUri, $m)) {
$imageData = base64_decode($m[2]);
$ext = $m[1] === 'svg+xml' ? 'svg' : 'png';
$filename = __DIR__ . "/test_qr." . $ext;
file_put_contents($filename, $imageData);
echo "✅ QR code saved to: $filename\n";
}
echo "\n✅ Success! QR code generation working correctly.\n";
} catch (Throwable $e) {
echo "❌ Error: " . $e->getMessage() . "\n";
echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
exit(1);
}