Merge branch 'development'
This commit is contained in:
103
PAYMENT_IMPLEMENTATION_SUMMARY.md
Normal file
103
PAYMENT_IMPLEMENTATION_SUMMARY.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Payment Integration Implementation Summary
|
||||
|
||||
## Overview
|
||||
Complete payment integration for software upgrades using existing ecommerce infrastructure (transaction API, invoice API, PHPMailer, DomPDF).
|
||||
|
||||
## New Files to Create
|
||||
|
||||
### 1. `/webhook_mollie.php` (Root directory)
|
||||
**Purpose**: Mollie webhook handler specifically for software upgrades
|
||||
**Based on**: Existing webhook.php from commerce product
|
||||
**Key features**:
|
||||
- ✅ Uses `/v2/transactions/` API for status updates (consistent with commerce)
|
||||
- ✅ Uses `/v2/invoice/` API for invoice generation
|
||||
- ✅ Creates PDF invoice with DomPDF
|
||||
- ✅ Sends email via PHPMailer
|
||||
- ✅ Creates software license
|
||||
- ✅ Multi-language support
|
||||
- ✅ Sends to bookkeeping if configured
|
||||
|
||||
**Webhook URL**: `https://yourdomain.com/webhook_mollie.php`
|
||||
|
||||
### 2. `/api/v2/post/payment.php`
|
||||
**Purpose**: Create Mollie payment for software upgrade
|
||||
**Input**: serial_number, version_id, user_data
|
||||
**Output**: {checkout_url, payment_id}
|
||||
**Security**: Server-side price calculation
|
||||
|
||||
### 3. `/api/v2/get/payment.php`
|
||||
**Purpose**: Retrieve payment status
|
||||
**Input**: ?payment_id=xxx
|
||||
**Output**: {payment_id, payment_status, serial_number, equipment_id, ...}
|
||||
|
||||
## Modified Files
|
||||
|
||||
### 1. `/assets/functions.php`
|
||||
**Add new functions**:
|
||||
- `generateUniqueLicenseKey()` - Generate unique license keys
|
||||
- `generateSoftwareInvoice($invoice_data, $order_id, $language)` - Generate HTML invoice for software upgrades
|
||||
- Based on existing `generateInvoice()` function
|
||||
- Custom template for software licenses
|
||||
- Shows: Device serial number, software version, license key, expiry date
|
||||
- Returns: [$html_content, $customer_email, $order_id]
|
||||
|
||||
### 2. `/assets/softwaretool.js`
|
||||
**Modify**:
|
||||
- `processPayment()` - Call `/v2/post/payment` API
|
||||
- `downloadAndInstallSoftware()` - Add serial number verification
|
||||
|
||||
### 3. `/softwaretool.php`
|
||||
**Add**: Payment return detection (`?payment_id=xxx`)
|
||||
|
||||
## Database
|
||||
**No changes needed** - Uses existing:
|
||||
- `transactions` table (txn_id, payment_status, payment_amount, etc.)
|
||||
- `transactions_items` table (item_id, item_options with JSON)
|
||||
|
||||
## Payment Status Codes (Matching Commerce System)
|
||||
- `0` = Pending
|
||||
- `1` = Paid
|
||||
- `101` = Open/Pending (Mollie)
|
||||
- `102` = Failed
|
||||
- `103` = Expired
|
||||
- `999` = Canceled
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. ✅ Add Mollie constants to config.php
|
||||
2. Create helper functions in functions.php:
|
||||
- `generateUniqueLicenseKey()`
|
||||
- `generateSoftwareInvoice()`
|
||||
3. Create `/api/v2/post/payment.php`
|
||||
4. Create `/api/v2/get/payment.php`
|
||||
5. Create `/webhook_mollie.php`
|
||||
6. Modify frontend JavaScript
|
||||
7. Modify softwaretool.php
|
||||
8. Test in DEBUG mode
|
||||
9. Test with Mollie sandbox
|
||||
10. Deploy to production
|
||||
|
||||
## Key Benefits
|
||||
|
||||
1. **Consistent with ecommerce** - Uses same API structure
|
||||
2. **Professional invoices** - PDF generation + email delivery
|
||||
3. **Complete audit trail** - Transactions + invoices + licenses
|
||||
4. **Multi-language** - Invoice language based on customer country
|
||||
5. **Bookkeeping integration** - Auto-send to bookkeeping email
|
||||
6. **Refund handling** - Webhook detects refunds (TODO: disable license)
|
||||
|
||||
## Invoice Email Template
|
||||
|
||||
The email will include:
|
||||
- Subject: "Software Upgrade - Invoice: [ORDER_ID]"
|
||||
- HTML body with invoice details
|
||||
- PDF attachment (Invoice_[ORDER_ID].pdf)
|
||||
- Sent to customer email + bookkeeping (if configured)
|
||||
|
||||
**Invoice contains:**
|
||||
- Customer details (name, address, email)
|
||||
- Order ID / Transaction ID
|
||||
- Software upgrade details (version, device serial number)
|
||||
- License key + expiry date (2099-12-31)
|
||||
- Price breakdown
|
||||
- Company information
|
||||
825
PAYMENT_INTEGRATION_PLAN.md
Normal file
825
PAYMENT_INTEGRATION_PLAN.md
Normal file
@@ -0,0 +1,825 @@
|
||||
# Plan: Payment Flow with Redirect for Software Upgrade Tool
|
||||
|
||||
## User Request
|
||||
Design the payment flow for software upgrades using Mollie (payment provider) with the following requirements:
|
||||
1. User initiates paid upgrade
|
||||
2. System redirects to Mollie for payment
|
||||
3. After successful payment, Mollie redirects back to software tool
|
||||
4. System creates license connected to serialnumber
|
||||
5. Download and upload to device starts automatically
|
||||
|
||||
## Key Challenge
|
||||
**User Experience**: How to resume the upgrade flow after payment redirect, ensuring seamless transition from payment completion back to automatic download/upload.
|
||||
|
||||
---
|
||||
|
||||
## Current System Analysis
|
||||
|
||||
### Existing Infrastructure
|
||||
✅ **Transactions Table** - Ready for payment tracking (txn_id, payment_status, payment_amount)
|
||||
✅ **Licenses Table** - Has transaction_id field for linking (currently unused)
|
||||
✅ **Payment Modal UI** - Frontend form exists in softwaretool.js (lines 455-572)
|
||||
❌ **Payment Provider Integration** - No Mollie/Stripe/PayPal implementation exists
|
||||
❌ **Webhook Handlers** - No callback endpoints implemented
|
||||
❌ **Redirect Handling** - No return_url/cancel_url pattern
|
||||
❌ **License Auto-creation** - No logic to create licenses after successful payment
|
||||
❌ **Payment Session State** - No state persistence across redirect cycle
|
||||
|
||||
### Current Payment Flow (Simulated)
|
||||
```
|
||||
softwaretool.js:
|
||||
1. User clicks "Purchase & Install" → showPaymentModal()
|
||||
2. User fills form → processPayment()
|
||||
3. [SIMULATED 2-second delay - no actual payment]
|
||||
4. downloadAndInstallSoftware() → triggers upload.js
|
||||
```
|
||||
|
||||
**Problem**: Step 3 will become a redirect to Mollie, breaking the flow and losing all state.
|
||||
|
||||
---
|
||||
|
||||
## User's Preferred Flow (APPROVED)
|
||||
|
||||
The user wants a simpler, more elegant approach:
|
||||
|
||||
1. **Payment creates license** - Mollie webhook creates license linked to serial number
|
||||
2. **Return to software tool** - User redirected back with upgrade information in URL
|
||||
3. **Reconnect device** - User connects device (may be different device!)
|
||||
4. **Re-check software options** - System calls `software_update` API again
|
||||
5. **License automatically applied** - Paid upgrade now shows as FREE (license found)
|
||||
6. **Install button changes** - "Purchase & Install" becomes "Install Now" (free)
|
||||
7. **User proceeds** - Click install to download and upload
|
||||
|
||||
### Key Benefits
|
||||
- ✅ No complex state management needed
|
||||
- ✅ Existing license checking logic handles everything
|
||||
- ✅ User can connect different device (license is separate)
|
||||
- ✅ Clean separation: payment → license → upgrade check
|
||||
- ✅ Works with existing `software_update.php` license validation (lines 274-311)
|
||||
|
||||
### Critical Security Check
|
||||
**IMPORTANT**: Before starting upload, verify serial number matches the one from payment.
|
||||
- Store `serial_number` in payment session/URL
|
||||
- When user returns and reconnects device, compare:
|
||||
- `serialnumber_from_payment` vs `serialnumber_from_device`
|
||||
- If mismatch: Show warning "Different device detected - license applied to original device (SN: XXXXX)"
|
||||
|
||||
---
|
||||
|
||||
## Proposed Solution Architecture
|
||||
|
||||
### Database Changes
|
||||
**No new tables needed** - Use existing `transactions` and `transactions_items` tables
|
||||
|
||||
**`transactions` table fields:**
|
||||
- `txn_id` (varchar 255, UNIQUE) - Store Mollie payment_id here
|
||||
- `payment_status` (int 11) - Payment status code (need to define: 0=pending, 1=paid, 2=failed, 3=canceled, etc.)
|
||||
- `payment_amount` (decimal 7,2) - Price
|
||||
- `payer_email` (varchar 255) - Customer email
|
||||
- `first_name`, `last_name` - Customer name
|
||||
- `address_*` fields - Customer address
|
||||
- `account_id` (varchar 255) - Can store serial_number here or user account
|
||||
- `payment_method` (int 11) - Payment method ID
|
||||
- `created`, `updated` - Timestamps
|
||||
|
||||
**`transactions_items` table fields:**
|
||||
- `txn_id` (varchar 255) - Links to transactions.txn_id
|
||||
- `item_id` (int 11) - Store version_id (products_software_versions.rowID)
|
||||
- `item_price` (decimal 7,2) - Software version price
|
||||
- `item_quantity` (int 11) - Always 1 for software upgrades
|
||||
- `item_options` (varchar 255) - Store JSON with: `{"serial_number": "22110095", "equipment_id": 123, "hw_version": "r08"}`
|
||||
- `created`, `updated` - Timestamps
|
||||
|
||||
**Payment Status Codes** (matching existing webhook.php):
|
||||
- `0` = Pending (initial state, before Mollie call)
|
||||
- `1` = Paid (payment successful)
|
||||
- `101` = Open/Pending (Mollie isPending or isOpen)
|
||||
- `102` = Failed (Mollie isFailed)
|
||||
- `103` = Expired (Mollie isExpired)
|
||||
- `999` = Canceled (Mollie isCanceled)
|
||||
|
||||
### API Endpoints Needed (Following Standard Structure)
|
||||
1. **POST /api/v2/post/payment.php** - Initiates Mollie payment (create action)
|
||||
2. **GET /api/v2/get/payment.php** - Retrieves payment status and details
|
||||
3. **NEW `webhook_mollie.php`** - Separate webhook for software upgrades (based on webhook.php structure, but simplified for this use case)
|
||||
|
||||
### Simplified Flow Diagram
|
||||
```
|
||||
[User] → [Select Paid Upgrade] → [Payment Modal]
|
||||
↓
|
||||
processPayment() calls POST /v2/post/payment
|
||||
- Store pending payment in DB
|
||||
- Call Mollie API: create payment
|
||||
- Get checkout URL
|
||||
- Redirect user to Mollie
|
||||
↓
|
||||
User pays at Mollie ←→ [Mollie Payment Page]
|
||||
↓
|
||||
┌───────────────────────┴───────────────────────┐
|
||||
↓ ↓
|
||||
[Mollie redirects user back] [Mollie webhook fires asynchronously]
|
||||
softwaretool.php?payment_id={payment_id} NEW webhook_mollie.php receives POST
|
||||
- Calls GET /v2/get/payment?payment_id=X - Fetches payment from Mollie API
|
||||
- Shows status message - Updates transaction status (1=paid)
|
||||
- Display device connection button - Creates license in products_software_licenses
|
||||
- Updates equipment.sw_version_license
|
||||
↓
|
||||
[User clicks "Connect Device"]
|
||||
↓
|
||||
connectDeviceForSoftware()
|
||||
- User connects device (may be different device!)
|
||||
- Read SN, FW, HW from device
|
||||
↓
|
||||
checkSoftwareAvailability() → calls /v2/software_update
|
||||
- Existing license validation (lines 274-311) finds license
|
||||
- Paid upgrade now shows price = 0.00
|
||||
- Button text changes: "Purchase & Install" → "Install Now"
|
||||
↓
|
||||
[User clicks "Install Now"]
|
||||
↓
|
||||
selectUpgrade(option) → sees price = 0, skips payment modal
|
||||
↓
|
||||
downloadAndInstallSoftware()
|
||||
- CRITICAL: Verify serial number matches payment
|
||||
- If mismatch: Show warning but allow (license already applied)
|
||||
- Download firmware
|
||||
- Trigger upload.js
|
||||
```
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
**1. Leverage Existing License Logic**
|
||||
- No need to manually check licenses in frontend
|
||||
- `software_update.php` lines 274-311 already handle this perfectly
|
||||
- When license exists and is valid, price automatically becomes 0.00
|
||||
- Frontend just needs to check `if (price === 0)` to show different button
|
||||
|
||||
**2. Minimal State Management**
|
||||
- Store only essential data in `transactions` and `transactions_items`
|
||||
- URL parameters carry context back (payment_id)
|
||||
- No need to persist entire upgrade state
|
||||
- User reconnects device = fresh state from device
|
||||
|
||||
**3. Serial Number Verification**
|
||||
- Store `serial_number` in `transactions_items.item_options` JSON
|
||||
- After return, when user reconnects device, compare:
|
||||
- `serialnumber_from_payment` (from item_options JSON)
|
||||
- `deviceSerialNumber` (from connected device)
|
||||
- If mismatch: Show warning "Different device detected. License was applied to device SN: XXXXX"
|
||||
- Allow upload to proceed (license is already created for original SN)
|
||||
|
||||
**4. Separate Webhook for Software Upgrades**
|
||||
- Create new `webhook_mollie.php` based on structure from existing webhook.php
|
||||
- Specifically designed for software upgrade payments (no invoice generation needed)
|
||||
- Simplified logic: Just update transaction status and create license
|
||||
- Webhook URL: `https://site.com/webhook_mollie.php`
|
||||
- Webhook is authoritative for license creation
|
||||
- Return URL handler just shows status message
|
||||
- Race condition safe: user may see "payment successful" before webhook fires
|
||||
|
||||
---
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Database & Payment Infrastructure
|
||||
|
||||
**1.1 Database Table - No Changes Needed**
|
||||
```
|
||||
The existing transactions and transactions_items tables will be used.
|
||||
No schema modifications required.
|
||||
```
|
||||
|
||||
**1.2 Create `/api/v2/post/payment.php`**
|
||||
```php
|
||||
<?php
|
||||
defined($security_key) or exit;
|
||||
|
||||
// POST endpoint for payment creation
|
||||
// Input (JSON): serial_number, version_id, user_data (name, email, address)
|
||||
// Output (JSON): {checkout_url: "https://mollie.com/...", payment_id: "tr_xxx"}
|
||||
|
||||
//Connect to DB
|
||||
$pdo = dbConnect($dbname);
|
||||
|
||||
//CONTENT FROM API (POST)
|
||||
$post_content = json_decode($input, true);
|
||||
|
||||
// SECURITY: Never trust price/currency from frontend!
|
||||
// Steps:
|
||||
1. Validate inputs (serial_number, version_id, user_data)
|
||||
2. SERVER-SIDE: Calculate actual price using software_update logic:
|
||||
a. Get equipment data from serial_number
|
||||
b. Get version data from version_id
|
||||
c. Check upgrade path pricing (same logic as software_update.php lines 237-253)
|
||||
d. Check license validity (same logic as software_update.php lines 274-311)
|
||||
e. Calculate FINAL price server-side
|
||||
3. Verify price > 0 (free upgrades shouldn't reach payment API)
|
||||
4. Call Mollie API FIRST to get payment_id:
|
||||
$mollie->payments->create([
|
||||
'amount' => ['currency' => 'EUR', 'value' => $final_price],
|
||||
'description' => 'Software upgrade to version X',
|
||||
'redirectUrl' => 'https://site.com/softwaretool.php?payment_return=1&payment_id={payment_id}',
|
||||
'webhookUrl' => 'https://site.com/webhook_mollie.php', // NEW webhook for software upgrades
|
||||
'metadata' => ['order_id' => $mollie_payment_id] // for compatibility
|
||||
])
|
||||
5. Store transaction in DB with Mollie payment_id:
|
||||
INSERT INTO transactions (txn_id, payment_amount, payment_status, payer_email, first_name, last_name, address_*, account_id, ...)
|
||||
VALUES ($mollie_payment_id, $final_price, 0, ...) -- 0 = pending
|
||||
6. Store transaction item:
|
||||
INSERT INTO transactions_items (txn_id, item_id, item_price, item_quantity, item_options, ...)
|
||||
VALUES ($mollie_payment_id, $version_id, $final_price, 1, '{"serial_number":"...", "equipment_id":...}', ...)
|
||||
7. Return JSON: {checkout_url: $mollie_checkout_url, payment_id: $mollie_payment_id}
|
||||
```
|
||||
|
||||
**1.3 Create `/api/v2/get/payment.php`**
|
||||
```php
|
||||
<?php
|
||||
defined($security_key) or exit;
|
||||
|
||||
// GET endpoint for payment status retrieval
|
||||
// Input (URL): ?payment_id=tr_xxx
|
||||
// Output (JSON): {payment_id, serial_number, version_id, payment_status, price, currency, user_data}
|
||||
|
||||
//Connect to DB
|
||||
$pdo = dbConnect($dbname);
|
||||
|
||||
//NEW ARRAY
|
||||
$criterias = [];
|
||||
|
||||
//Check for $_GET variables
|
||||
if(isset($get_content) && $get_content!=''){
|
||||
$requests = explode("&", $get_content);
|
||||
foreach ($requests as $y){
|
||||
$v = explode("=", $y);
|
||||
$criterias[$v[0]] = $v[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Steps:
|
||||
1. Validate payment_id from URL
|
||||
2. Fetch transaction: SELECT * FROM transactions WHERE txn_id = ?
|
||||
3. Fetch transaction item: SELECT * FROM transactions_items WHERE txn_id = ?
|
||||
4. Parse item_options JSON to get serial_number, equipment_id
|
||||
5. Return JSON with payment details:
|
||||
{
|
||||
"payment_id": txn_id,
|
||||
"payment_status": payment_status, // 0=pending, 1=paid, 2=failed, 3=canceled
|
||||
"payment_amount": payment_amount,
|
||||
"serial_number": from item_options JSON,
|
||||
"equipment_id": from item_options JSON,
|
||||
"version_id": item_id,
|
||||
"payer_email": payer_email,
|
||||
"customer_name": first_name + " " + last_name
|
||||
}
|
||||
6. If not found, return error
|
||||
```
|
||||
|
||||
**1.4 Create NEW `webhook_mollie.php`**
|
||||
```php
|
||||
<?php
|
||||
// NEW FILE - Webhook for software upgrade payments
|
||||
// Based on structure from existing webhook.php from commerce product
|
||||
// Uses existing transaction API + invoice API + email system
|
||||
|
||||
require_once 'assets/config.php';
|
||||
require_once 'assets/functions.php';
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
//LOGIN TO API (same as commerce webhook.php)
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$data = json_encode(array("clientID" => clientID, "clientsecret" => clientsecret), JSON_UNESCAPED_UNICODE);
|
||||
$responses = ioAPIv2('/v2/authorization', $data,'');
|
||||
//Decode Payload
|
||||
if (!empty($responses)){$responses = json_decode($responses,true);}else{$responses = '400';}
|
||||
$clientsecret = $responses['token'];
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// BASEURL is required for invoice template
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$base_url = 'https://'.$_SERVER['SERVER_NAME'].'/';
|
||||
define('base_url', $base_url);
|
||||
|
||||
try {
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// Initialize the Mollie API library
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
require "initialize.php"; // Mollie initialization (from commerce webhook)
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
//Retrieve the payment's current state
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$payment = $mollie->payments->get($_POST["id"]);
|
||||
$orderId = $payment->metadata->order_id;
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// Update the transaction using existing API
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
if ($payment->isPaid() && !$payment->hasRefunds() && !$payment->hasChargebacks()) {
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// PAID - Update transaction status via API
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$payload = json_encode(array("txn_id" => $orderId, "payment_status" => 1), JSON_UNESCAPED_UNICODE);
|
||||
$transaction = ioAPIv2('/v2/transactions/',$payload,$clientsecret);
|
||||
$transaction = json_decode($transaction,true);
|
||||
|
||||
if ($transaction !== null && !empty($transaction)) {
|
||||
if(count($transaction) > 0) {
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// CREATE LICENSE for software upgrade
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$pdo = dbConnect($dbname);
|
||||
|
||||
// Fetch transaction items to find software upgrade
|
||||
$sql = 'SELECT * FROM transactions_items WHERE txn_id = ?';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$orderId]);
|
||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
foreach ($items as $item) {
|
||||
if (!empty($item['item_options'])) {
|
||||
$options = json_decode($item['item_options'], true);
|
||||
|
||||
// Check if this is a software upgrade (has serial_number and equipment_id)
|
||||
if (isset($options['serial_number']) && isset($options['equipment_id'])) {
|
||||
|
||||
// Check if license already exists for this transaction
|
||||
$sql = 'SELECT rowID FROM products_software_licenses WHERE transaction_id = ?';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$orderId]);
|
||||
$existing_license = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$existing_license) {
|
||||
// Generate unique license key
|
||||
$license_key = generateUniqueLicenseKey();
|
||||
|
||||
// Create license
|
||||
$sql = 'INSERT INTO products_software_licenses
|
||||
(license_key, equipment_id, license_type, status, start_at, expires_at, transaction_id, created, createdby)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
$license_key,
|
||||
$options['equipment_id'],
|
||||
'upgrade',
|
||||
1, // active
|
||||
date('Y-m-d H:i:s'),
|
||||
'2099-12-31 23:59:59', // effectively permanent
|
||||
$orderId,
|
||||
date('Y-m-d H:i:s'),
|
||||
'webhook' // created by webhook
|
||||
]);
|
||||
|
||||
// Update equipment.sw_version_license
|
||||
$sql = 'UPDATE equipment SET sw_version_license = ? WHERE rowID = ?';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$license_key, $options['equipment_id']]);
|
||||
|
||||
error_log("Webhook: License created for equipment_id: " . $options['equipment_id'] . ", license_key: " . $license_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
//Generate INVOICE RECORD via API
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$payload = json_encode(array("txn_id" => $transaction['transaction_id']), JSON_UNESCAPED_UNICODE);
|
||||
$invoice = ioAPIv2('/v2/invoice/',$payload,$clientsecret);
|
||||
$invoice = json_decode($invoice,true);
|
||||
|
||||
if ($invoice !== null && !empty($invoice)) {
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
//Generate INVOICE PDF and send email
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$invoice_cust = ioAPIv2('/v2/invoice/list=invoice&id='.$invoice['invoice_id'],'',$clientsecret);
|
||||
$invoice_cust = json_decode($invoice_cust,true);
|
||||
|
||||
// Determine invoice language
|
||||
if (!empty($invoice_cust['customer']['language'])) {
|
||||
$invoice_language = strtoupper($invoice_cust['customer']['language']);
|
||||
} elseif (!empty($invoice_cust['customer']['country']) && isset($available_languages[strtoupper($invoice_cust['customer']['country'])])) {
|
||||
$invoice_language = $available_languages[strtoupper($invoice_cust['customer']['country'])];
|
||||
} else {
|
||||
$invoice_language = 'US'; // Default fallback
|
||||
}
|
||||
|
||||
// Generate invoice HTML (using custom template for software upgrades)
|
||||
list($data,$customer_email,$order_id) = generateSoftwareInvoice($invoice_cust,$orderId,$invoice_language);
|
||||
|
||||
//CREATE PDF using DomPDF
|
||||
$dompdf->loadHtml($data);
|
||||
$dompdf->setPaper('A4', 'portrait');
|
||||
$dompdf->render();
|
||||
$subject = ($invoice_software_subject ?? 'Software Upgrade - Invoice: ').$order_id;
|
||||
$attachment = $dompdf->output();
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
//Send email via PHPMailer
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
send_mail_by_PHPMailer($customer_email, $subject, $data, $attachment, $subject);
|
||||
|
||||
if(invoice_bookkeeping){
|
||||
send_mail_by_PHPMailer(email_bookkeeping, $subject, $data, $attachment, $subject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} elseif ($payment->isOpen()) {
|
||||
// OPEN/PENDING (101)
|
||||
$payload = json_encode(array("txn_id" => $orderId, "payment_status" => 101), JSON_UNESCAPED_UNICODE);
|
||||
$transaction = ioAPIv2('/v2/transactions/',$payload,$clientsecret);
|
||||
|
||||
} elseif ($payment->isPending()) {
|
||||
// PENDING (101)
|
||||
$payload = json_encode(array("txn_id" => $orderId, "payment_status" => 101), JSON_UNESCAPED_UNICODE);
|
||||
$transaction = ioAPIv2('/v2/transactions/',$payload,$clientsecret);
|
||||
|
||||
} elseif ($payment->isFailed()) {
|
||||
// FAILED (102)
|
||||
$payload = json_encode(array("txn_id" => $orderId, "payment_status" => 102), JSON_UNESCAPED_UNICODE);
|
||||
$transaction = ioAPIv2('/v2/transactions/',$payload,$clientsecret);
|
||||
|
||||
} elseif ($payment->isExpired()) {
|
||||
// EXPIRED (103)
|
||||
$payload = json_encode(array("txn_id" => $orderId, "payment_status" => 103), JSON_UNESCAPED_UNICODE);
|
||||
$transaction = ioAPIv2('/v2/transactions/',$payload,$clientsecret);
|
||||
|
||||
} elseif ($payment->isCanceled()) {
|
||||
// CANCELED (999)
|
||||
$payload = json_encode(array("txn_id" => $orderId, "payment_status" => 999), JSON_UNESCAPED_UNICODE);
|
||||
$transaction = ioAPIv2('/v2/transactions/',$payload,$clientsecret);
|
||||
|
||||
} elseif ($payment->hasRefunds()) {
|
||||
// REFUNDED (1 + refund flag)
|
||||
$payload = json_encode(array("txn_id" => $orderId, "payment_status" => 1), JSON_UNESCAPED_UNICODE);
|
||||
$transaction = ioAPIv2('/v2/transactions/',$payload,$clientsecret);
|
||||
// TODO: Disable license on refund
|
||||
}
|
||||
|
||||
} catch (\Mollie\Api\Exceptions\ApiException $e) {
|
||||
error_log("Webhook API call failed: " . htmlspecialchars($e->getMessage()));
|
||||
http_response_code(500);
|
||||
echo "API call failed: " . htmlspecialchars($e->getMessage());
|
||||
} catch (Exception $e) {
|
||||
error_log("Webhook error: " . htmlspecialchars($e->getMessage()));
|
||||
http_response_code(500);
|
||||
}
|
||||
```
|
||||
|
||||
**Key Features (matching commerce webhook.php):**
|
||||
- ✅ Uses `/v2/transactions/` API for status updates
|
||||
- ✅ Uses `/v2/invoice/` API for invoice generation
|
||||
- ✅ Generates PDF invoice with DomPDF
|
||||
- ✅ Sends email via PHPMailer
|
||||
- ✅ Creates license for software upgrade
|
||||
- ✅ Uses same payment status codes (0, 1, 101, 102, 103, 999)
|
||||
- ✅ Handles refunds (TODO: disable license)
|
||||
- ✅ Multi-language invoice support
|
||||
- ✅ Sends to bookkeeping if configured
|
||||
|
||||
### Phase 2: Frontend Integration
|
||||
|
||||
**2.1 Modify `processPayment()` in softwaretool.js (lines 574-608)**
|
||||
```javascript
|
||||
async function processPayment(paymentData, option, modal) {
|
||||
try {
|
||||
progressBar("10", "Processing payment...", "#04AA6D");
|
||||
|
||||
// SECURITY: Only send serial_number and version_id
|
||||
// Server will calculate the price to prevent tampering
|
||||
const response = await fetch(link + "/v2/post/payment", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer " + document.getElementById("servicetoken").textContent
|
||||
},
|
||||
body: JSON.stringify({
|
||||
serial_number: deviceSerialNumber,
|
||||
version_id: option.version_id,
|
||||
user_data: paymentData // name, email, address only
|
||||
// REMOVED: price, currency - server calculates these
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.checkout_url) {
|
||||
await logCommunication(`Redirecting to payment provider`, 'sent');
|
||||
// Redirect to Mollie checkout
|
||||
window.location.href = result.checkout_url;
|
||||
} else {
|
||||
throw new Error(result.error || "Failed to create payment");
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
await logCommunication(`Payment error: ${error.message}`, 'error');
|
||||
progressBar("0", "Payment failed: " + error.message, "#ff6666");
|
||||
alert("Payment failed: " + error.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**2.2 Remove equipment_id tracking - NOT NEEDED**
|
||||
```javascript
|
||||
// SECURITY: We don't need to track equipment_id in frontend
|
||||
// The server will look it up from serial_number in the payment/create API
|
||||
// This prevents tampering with equipment_id
|
||||
```
|
||||
|
||||
**2.3 Add Serial Number Verification in `downloadAndInstallSoftware()` (lines 610-699)**
|
||||
```javascript
|
||||
async function downloadAndInstallSoftware(option) {
|
||||
// Check if we're returning from payment
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const paymentId = urlParams.get('payment_id');
|
||||
|
||||
if (paymentId) {
|
||||
// Verify serial number matches payment using GET /v2/get/payment
|
||||
const response = await fetch(link + `/v2/get/payment?payment_id=${paymentId}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Authorization": "Bearer " + document.getElementById("servicetoken").textContent
|
||||
}
|
||||
});
|
||||
|
||||
const paymentData = await response.json();
|
||||
|
||||
if (paymentData.serial_number !== deviceSerialNumber) {
|
||||
const confirmed = confirm(
|
||||
`WARNING: Different device detected!\n\n` +
|
||||
`License was created for device: ${paymentData.serial_number}\n` +
|
||||
`Currently connected device: ${deviceSerialNumber}\n\n` +
|
||||
`The license is already applied to the original device. ` +
|
||||
`Do you want to continue with this device anyway?`
|
||||
);
|
||||
|
||||
if (!confirmed) {
|
||||
progressBar("0", "Upload canceled by user", "#ff6666");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Continue with existing download logic...
|
||||
selectedSoftwareUrl = option.source;
|
||||
// ... rest of function unchanged
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: Serial number verification uses existing GET /v2/get/payment endpoint (no separate verify endpoint needed)
|
||||
|
||||
### Phase 3: Return URL Handling
|
||||
|
||||
**3.1 Modify `softwaretool.php` to detect return from payment**
|
||||
```php
|
||||
// Add near top of softwaretool.php (after includes, before $view)
|
||||
<?php
|
||||
$payment_return = isset($_GET['payment_id']) ? $_GET['payment_id'] : null;
|
||||
|
||||
if ($payment_return) {
|
||||
// Optionally fetch payment status via GET /v2/get/payment
|
||||
// and show appropriate message banner at top of page
|
||||
// "Payment successful! Please reconnect your device to continue."
|
||||
// User will then click "Connect Device" button
|
||||
// After connection, checkSoftwareAvailability() will run
|
||||
// License will be found via existing logic, price will be 0.00
|
||||
}
|
||||
?>
|
||||
```
|
||||
|
||||
**3.2 Optional: Auto-trigger device connection after payment return**
|
||||
```javascript
|
||||
// In softwaretool.js, check URL on page load
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.has('payment_id')) {
|
||||
// Show message: "Payment successful! Please reconnect your device."
|
||||
// Optionally auto-show device connection UI
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Phase 4: Testing Strategy
|
||||
|
||||
**4.1 DEBUG Mode Testing (Complete Simulation)**
|
||||
```php
|
||||
// In /api/v2/post/payment.php, check if DEBUG mode
|
||||
if (defined('debug') && debug) {
|
||||
// FULL SIMULATION: No Mollie API connection, no device connection
|
||||
$fake_payment_id = 'DEBUG_' . uniqid();
|
||||
|
||||
// 1. Store transaction with status 0 (pending)
|
||||
$sql = 'INSERT INTO transactions
|
||||
(txn_id, payment_amount, payment_status, payer_email, first_name, last_name,
|
||||
address_street, address_city, address_state, address_zip, address_country, account_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
$fake_payment_id,
|
||||
$final_price,
|
||||
0, // 0 = pending
|
||||
$post_content['user_data']['email'],
|
||||
$post_content['user_data']['first_name'] ?? '',
|
||||
$post_content['user_data']['last_name'] ?? '',
|
||||
$post_content['user_data']['address_street'] ?? '',
|
||||
$post_content['user_data']['address_city'] ?? '',
|
||||
$post_content['user_data']['address_state'] ?? '',
|
||||
$post_content['user_data']['address_zip'] ?? '',
|
||||
$post_content['user_data']['address_country'] ?? '',
|
||||
$post_content['serial_number'] // store serial number in account_id
|
||||
]);
|
||||
|
||||
// 2. Store transaction item
|
||||
$item_options = json_encode([
|
||||
'serial_number' => $post_content['serial_number'],
|
||||
'equipment_id' => $equipment_id,
|
||||
'hw_version' => $hw_version
|
||||
]);
|
||||
$sql = 'INSERT INTO transactions_items
|
||||
(txn_id, item_id, item_price, item_quantity, item_options)
|
||||
VALUES (?, ?, ?, ?, ?)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
$fake_payment_id,
|
||||
$post_content['version_id'],
|
||||
$final_price,
|
||||
1,
|
||||
$item_options
|
||||
]);
|
||||
|
||||
// 3. Immediately simulate webhook success (update status to paid + create license)
|
||||
$sql = 'UPDATE transactions SET payment_status = 1 WHERE txn_id = ?'; // 1 = paid
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$fake_payment_id]);
|
||||
|
||||
// 4. Create license
|
||||
$license_key = generateUniqueLicenseKey();
|
||||
$sql = 'INSERT INTO products_software_licenses
|
||||
(license_key, equipment_id, license_type, status, start_at, expires_at, transaction_id, created, createdby)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
$license_key,
|
||||
$equipment_id,
|
||||
'upgrade',
|
||||
1,
|
||||
date('Y-m-d H:i:s'),
|
||||
'2099-12-31 23:59:59',
|
||||
$fake_payment_id,
|
||||
date('Y-m-d H:i:s'),
|
||||
$username
|
||||
]);
|
||||
|
||||
// 5. Update equipment.sw_version_license
|
||||
$sql = 'UPDATE equipment SET sw_version_license = ? WHERE rowID = ?';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$license_key, $equipment_id]);
|
||||
|
||||
// 6. Return fake checkout URL that redirects immediately
|
||||
$messages = [
|
||||
'checkout_url' => 'https://'.$_SERVER['SERVER_NAME'].'/softwaretool.php?payment_return=1&payment_id=' . $fake_payment_id,
|
||||
'payment_id' => $fake_payment_id
|
||||
];
|
||||
echo json_encode($messages);
|
||||
exit;
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: In DEBUG mode, the entire payment + license creation flow is simulated without:
|
||||
- Calling Mollie API
|
||||
- Requiring physical device connection (works with DEBUG mode mock device data in softwaretool.js)
|
||||
|
||||
**4.2 Mollie Sandbox Testing**
|
||||
1. Use Mollie test API key
|
||||
2. Test successful payment flow
|
||||
3. Test failed payment flow
|
||||
4. Test canceled payment flow
|
||||
5. Test webhook delivery
|
||||
6. Test license creation
|
||||
|
||||
**4.3 Serial Number Mismatch Testing**
|
||||
1. Complete payment with device A (SN: 22110095)
|
||||
2. Disconnect device A
|
||||
3. Connect device B (different SN)
|
||||
4. Verify warning appears
|
||||
5. Verify license was created for device A
|
||||
|
||||
---
|
||||
|
||||
## Critical Files to Modify
|
||||
|
||||
### New Files
|
||||
- `/api/v2/post/payment.php` - Payment creation (POST)
|
||||
- `/api/v2/get/payment.php` - Payment status retrieval (GET)
|
||||
- `/webhook_mollie.php` - Mollie webhook handler for software upgrades (based on existing webhook.php structure)
|
||||
- `generateSoftwareInvoice()` function in `/assets/functions.php` - Invoice template for software upgrades
|
||||
|
||||
### Modified Files
|
||||
- `/assets/softwaretool.js`:
|
||||
- `processPayment()` (lines 574-608) - Call POST /v2/post/payment instead of simulation
|
||||
- `downloadAndInstallSoftware()` (lines 610-699) - Add serial number verification using GET /v2/get/payment
|
||||
- Add payment return detection on page load (optional)
|
||||
- `/softwaretool.php`:
|
||||
- Add payment return URL detection (check for ?payment_id=X)
|
||||
- Optionally show success message banner after payment
|
||||
- `/api/v2/get/software_update.php`:
|
||||
- **No changes needed** (existing license logic at lines 274-311 works perfectly!)
|
||||
|
||||
### Database & Helper Functions
|
||||
- No new tables needed (using existing `transactions` and `transactions_items`)
|
||||
- Add helper function `generateUniqueLicenseKey()` in `assets/functions.php`
|
||||
- Payment status codes already defined in existing webhook.php (0, 1, 101, 102, 103, 999)
|
||||
|
||||
---
|
||||
|
||||
## Security Architecture Summary
|
||||
|
||||
### ✅ **SECURE APPROACH: Server-Side Price Validation**
|
||||
|
||||
**Frontend sends:**
|
||||
- `serial_number` (from connected device)
|
||||
- `version_id` (which version they want)
|
||||
- `user_data` (name, email, address)
|
||||
|
||||
**Backend does:**
|
||||
1. Look up equipment from `serial_number`
|
||||
2. Look up version from `version_id`
|
||||
3. **Calculate actual price using same logic as software_update.php**:
|
||||
- Check upgrade path pricing (lines 244-260)
|
||||
- Check if license exists and reduces price (lines 274-311)
|
||||
- Get final server-calculated price
|
||||
4. Verify price > 0 (reject free upgrades)
|
||||
5. Create Mollie payment with **SERVER-CALCULATED price**
|
||||
6. Store pending payment with correct price
|
||||
|
||||
### ❌ **INSECURE APPROACH: Never Do This**
|
||||
```javascript
|
||||
// WRONG - User can modify price in browser console!
|
||||
body: JSON.stringify({
|
||||
serial_number: deviceSerialNumber,
|
||||
version_id: option.version_id,
|
||||
price: 0.01, // <-- Tampered from 49.99!
|
||||
currency: "EUR"
|
||||
})
|
||||
```
|
||||
|
||||
**Why this is dangerous:**
|
||||
- User can open browser console
|
||||
- Change `option.price = 0.01` before payment
|
||||
- Backend trusts this value = user pays 1 cent for €49.99 upgrade
|
||||
|
||||
### ✅ **CORRECT APPROACH**
|
||||
```javascript
|
||||
// SECURE - Only send identifiers, server calculates price
|
||||
body: JSON.stringify({
|
||||
serial_number: deviceSerialNumber, // Who is buying
|
||||
version_id: option.version_id, // What they want
|
||||
user_data: paymentData // Customer info
|
||||
// NO PRICE - server calculates it!
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration & Requirements (USER CONFIRMED)
|
||||
|
||||
1. ✅ **Mollie API Credentials**: User has Mollie info - will be added as constants in `config.php`
|
||||
- `MOLLIE_API_KEY_TEST` (for sandbox)
|
||||
- `MOLLIE_API_KEY_LIVE` (for production)
|
||||
2. ✅ **License Duration**: `expires_at = '2099-12-31 23:59:59'` (effectively permanent until 2099)
|
||||
3. ✅ **Multiple Devices**: One license per device (license linked to specific equipment_id)
|
||||
4. ✅ **DEBUG Mode**: Full payment process simulation without Mollie connection AND without device connection
|
||||
5. ✅ **Transaction Logging**: Use existing ecommerce transaction APIs:
|
||||
- `transactions` table - main transaction record
|
||||
- `transaction_items` table - line items (software upgrade details)
|
||||
|
||||
---
|
||||
|
||||
## Next Steps After Plan Approval
|
||||
|
||||
1. ✅ Add Mollie constants to `config.php`:
|
||||
```php
|
||||
define('MOLLIE_API_KEY_TEST', 'test_xxxxx'); // User will provide
|
||||
define('MOLLIE_API_KEY_LIVE', 'live_xxxxx'); // User will provide
|
||||
```
|
||||
2. ✅ Mollie PHP SDK already installed (used by existing webhook.php)
|
||||
3. ✅ No database changes needed (using existing `transactions` and `transactions_items` tables)
|
||||
4. Create helper functions in `assets/functions.php`:
|
||||
- `generateUniqueLicenseKey()` - Generate unique license keys
|
||||
- `generateSoftwareInvoice()` - Generate HTML invoice for software upgrades (based on existing generateInvoice())
|
||||
5. ✅ Payment status codes already defined in existing webhook.php
|
||||
6. Implement NEW backend files:
|
||||
- `/api/v2/post/payment.php` (with DEBUG mode support)
|
||||
- `/api/v2/get/payment.php`
|
||||
- `/webhook_mollie.php` (based on existing webhook.php structure)
|
||||
8. Modify frontend JavaScript in `/assets/softwaretool.js`:
|
||||
- Update `processPayment()` to call POST /v2/post/payment
|
||||
- Add serial number verification in `downloadAndInstallSoftware()`
|
||||
9. Modify `/softwaretool.php` to detect payment return
|
||||
10. Test in DEBUG mode (full simulation without Mollie or device)
|
||||
11. Test with Mollie sandbox
|
||||
12. Deploy to production
|
||||
|
||||
@@ -247,10 +247,13 @@ if (!empty($post_content['sn']) && !empty($post_content['testdetails'])) {
|
||||
$sw_version = substr($sw_version, 0, -4);
|
||||
}
|
||||
|
||||
// Translate hardware version to standardized format
|
||||
$translated_hw_version = translateDeviceHardwareVersion($hw_version);
|
||||
|
||||
//Update Equipment record
|
||||
$sql = "UPDATE equipment SET hw_version = ?, sw_version = ? $whereclause";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$hw_version,$sw_version]);
|
||||
$stmt->execute([$translated_hw_version,$sw_version]);
|
||||
}
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
//Update equipment status ++++++++++++++++++++++++++
|
||||
|
||||
@@ -46,7 +46,8 @@ if(isset($get_content) && $get_content!=''){
|
||||
$clause .= ' AND e.serialnumber = :'.$v[0];
|
||||
}
|
||||
elseif ($v[0] == 'hw_version') {
|
||||
//build up search
|
||||
//build up search - translate hardware version for comparison
|
||||
$criterias[$v[0]] = translateDeviceHardwareVersion($criterias[$v[0]]);
|
||||
$clause .= ' AND ps.hw_version = :'.$v[0];
|
||||
}
|
||||
elseif ($v[0] == 'status') {
|
||||
@@ -152,9 +153,11 @@ if (!isset($criterias['productrowid']) && isset($criterias['sn']) && $criterias[
|
||||
|
||||
//check if current version is send and update the equipment record
|
||||
if(isset($criterias['hw_version']) && $criterias['hw_version'] !=''){
|
||||
// Translate hardware version to standardized format
|
||||
$translated_hw_version = translateDeviceHardwareVersion($criterias['hw_version']);
|
||||
$sql = 'UPDATE equipment SET hw_version = ?, updatedby = ? WHERE serialnumber = ? ';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$criterias['hw_version'],$username,$criterias['sn']]);
|
||||
$stmt->execute([$translated_hw_version,$username,$criterias['sn']]);
|
||||
}
|
||||
|
||||
//GET PRODUCTCODE, SW_VERSION_UPGRADE, HW_VERSION from equipment SN
|
||||
|
||||
@@ -48,7 +48,8 @@ if(isset($get_content) && $get_content!=''){
|
||||
$clause .= ' AND ps.status = :'.$v[0];
|
||||
}
|
||||
elseif ($v[0] == 'hw_version') {
|
||||
//build up search
|
||||
//build up search - translate hardware version for comparison
|
||||
$criterias[$v[0]] = translateDeviceHardwareVersion($criterias[$v[0]]);
|
||||
$clause .= ' AND ps.hw_version = :'.$v[0];
|
||||
}
|
||||
else {//create clause
|
||||
@@ -149,9 +150,11 @@ if (!isset($criterias['productrowid']) && isset($criterias['sn']) && $criterias[
|
||||
|
||||
//check if current version is send and update the equipment record
|
||||
if(isset($criterias['hw_version']) && $criterias['hw_version'] !=''){
|
||||
// Translate hardware version to standardized format
|
||||
$translated_hw_version = translateDeviceHardwareVersion($criterias['hw_version']);
|
||||
$sql = 'UPDATE equipment SET hw_version = ?, updatedby = ? WHERE serialnumber = ? ';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$criterias['hw_version'],$username,$criterias['sn']]);
|
||||
$stmt->execute([$translated_hw_version,$username,$criterias['sn']]);
|
||||
}
|
||||
|
||||
//GET PRODUCTCODE, SW_VERSION_UPGRADE, HW_VERSION from equipment SN
|
||||
|
||||
@@ -14,6 +14,7 @@ $pdo = dbConnect($dbname);
|
||||
//NEW ARRAY
|
||||
$criterias = [];
|
||||
$clause = '';
|
||||
$debug = [];
|
||||
|
||||
//Check for $_GET variables and build up clause
|
||||
if(isset($get_content) && $get_content!=''){
|
||||
@@ -27,6 +28,11 @@ if(isset($get_content) && $get_content!=''){
|
||||
}
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
$debug['request_parameters'] = $criterias;
|
||||
$debug['timestamp'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
// IF SN IS PROVIDED, CHECK FOR AVAILABLE UPGRADES
|
||||
if (isset($criterias['sn']) && $criterias['sn'] != ''){
|
||||
|
||||
@@ -42,9 +48,11 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
|
||||
|
||||
//check if current hw_version is send and update the equipment record
|
||||
if(isset($criterias['hw_version']) && $criterias['hw_version'] !=''){
|
||||
// Translate hardware version to standardized format
|
||||
$translated_hw_version = translateDeviceHardwareVersion($criterias['hw_version']);
|
||||
$sql = 'UPDATE equipment SET hw_version = ?, updatedby = ? WHERE serialnumber = ? ';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$criterias['hw_version'],$username,$criterias['sn']]);
|
||||
$stmt->execute([$translated_hw_version,$username,$criterias['sn']]);
|
||||
}
|
||||
|
||||
//GET EQUIPMENT AND PRODUCT DATA BASED ON SERIAL NUMBER
|
||||
@@ -72,8 +80,46 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
|
||||
$sw_version_license = $equipment_data['sw_version_license'];
|
||||
$equipment_rowid = $equipment_data['equipment_rowid'];
|
||||
|
||||
//GET ALL DATA: active assignments, version details, and upgrade paths
|
||||
//Filter on active status, hw_version compatibility, and exclude current version
|
||||
if (debug) {
|
||||
$debug['equipment_data'] = [
|
||||
'product_rowid' => $product_rowid,
|
||||
'productcode' => $productcode,
|
||||
'current_sw_version_raw' => $current_sw_version,
|
||||
'hw_version' => $hw_version
|
||||
];
|
||||
}
|
||||
|
||||
// Normalize software version for comparison (lowercase, trim leading zeros)
|
||||
$current_sw_version = strtolower(ltrim($current_sw_version, '0'));
|
||||
|
||||
// Translate incoming hw_version parameter for comparison if provided
|
||||
$comparison_hw_version = $hw_version;
|
||||
$hw_version_from_request = null;
|
||||
if(isset($criterias['hw_version']) && $criterias['hw_version'] !=''){
|
||||
$hw_version_from_request = $criterias['hw_version'];
|
||||
$comparison_hw_version = translateDeviceHardwareVersion($criterias['hw_version']);
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
$debug['normalized_data'] = [
|
||||
'current_sw_version' => $current_sw_version,
|
||||
'hw_version_from_request' => $hw_version_from_request,
|
||||
'comparison_hw_version' => $comparison_hw_version,
|
||||
'hw_version_valid' => ($comparison_hw_version !== '')
|
||||
];
|
||||
}
|
||||
|
||||
// Check if hardware version is invalid (all zeros)
|
||||
if ($hw_version_from_request && $comparison_hw_version === '') {
|
||||
$messages = ["software_available" => "error", "error" => "Invalid hardware version (000000) - device may not be properly initialized"];
|
||||
if (debug) {
|
||||
$messages['debug'] = $debug;
|
||||
}
|
||||
echo json_encode($messages, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
//GET ALL ACTIVE SOFTWARE ASSIGNMENTS for this product with matching HW version
|
||||
$sql = 'SELECT
|
||||
psv.rowID as version_id,
|
||||
psv.version,
|
||||
@@ -82,59 +128,121 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
|
||||
psv.mandatory,
|
||||
psv.latest,
|
||||
psv.hw_version,
|
||||
psv.file_path,
|
||||
pup.price,
|
||||
pup.currency,
|
||||
pup.from_version_id,
|
||||
from_ver.version as from_version
|
||||
psv.file_path
|
||||
FROM products_software_assignment psa
|
||||
JOIN products_software_versions psv ON psa.software_version_id = psv.rowID
|
||||
LEFT JOIN products_software_upgrade_paths pup ON pup.to_version_id = psv.rowID AND pup.is_active = 1
|
||||
LEFT JOIN products_software_versions from_ver ON pup.from_version_id = from_ver.rowID
|
||||
WHERE psa.product_id = ?
|
||||
AND psa.status = 1
|
||||
AND (psv.hw_version = ? OR psv.hw_version IS NULL OR psv.hw_version = "")
|
||||
AND psv.version != ?';
|
||||
AND (psv.hw_version = ? OR psv.hw_version IS NULL OR psv.hw_version = "")';
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$product_rowid, $hw_version, $current_sw_version ?? '']);
|
||||
$stmt->execute([$product_rowid, $comparison_hw_version]);
|
||||
$versions = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (debug) {
|
||||
$debug['active_assignments'] = [
|
||||
'count' => count($versions),
|
||||
'versions' => array_map(function($v) {
|
||||
return [
|
||||
'version_id' => $v['version_id'],
|
||||
'version' => $v['version'],
|
||||
'name' => $v['name'],
|
||||
'hw_version' => $v['hw_version'],
|
||||
'latest' => $v['latest']
|
||||
];
|
||||
}, $versions)
|
||||
];
|
||||
}
|
||||
|
||||
if (empty($versions)) {
|
||||
// No versions available
|
||||
$software_available = "no";
|
||||
if (debug) {
|
||||
$debug['decision'] = 'No active software assignments found';
|
||||
}
|
||||
} else {
|
||||
$has_priced_options = false;
|
||||
$has_latest_version_different = false;
|
||||
|
||||
if (debug) {
|
||||
$debug['version_checks'] = [];
|
||||
}
|
||||
|
||||
foreach ($versions as $version) {
|
||||
//Normalize version for comparison (lowercase, trim leading zeros)
|
||||
$normalized_version = strtolower(ltrim($version['version'], '0'));
|
||||
|
||||
//Skip if this is the current version
|
||||
if ($current_sw_version && $normalized_version == $current_sw_version) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//Check if this version should be shown (same logic as software_update)
|
||||
$show_version = false;
|
||||
$final_price = '0.00';
|
||||
$decision_reason = '';
|
||||
|
||||
if (debug) {
|
||||
$version_check = [
|
||||
'version' => $version['version'],
|
||||
'name' => $version['name'],
|
||||
'normalized' => $normalized_version,
|
||||
'is_current' => ($current_sw_version && $normalized_version == $current_sw_version)
|
||||
];
|
||||
}
|
||||
|
||||
if (!$current_sw_version || $current_sw_version == '') {
|
||||
//No current version - show all
|
||||
$show_version = true;
|
||||
} elseif ($version['from_version'] == $current_sw_version) {
|
||||
//Upgrade path exists from current version
|
||||
$show_version = true;
|
||||
$decision_reason = 'No current version - showing all';
|
||||
} else {
|
||||
//Check if any upgrade paths exist for this version
|
||||
//Check if this version is part of ANY upgrade path system (either FROM or TO)
|
||||
$sql = 'SELECT COUNT(*) as path_count
|
||||
FROM products_software_upgrade_paths
|
||||
WHERE to_version_id = ? AND is_active = 1';
|
||||
WHERE (to_version_id = ? OR from_version_id = ?) AND is_active = 1';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$version['version_id']]);
|
||||
$stmt->execute([$version['version_id'], $version['version_id']]);
|
||||
$path_check = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($path_check['path_count'] == 0) {
|
||||
//No paths exist at all - show as free upgrade
|
||||
$show_version = true;
|
||||
if (debug) {
|
||||
$version_check['path_count'] = $path_check['path_count'];
|
||||
}
|
||||
|
||||
if ($path_check['path_count'] == 0) {
|
||||
//Not part of any upgrade path system - show as free upgrade
|
||||
$show_version = true;
|
||||
$decision_reason = 'No upgrade paths defined - showing as free';
|
||||
} else {
|
||||
//Part of an upgrade path system
|
||||
//Only show if there's an explicit path FROM current version TO this version
|
||||
$sql = 'SELECT pup.price, pup.currency
|
||||
FROM products_software_upgrade_paths pup
|
||||
JOIN products_software_versions from_ver ON pup.from_version_id = from_ver.rowID
|
||||
WHERE pup.to_version_id = ?
|
||||
AND LOWER(TRIM(LEADING "0" FROM from_ver.version)) = ?
|
||||
AND pup.is_active = 1';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$version['version_id'], $current_sw_version]);
|
||||
$upgrade_path = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($upgrade_path) {
|
||||
//Valid upgrade path found FROM current version
|
||||
$show_version = true;
|
||||
$final_price = $upgrade_path['price'] ?? '0.00';
|
||||
$decision_reason = 'Found upgrade path from current with price: ' . $final_price;
|
||||
} else {
|
||||
$decision_reason = 'Has upgrade paths but none from current version';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
$version_check['show_version'] = $show_version;
|
||||
$version_check['reason'] = $decision_reason;
|
||||
}
|
||||
|
||||
if ($show_version) {
|
||||
//Check if there's a valid license for this upgrade
|
||||
$final_price = $version['price'] ?? '0.00';
|
||||
|
||||
if ($final_price > 0 && $sw_version_license) {
|
||||
//Check if the license is valid
|
||||
$sql = 'SELECT status, start_at, expires_at
|
||||
@@ -162,9 +270,19 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
|
||||
}
|
||||
|
||||
// Check if there's a "latest" flagged version that's different from current
|
||||
if ($version['latest'] == 1 && $version['version'] != $current_sw_version) {
|
||||
if ($version['latest'] == 1 && $normalized_version != $current_sw_version) {
|
||||
$has_latest_version_different = true;
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
$version_check['final_price'] = $final_price;
|
||||
$version_check['has_priced_option'] = ($final_price > 0);
|
||||
$version_check['is_latest_different'] = ($version['latest'] == 1 && $normalized_version != $current_sw_version);
|
||||
}
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
$debug['version_checks'][] = $version_check;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,14 +292,30 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
|
||||
// 3. Default -> "no"
|
||||
if ($has_priced_options) {
|
||||
$software_available = "yes";
|
||||
$availability_reason = "Has priced upgrade options available";
|
||||
} elseif ($has_latest_version_different) {
|
||||
$software_available = "yes";
|
||||
$availability_reason = "Has free latest version available";
|
||||
} else {
|
||||
$software_available = "no";
|
||||
$availability_reason = "No upgrades available or already on latest";
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
$debug['final_decision'] = [
|
||||
'has_priced_options' => $has_priced_options,
|
||||
'has_latest_version_different' => $has_latest_version_different,
|
||||
'software_available' => $software_available,
|
||||
'reason' => $availability_reason
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$messages = ["software_available" => $software_available];
|
||||
|
||||
if (debug) {
|
||||
debuglog(json_encode($debug));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$messages = ["error" => "No serialnumber found"];
|
||||
|
||||
@@ -9,8 +9,6 @@ defined($security_key) or exit;
|
||||
//Connect to DB
|
||||
$pdo = dbConnect($dbname);
|
||||
|
||||
var_dump($_GET);
|
||||
|
||||
// STEP 1: Validate token parameter exists
|
||||
if (!isset($_GET['token']) || $_GET['token'] == '') {
|
||||
http_response_code(400);
|
||||
@@ -135,8 +133,10 @@ if ($assignment['assigned'] == 0) {
|
||||
}
|
||||
|
||||
// STEP 6: Hardware version compatibility
|
||||
if ($version['hw_version'] && $version['hw_version'] != '' && $equipment['hw_version']) {
|
||||
if ($version['hw_version'] != $equipment['hw_version']) {
|
||||
// Only check if version has hw_version requirement (not NULL or empty)
|
||||
// Match logic from software_update.php line 103
|
||||
if ($version['hw_version'] && $version['hw_version'] != '') {
|
||||
if ($equipment['hw_version'] && $version['hw_version'] != $equipment['hw_version']) {
|
||||
http_response_code(403);
|
||||
log_download([
|
||||
'user_id' => $user_data['id'],
|
||||
|
||||
@@ -13,6 +13,7 @@ $pdo = dbConnect($dbname);
|
||||
//NEW ARRAY
|
||||
$criterias = [];
|
||||
$clause = '';
|
||||
$debug = [];
|
||||
|
||||
//Check for $_GET variables and build up clause
|
||||
if(isset($get_content) && $get_content!=''){
|
||||
@@ -26,6 +27,11 @@ if(isset($get_content) && $get_content!=''){
|
||||
}
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
$debug['request_parameters'] = $criterias;
|
||||
$debug['timestamp'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
// IF SN IS PROVIDED, HANDLE UPGRADE OPTIONS
|
||||
if (isset($criterias['sn']) && $criterias['sn'] != ''){
|
||||
|
||||
@@ -41,9 +47,11 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
|
||||
|
||||
//check if current hw_version is send and update the equipment record
|
||||
if(isset($criterias['hw_version']) && $criterias['hw_version'] !=''){
|
||||
// Translate hardware version to standardized format
|
||||
$translated_hw_version = translateDeviceHardwareVersion($criterias['hw_version']);
|
||||
$sql = 'UPDATE equipment SET hw_version = ?, updatedby = ? WHERE serialnumber = ? ';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$criterias['hw_version'],$username,$criterias['sn']]);
|
||||
$stmt->execute([$translated_hw_version,$username,$criterias['sn']]);
|
||||
}
|
||||
|
||||
//GET EQUIPMENT AND PRODUCT DATA BASED ON SERIAL NUMBER
|
||||
@@ -71,8 +79,47 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
|
||||
$sw_version_license = $equipment_data['sw_version_license'];
|
||||
$equipment_rowid = $equipment_data['equipment_rowid'];
|
||||
|
||||
//GET ALL DATA: active assignments, version details, and upgrade paths
|
||||
//Filter on active status and hw_version compatibility
|
||||
if (debug) {
|
||||
$debug['equipment_data'] = [
|
||||
'product_rowid' => $product_rowid,
|
||||
'productcode' => $productcode,
|
||||
'current_sw_version_raw' => $current_sw_version,
|
||||
'hw_version' => $hw_version,
|
||||
'sw_version_license' => $sw_version_license
|
||||
];
|
||||
}
|
||||
|
||||
// Normalize software version for comparison (lowercase, trim leading zeros)
|
||||
$current_sw_version = strtolower(ltrim($current_sw_version, '0'));
|
||||
|
||||
// Translate incoming hw_version parameter for comparison if provided
|
||||
$comparison_hw_version = $hw_version;
|
||||
$hw_version_from_request = null;
|
||||
if(isset($criterias['hw_version']) && $criterias['hw_version'] !=''){
|
||||
$hw_version_from_request = $criterias['hw_version'];
|
||||
$comparison_hw_version = translateDeviceHardwareVersion($criterias['hw_version']);
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
$debug['normalized_data'] = [
|
||||
'current_sw_version' => $current_sw_version,
|
||||
'hw_version_from_request' => $hw_version_from_request,
|
||||
'comparison_hw_version' => $comparison_hw_version,
|
||||
'hw_version_valid' => ($comparison_hw_version !== '')
|
||||
];
|
||||
}
|
||||
|
||||
// Check if hardware version is invalid (all zeros)
|
||||
if ($hw_version_from_request && $comparison_hw_version === '') {
|
||||
$messages = ["error" => "Invalid hardware version (000000) - device may not be properly initialized"];
|
||||
if (debug) {
|
||||
$messages['debug'] = $debug;
|
||||
}
|
||||
echo json_encode($messages, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
//GET ALL ACTIVE SOFTWARE ASSIGNMENTS for this product with matching HW version
|
||||
$sql = 'SELECT
|
||||
psv.rowID as version_id,
|
||||
psv.version,
|
||||
@@ -81,60 +128,157 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
|
||||
psv.mandatory,
|
||||
psv.latest,
|
||||
psv.hw_version,
|
||||
psv.file_path,
|
||||
pup.price,
|
||||
pup.currency,
|
||||
pup.from_version_id,
|
||||
from_ver.version as from_version
|
||||
psv.file_path
|
||||
FROM products_software_assignment psa
|
||||
JOIN products_software_versions psv ON psa.software_version_id = psv.rowID
|
||||
LEFT JOIN products_software_upgrade_paths pup ON pup.to_version_id = psv.rowID AND pup.is_active = 1
|
||||
LEFT JOIN products_software_versions from_ver ON pup.from_version_id = from_ver.rowID
|
||||
WHERE psa.product_id = ?
|
||||
AND psa.status = 1
|
||||
AND (psv.hw_version = ? OR psv.hw_version IS NULL OR psv.hw_version = "")
|
||||
AND (? IS NULL OR ? = "" OR psv.version != ?)';
|
||||
AND (psv.hw_version = ? OR psv.hw_version IS NULL OR psv.hw_version = "")';
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$product_rowid, $hw_version, $current_sw_version, $current_sw_version, $current_sw_version]);
|
||||
$stmt->execute([$product_rowid, $comparison_hw_version]);
|
||||
$versions = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (debug) {
|
||||
$debug['active_assignments'] = [
|
||||
'count' => count($versions),
|
||||
'versions' => array_map(function($v) {
|
||||
return [
|
||||
'version_id' => $v['version_id'],
|
||||
'version' => $v['version'],
|
||||
'name' => $v['name'],
|
||||
'hw_version' => $v['hw_version'],
|
||||
'latest' => $v['latest']
|
||||
];
|
||||
}, $versions)
|
||||
];
|
||||
}
|
||||
|
||||
if (empty($versions)) {
|
||||
$messages = ["error" => "No active software assignments found for product"];
|
||||
if (debug) {
|
||||
$messages['debug'] = $debug;
|
||||
}
|
||||
} else {
|
||||
foreach ($versions as $version) {
|
||||
//Check if this version should be shown:
|
||||
//1. If there's a matching upgrade path from current version, show it
|
||||
//2. If no current version exists, show all
|
||||
//3. If there's no upgrade path but also no paths exist for this version at all, show it (free upgrade)
|
||||
// First check if current version has paid upgrade paths FROM it
|
||||
$has_paid_upgrade_from_current = false;
|
||||
if ($current_sw_version) {
|
||||
$sql = 'SELECT COUNT(*) as paid_count
|
||||
FROM products_software_upgrade_paths pup
|
||||
JOIN products_software_versions from_ver ON pup.from_version_id = from_ver.rowID
|
||||
WHERE LOWER(TRIM(LEADING "0" FROM from_ver.version)) = ?
|
||||
AND pup.price > 0
|
||||
AND pup.is_active = 1';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$current_sw_version]);
|
||||
$paid_check = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$has_paid_upgrade_from_current = ($paid_check['paid_count'] > 0);
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
$debug['has_paid_upgrade_from_current'] = $has_paid_upgrade_from_current;
|
||||
$debug['version_decisions'] = [];
|
||||
}
|
||||
|
||||
foreach ($versions as $version) {
|
||||
//Normalize version for comparison (lowercase, trim leading zeros)
|
||||
$normalized_version = strtolower(ltrim($version['version'], '0'));
|
||||
|
||||
$is_current_version = ($current_sw_version && $normalized_version == $current_sw_version);
|
||||
|
||||
//All versions with matching HW are potential upgrades
|
||||
$show_version = false;
|
||||
$final_price = '0.00';
|
||||
$final_currency = '';
|
||||
$is_current = false;
|
||||
$decision_reason = '';
|
||||
|
||||
if (debug) {
|
||||
$version_debug = [
|
||||
'version' => $version['version'],
|
||||
'name' => $version['name'],
|
||||
'normalized_version' => $normalized_version,
|
||||
'is_current_version' => $is_current_version,
|
||||
'latest' => $version['latest']
|
||||
];
|
||||
}
|
||||
|
||||
if (!$current_sw_version || $current_sw_version == '') {
|
||||
//No current version - show all
|
||||
$show_version = true;
|
||||
} elseif ($version['from_version'] == $current_sw_version) {
|
||||
//Upgrade path exists from current version
|
||||
//No current version - show all as free upgrades
|
||||
if (!$is_current_version) {
|
||||
$show_version = true;
|
||||
$decision_reason = 'No current version stored - showing as free upgrade';
|
||||
} else {
|
||||
//Check if any upgrade paths exist for this version
|
||||
$decision_reason = 'Skipped - is current version but no upgrades scenario';
|
||||
}
|
||||
} else {
|
||||
//Check if this is the current version and should be shown as disabled
|
||||
if ($is_current_version && $has_paid_upgrade_from_current && $version['latest'] == 1) {
|
||||
//Show current version as disabled only if it's the latest AND there's a paid upgrade available
|
||||
$show_version = true;
|
||||
$is_current = true;
|
||||
$final_price = '0.00';
|
||||
$final_currency = '';
|
||||
$decision_reason = 'Showing as CURRENT - is latest version with paid upgrade available';
|
||||
} else if ($is_current_version && !($has_paid_upgrade_from_current && $version['latest'] == 1)) {
|
||||
$decision_reason = 'Skipped - is current version but not (latest + has_paid_upgrade)';
|
||||
} else if (!$is_current_version) {
|
||||
//Check if this version is part of ANY upgrade path system (either FROM or TO)
|
||||
$sql = 'SELECT COUNT(*) as path_count
|
||||
FROM products_software_upgrade_paths
|
||||
WHERE to_version_id = ? AND is_active = 1';
|
||||
WHERE (to_version_id = ? OR from_version_id = ?) AND is_active = 1';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$version['version_id']]);
|
||||
$stmt->execute([$version['version_id'], $version['version_id']]);
|
||||
$path_check = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($path_check['path_count'] == 0) {
|
||||
//No paths exist at all - show as free upgrade
|
||||
$show_version = true;
|
||||
if (debug) {
|
||||
$version_debug['upgrade_path_count'] = $path_check['path_count'];
|
||||
}
|
||||
|
||||
if ($path_check['path_count'] == 0) {
|
||||
//Not part of any upgrade path system - show as free upgrade
|
||||
$show_version = true;
|
||||
$decision_reason = 'Showing as FREE - no upgrade paths defined for this version';
|
||||
} else {
|
||||
//Part of an upgrade path system
|
||||
//Only show if there's an explicit path FROM current version TO this version
|
||||
$sql = 'SELECT pup.price, pup.currency
|
||||
FROM products_software_upgrade_paths pup
|
||||
JOIN products_software_versions from_ver ON pup.from_version_id = from_ver.rowID
|
||||
WHERE pup.to_version_id = ?
|
||||
AND LOWER(TRIM(LEADING "0" FROM from_ver.version)) = ?
|
||||
AND pup.is_active = 1';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$version['version_id'], $current_sw_version]);
|
||||
$upgrade_path = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($upgrade_path) {
|
||||
//Valid upgrade path found FROM current version
|
||||
$show_version = true;
|
||||
$final_price = $upgrade_path['price'] ?? '0.00';
|
||||
$final_currency = $upgrade_path['currency'] ?? '';
|
||||
$decision_reason = 'Showing - found upgrade path FROM current (' . $current_sw_version . ') with price: ' . $final_price . ' ' . $final_currency;
|
||||
} else {
|
||||
$decision_reason = 'Skipped - has upgrade paths but none FROM current version (' . $current_sw_version . ')';
|
||||
}
|
||||
//If no path from current version exists, don't show (show_version stays false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
$version_debug['decision'] = [
|
||||
'show_version' => $show_version,
|
||||
'is_current' => $is_current,
|
||||
'final_price' => $final_price,
|
||||
'final_currency' => $final_currency,
|
||||
'reason' => $decision_reason
|
||||
];
|
||||
}
|
||||
|
||||
if ($show_version) {
|
||||
//Check if there's a valid license for this upgrade
|
||||
$final_price = $version['price'] ?? '0.00';
|
||||
$final_currency = $version['currency'] ?? '';
|
||||
|
||||
$license_applied = false;
|
||||
if ($final_price > 0 && $sw_version_license) {
|
||||
//Check if the license is valid
|
||||
$sql = 'SELECT status, start_at, expires_at
|
||||
@@ -151,7 +295,17 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
|
||||
|
||||
//Check if license is within valid date range
|
||||
if ((!$start_at || $start_at <= $now) && (!$expires_at || $expires_at >= $now)) {
|
||||
$original_price = $final_price;
|
||||
$final_price = '0.00';
|
||||
$license_applied = true;
|
||||
|
||||
if (debug) {
|
||||
$version_debug['license_applied'] = [
|
||||
'license_key' => $sw_version_license,
|
||||
'original_price' => $original_price,
|
||||
'new_price' => $final_price
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,9 +323,14 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
|
||||
"source" => '',
|
||||
"source_type" => '',
|
||||
"price" => $final_price,
|
||||
"currency" => $final_currency
|
||||
"currency" => $final_currency,
|
||||
"is_current" => $is_current
|
||||
];
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
$debug['version_decisions'][] = $version_debug;
|
||||
}
|
||||
}
|
||||
|
||||
//GENERATE DOWNLOAD TOKENS FOR EACH OPTION
|
||||
@@ -180,13 +339,38 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
|
||||
$download_token = create_download_url_token($criterias['sn'], $option['version_id']);
|
||||
|
||||
// Create secure download URL
|
||||
$download_url = 'https://'.$_SERVER['SERVER_NAME'].'/api.php/v2/software_download/token='.$download_token;
|
||||
$download_url = 'https://'.$_SERVER['SERVER_NAME'].'/api.php/v2/software_download?token='.$download_token;
|
||||
|
||||
// Set source as download URL
|
||||
$option['source'] = $download_url;
|
||||
$option['source_type'] = 'token_url';
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
$debug['final_output'] = [
|
||||
'total_versions_shown' => count($output),
|
||||
'versions' => array_map(function($o) {
|
||||
return [
|
||||
'name' => $o['name'],
|
||||
'version' => $o['version'],
|
||||
'price' => $o['price'],
|
||||
'is_current' => $o['is_current']
|
||||
];
|
||||
}, $output)
|
||||
];
|
||||
}
|
||||
|
||||
$messages = $output;
|
||||
|
||||
if (debug && !empty($output)) {
|
||||
// Add debug as separate field in response
|
||||
foreach ($messages as &$msg) {
|
||||
$msg['_debug'] = $debug;
|
||||
break; // Only add to first item
|
||||
}
|
||||
} elseif (debug && empty($output)) {
|
||||
$messages = ['message' => 'No upgrades available', 'debug' => $debug];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,10 +233,13 @@ if (isset($post_content['sn']) && isset($post_content['payload'])){
|
||||
$sw_version = substr($sw_version, 0, -4);
|
||||
}
|
||||
|
||||
// Translate hardware version to standardized format
|
||||
$translated_hw_version = translateDeviceHardwareVersion($hw_version);
|
||||
|
||||
//Update Equipment record
|
||||
$sql = "UPDATE equipment SET hw_version = ?, sw_version = ? $whereclause";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$hw_version,$sw_version]);
|
||||
$stmt->execute([$translated_hw_version,$sw_version]);
|
||||
}
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
//Update equipment status ++++++++++++++++++++++++++
|
||||
|
||||
@@ -62,6 +62,15 @@ $clause = substr($clause, 2); //Clean clause - remove first comma
|
||||
$clause_insert = substr($clause_insert, 2); //Clean clause - remove first comma
|
||||
$input_insert = substr($input_insert, 1); //Clean clause - remove first comma
|
||||
|
||||
//VALIDATE: Prevent FROM and TO being the same version
|
||||
if (($command == 'insert' || $command == 'update') &&
|
||||
isset($criterias['from_version_id']) && isset($criterias['to_version_id']) &&
|
||||
$criterias['from_version_id'] == $criterias['to_version_id']) {
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "FROM version cannot be the same as TO version in upgrade path"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
//QUERY AND VERIFY ALLOWED
|
||||
if ($command == 'update' && isAllowed('products_software_upgrade_paths',$profile,$permission,'U') === 1){
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ else {
|
||||
//do nothing
|
||||
}
|
||||
|
||||
//translate HW_VERSION to correct string
|
||||
if (isset($post_content['hw_version']) && $post_content['hw_version'] !=''){$post_content['hw_version'] =translateDeviceHardwareVersion($post_content['hw_version']); }
|
||||
|
||||
//CREATE NEW ARRAY AND MAP TO CLAUSE
|
||||
if(isset($post_content) && $post_content!=''){
|
||||
foreach ($post_content as $key => $var){
|
||||
|
||||
@@ -5192,3 +5192,100 @@ function updateSoftwareVersionStatus($pdo, $serialnumber = null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// Hardware Version Translation Functions
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
/**
|
||||
* Translates hardware version to standardized format
|
||||
* Examples:
|
||||
* - r80, R80, 80 -> r08
|
||||
* - r70, R70, 70 -> r07
|
||||
* - r60, R60, 60 -> r06
|
||||
* etc.
|
||||
*
|
||||
* @param string $hw_version - Input hardware version
|
||||
* @return string - Standardized hardware version
|
||||
*/
|
||||
function translateHardwareVersion($hw_version) {
|
||||
if (empty($hw_version) || $hw_version == '') {
|
||||
return $hw_version;
|
||||
}
|
||||
|
||||
// Remove any whitespace and convert to lowercase for processing
|
||||
$hw_clean = strtolower(trim($hw_version));
|
||||
|
||||
// Treat all-zeros as invalid/empty hardware version
|
||||
if (preg_match('/^0+$/', $hw_clean)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Define translation mapping
|
||||
$translation_map = [
|
||||
// r80/R80/80 variants -> r08
|
||||
'r80' => 'r08',
|
||||
'80' => 'r08',
|
||||
|
||||
// r70/R70/70 variants -> r07
|
||||
'r70' => 'r07',
|
||||
'70' => 'r07',
|
||||
|
||||
// r60/R60/60 variants -> r06
|
||||
'r60' => 'r06',
|
||||
'60' => 'r06',
|
||||
|
||||
// Already correct format, just ensure lowercase
|
||||
'r08' => 'r08',
|
||||
'08' => 'r08',
|
||||
'r07' => 'r07',
|
||||
'07' => 'r07',
|
||||
'r06' => 'r06',
|
||||
'06' => 'r06',
|
||||
];
|
||||
|
||||
// Check if we have a direct mapping
|
||||
if (isset($translation_map[$hw_clean])) {
|
||||
return $translation_map[$hw_clean];
|
||||
}
|
||||
|
||||
// Handle pattern matching for other potential formats
|
||||
// Extract numeric value from various formats (00000080, r90, 90, etc.)
|
||||
if (preg_match('/^r?0*(\d{1,2})$/', $hw_clean, $matches)) {
|
||||
$number = intval($matches[1]);
|
||||
if ($number >= 10 && $number <= 99) {
|
||||
// Convert to zero-padded format: 80 -> 08, 70 -> 07, etc.
|
||||
// Take the tens digit and format as 0X: 80->08, 70->07, 60->06
|
||||
$tensDigit = intval($number / 10);
|
||||
$padded = '0' . $tensDigit;
|
||||
return 'r' . $padded;
|
||||
}
|
||||
}
|
||||
|
||||
// If no translation found, return original input unchanged
|
||||
return $hw_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates hardware version received from device/API to standardized DB format
|
||||
* This should be called before storing hw_version in the database
|
||||
*
|
||||
* @param string $device_hw_version - Hardware version from device
|
||||
* @return string - Standardized hardware version for database storage
|
||||
*/
|
||||
function translateDeviceHardwareVersion($device_hw_version) {
|
||||
return translateHardwareVersion($device_hw_version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates hardware version from database to match device format if needed
|
||||
* This can be used for display or API responses
|
||||
*
|
||||
* @param string $db_hw_version - Hardware version from database
|
||||
* @return string - Hardware version (currently returns same as input)
|
||||
*/
|
||||
function translateDbHardwareVersion($db_hw_version) {
|
||||
// For now, we keep the standardized format from DB
|
||||
// This function exists for future reverse translation if needed
|
||||
return $db_hw_version;
|
||||
}
|
||||
1099
assets/softwaretool.js
Normal file
1099
assets/softwaretool.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -113,9 +113,7 @@ $view .= '</div>';
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#" class="active">'.$tab1.'</a>
|
||||
<a href="#">'.$tab3.'</a>
|
||||
</div>
|
||||
';
|
||||
</div>';
|
||||
|
||||
//GET PARTNERID
|
||||
$view_partners = '';
|
||||
@@ -162,6 +160,9 @@ $view .= '<div class="content-block tab-content active">
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#">'.$tab3.'</a>
|
||||
</div>';
|
||||
$view .= '<div class="content-block tab-content">
|
||||
<div class="form responsive-width-100">
|
||||
<label for="">'.$general_created.'</label>
|
||||
|
||||
16
dealer.php
16
dealer.php
@@ -125,24 +125,32 @@ $view .= '<div class="content-block">
|
||||
<i class="fa-solid fa-bars fa-sm"></i>'.($view_dealer_details_1 ?? 'Descriptions').'
|
||||
<div class="tabs">
|
||||
<a href="#" class="active">'.($dealers_short_description ?? 'Short').'</a>
|
||||
<a href="#">'.($dealers_long_description ?? 'Long').'</a>
|
||||
<a href="#">'.($dealers_usp1 ?? 'USP1').'</a>
|
||||
<a href="#">'.($dealers_usp2 ?? 'USP2').'</a>
|
||||
<a href="#">'.($dealers_usp3 ?? 'USP3').'</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table order-table tab-content active">
|
||||
'.(${$responses['short_description']} ?? $responses['short_description']).'
|
||||
</div>
|
||||
<div class="tabs">
|
||||
<a href="#">'.($dealers_long_description ?? 'Long').'</a>
|
||||
</div>
|
||||
<div class="table order-table tab-content">
|
||||
'.(${$responses['long_description']} ?? $responses['long_description']).'
|
||||
</div>
|
||||
<div class="tabs">
|
||||
<a href="#">'.($dealers_usp1 ?? 'USP1').'</a>
|
||||
</div>
|
||||
<div class="table order-table tab-content">
|
||||
'.(${$responses['usp1']} ?? $responses['usp1']).'
|
||||
</div>
|
||||
<div class="tabs">
|
||||
<a href="#">'.($dealers_usp2 ?? 'USP2').'</a>
|
||||
</div>
|
||||
<div class="table order-table tab-content">
|
||||
'.(${$responses['usp2']} ?? $responses['usp2']).'
|
||||
</div>
|
||||
<div class="tabs">
|
||||
<a href="#">'.($dealers_usp3 ?? 'USP3').'</a>
|
||||
</div>
|
||||
<div class="table order-table tab-content">
|
||||
'.(${$responses['usp3']} ?? $responses['usp3']).'
|
||||
</div>
|
||||
|
||||
@@ -49,9 +49,7 @@ $view .= '</div>';
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#" class="active">'.$tab1.'</a>
|
||||
<a href="#">'.$tab3.'</a>
|
||||
</div>
|
||||
';
|
||||
</div>';
|
||||
|
||||
$view .= '<div class="content-block tab-content active">
|
||||
<div class="form responsive-width-100">
|
||||
@@ -96,8 +94,11 @@ $view .= '<div class="content-block tab-content active">
|
||||
}
|
||||
$view .= '
|
||||
</div>
|
||||
</div>';
|
||||
</div';
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#">'.$tab3.'</a>
|
||||
</div>';
|
||||
$view .= '<div class="content-block tab-content">
|
||||
<div class="form responsive-width-100">
|
||||
<label for="productcode">'.$general_created.'</label>
|
||||
|
||||
@@ -26,21 +26,24 @@ $view = '
|
||||
<h2>'.$firmwaretool_h2 .'</h2>
|
||||
<p>'.$firmwaretool_p.'</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
|
||||
if (isset($_GET['equipmentID'])){$returnpage = 'equipment&equipmentID='.$_GET['equipmentID']; } else {$returnpage = 'dashboard';}
|
||||
|
||||
|
||||
//SHOW BACK BUTTON ONLY FOR PORTAL USERS
|
||||
if (isAllowed('dashboard',$_SESSION['profile'],$_SESSION['permission'],'R') != 0){
|
||||
$view .= '
|
||||
<div class="content-header responsive-flex-column pad-top-5">
|
||||
<a href="index.php?page='.$returnpage.'" class="btn">←</a>
|
||||
<div class="title-actions">
|
||||
<a href="index.php?page='.$returnpage.'" class="btn alt mar-right-2">←</a>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
|
||||
$view .= '
|
||||
</div>';
|
||||
|
||||
$view .= '<div class="content-block">
|
||||
<div class="block-header">
|
||||
<i class="fa-solid fa-bars fa-sm"></i>
|
||||
|
||||
@@ -82,7 +82,13 @@ if (isset($_GET['page']) && $_GET['page'] == 'logout') {
|
||||
//=====================================
|
||||
$allowed_views = explode(',',$_SESSION['profile']);
|
||||
$ignoreViews = ['profile','assets','sales'];
|
||||
|
||||
// If dashboard is in the profile, prioritize it
|
||||
if (in_array('dashboard', $allowed_views) && file_exists('dashboard.php')) {
|
||||
$allowed_views = 'dashboard';
|
||||
} else {
|
||||
$allowed_views = findExistingView($allowed_views, 'dashboard', $ignoreViews);
|
||||
}
|
||||
|
||||
//=====================================
|
||||
//FORWARD THE USER TO THE CORRECT PAGE
|
||||
|
||||
@@ -45,7 +45,6 @@ if (isset($_GET['success_msg'])) {
|
||||
<?php endif; ?>
|
||||
<div class="tabs">
|
||||
<a href="#" class="active">US</a>
|
||||
<a href="#" class="active">NL</a>
|
||||
</div>
|
||||
<div class="content-block">
|
||||
<div class="form responsive-width-100">
|
||||
@@ -53,7 +52,10 @@ if (isset($_GET['success_msg'])) {
|
||||
<label for="language_US"></label>
|
||||
<textarea name="language_US" id="language_US" style="min-height: 100vh;"><?=$contents?></textarea>
|
||||
</div>
|
||||
<div class="tab-content active">
|
||||
<div class="tabs">
|
||||
<a href="#" class="">NL</a>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<label for="language_NL"></label>
|
||||
<textarea name="language_NL" id="language_NL" style="min-height: 100vh;"><?=$contents2?></textarea>
|
||||
</div>
|
||||
|
||||
@@ -147,7 +147,6 @@ if (file_exists($filelocation_webserver)){
|
||||
|
||||
<div class="tabs">
|
||||
<a href="#" class="active">Application</a>
|
||||
<a href="#" class="">Webserver</a>
|
||||
</div>
|
||||
|
||||
<div class="content-block">
|
||||
@@ -156,6 +155,9 @@ if (file_exists($filelocation_webserver)){
|
||||
<label for="Logfile">Application Log</label>
|
||||
<textarea name="logfile" id="logfile" style="min-height: 70vh; font-family: 'Courier New', monospace; font-size: 12px; background: #1e1e1e; color: #f8f8f2; border: 1px solid #333; padding: 15px;"><?=$contents?></textarea>
|
||||
</div>
|
||||
<div class="tabs">
|
||||
<a href="#" class="">Webserver</a>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<label for="WebserverLog">Webserver Log</label>
|
||||
<textarea name="" id="webserver-log" style="min-height: 70vh; font-family: 'Courier New', monospace; font-size: 12px; background: #1e1e1e; color: #f8f8f2; border: 1px solid #333; padding: 15px;"><?=$contents_webserver?></textarea>
|
||||
|
||||
@@ -70,8 +70,6 @@ $view .='
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#" class="active">'.$general_actions .'</a>
|
||||
<a href="#" class="">Learning</a>
|
||||
<a href="#" class="">Translations</a>
|
||||
</div>
|
||||
';
|
||||
|
||||
@@ -96,6 +94,11 @@ $view .= '<div class="content-block tab-content active">
|
||||
</div>
|
||||
</div>';
|
||||
}
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#" class="">Learning</a>
|
||||
</div>
|
||||
';
|
||||
|
||||
if ($update_allowed === 1){
|
||||
$view .= '<div class="content-block tab-content">
|
||||
<div class="form responsive-width-100">
|
||||
@@ -109,6 +112,11 @@ if ($update_allowed === 1){
|
||||
</div>';
|
||||
}
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#" class="">Translations</a>
|
||||
</div>
|
||||
';
|
||||
|
||||
if ($update_allowed === 1){
|
||||
$view .= '<div class="content-block tab-content">
|
||||
<div class="form responsive-width-100">
|
||||
|
||||
11
partner.php
11
partner.php
@@ -120,10 +120,7 @@ $view .= '</div>';
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#" class="active">'.$tab1 .'</a>
|
||||
<a href="#">'.$tab2.'</a>
|
||||
<a href="#">'.$tab3.'</a>
|
||||
</div>
|
||||
';
|
||||
</div>';
|
||||
|
||||
//Define Service and partner enabled
|
||||
$view .= '<div class="content-block tab-content active">
|
||||
@@ -163,6 +160,9 @@ $salesid_dropdown = listPartner('salesid',$_SESSION['permission'],$partner_data-
|
||||
$soldto_dropdown = listPartner('soldto',$_SESSION['permission'],$partner_data->soldto,'');
|
||||
|
||||
//DISPLAY
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#">'.$tab2.'</a>
|
||||
</div>';
|
||||
$view .= '<div class="content-block tab-content">
|
||||
<div class="form responsive-width-100">
|
||||
';
|
||||
@@ -178,6 +178,9 @@ if ($_SESSION['permission'] == 3 || $_SESSION['permission'] == 4){
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#">'.$tab3.'</a>
|
||||
</div>';
|
||||
$view .= '<div class="content-block tab-content">
|
||||
<div class="form responsive-width-100">
|
||||
<label for="">'.$general_created.'</label>
|
||||
|
||||
@@ -141,9 +141,7 @@ $view .= '</div>';
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#" class="active">'.$tab1.'</a>
|
||||
<a href="#">'.$tab3.'</a>
|
||||
</div>
|
||||
';
|
||||
</div>';
|
||||
|
||||
$view .= '<div class="content-block tab-content active">
|
||||
<div class="form responsive-width-100">
|
||||
@@ -171,6 +169,9 @@ $view .= '
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#">'.$tab3.'</a>
|
||||
</div>';
|
||||
$view .= '<div class="content-block tab-content">
|
||||
<div class="form responsive-width-100">
|
||||
<label for="productcode">'.$general_created.'</label>
|
||||
|
||||
@@ -188,7 +188,6 @@ $view .= '</div>';
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#" class="active">'.$tab1.'</a>
|
||||
<a href="#">'.$tab3.'</a>
|
||||
</div>
|
||||
';
|
||||
|
||||
@@ -233,6 +232,11 @@ $view .= '
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#">'.$tab3.'</a>
|
||||
</div>
|
||||
';
|
||||
|
||||
$view .= '<div class="content-block tab-content">
|
||||
<div class="form responsive-width-100">
|
||||
<label for="productcode">'.$general_created.'</label>
|
||||
|
||||
@@ -37,12 +37,19 @@ $path = [
|
||||
'updatedby' => $_SESSION['username']
|
||||
];
|
||||
|
||||
// Determine filter version id from URL (for hw_version filtering)
|
||||
$filter_version_id = $_GET['from_version_id'] ?? $_GET['to_version_id'] ?? $_GET['id'] ?? '';
|
||||
// Check if coming from version page (id parameter) or editing existing path
|
||||
$from_version_page = false;
|
||||
$to_version_fixed = false;
|
||||
if (isset($_GET['id']) && !isset($_GET['path_id'])) {
|
||||
// Coming from version page - this is the TO version
|
||||
$from_version_page = true;
|
||||
$to_version_fixed = $_GET['id'];
|
||||
$path['to_version_id'] = $to_version_fixed;
|
||||
}
|
||||
|
||||
// If editing, fetch existing data
|
||||
if (isset($_GET['id']) && $_GET['id'] != '') {
|
||||
$api_url = '/v2/products_software_upgrade_paths/rowID=' . $_GET['id'];
|
||||
// If editing an existing path, load it
|
||||
if (isset($_GET['path_id']) && $_GET['path_id'] != '') {
|
||||
$api_url = '/v2/products_software_upgrade_paths/rowID=' . $_GET['path_id'];
|
||||
$response = ioServer($api_url, '');
|
||||
|
||||
if (!empty($response)) {
|
||||
@@ -53,6 +60,9 @@ if (isset($_GET['id']) && $_GET['id'] != '') {
|
||||
}
|
||||
}
|
||||
|
||||
// Determine filter version id from URL (for hw_version filtering)
|
||||
$filter_version_id = $_GET['from_version_id'] ?? $_GET['to_version_id'] ?? $_GET['id'] ?? '';
|
||||
|
||||
// Fetch software versions for selects
|
||||
$api_url = '/v2/products_software_versions/list';
|
||||
$versions_response = ioServer($api_url, '');
|
||||
@@ -163,10 +173,10 @@ $view ='
|
||||
<a href="' . $url . '" class="btn alt mar-right-2">' . $button_cancel . '</a>
|
||||
';
|
||||
|
||||
if ($delete_allowed === 1 && isset($_GET['id'])){
|
||||
if ($delete_allowed === 1 && isset($_GET['path_id']) && $_GET['path_id'] != ''){
|
||||
$view .= '<input type="submit" name="delete" value="X" class="btn red mar-right-2" onclick="return confirm(\'Are you sure you want to delete this upgrade path?\')">';
|
||||
}
|
||||
if (($update_allowed === 1 && isset($_GET['id'])) || ($create_allowed === 1 && !isset($_GET['id']))){
|
||||
if (($update_allowed === 1 && isset($_GET['path_id'])) || ($create_allowed === 1 && !isset($_GET['path_id']))){
|
||||
$view .= '<input type="submit" name="submit" value="💾+" class="btn">';
|
||||
}
|
||||
|
||||
@@ -179,21 +189,48 @@ $view .= '<div class="content-block">
|
||||
<option value="">Select From Version</option>';
|
||||
if (!empty($versions)) {
|
||||
foreach ($versions as $ver) {
|
||||
// Skip the TO version from FROM dropdown to prevent FROM = TO
|
||||
if ($path['to_version_id'] && $ver->rowID == $path['to_version_id']) {
|
||||
continue;
|
||||
}
|
||||
$selected = ($path['from_version_id'] == $ver->rowID) ? ' selected' : '';
|
||||
$view .= '<option value="' . $ver->rowID . '"' . $selected . '>' . htmlspecialchars($ver->name . ' (' . $ver->version . ')') . '</option>';
|
||||
}
|
||||
}
|
||||
$view .= ' </select>
|
||||
$view .= ' </select>';
|
||||
|
||||
// If TO version is fixed (coming from version page), show it as read-only text
|
||||
if ($from_version_page && $to_version_fixed) {
|
||||
$to_version_name = '';
|
||||
foreach ($versions as $ver) {
|
||||
if ($ver->rowID == $to_version_fixed) {
|
||||
$to_version_name = htmlspecialchars($ver->name . ' (' . $ver->version . ')');
|
||||
break;
|
||||
}
|
||||
}
|
||||
$view .= '
|
||||
<label for="to_version_display">To Version</label>
|
||||
<input type="text" id="to_version_display" value="' . $to_version_name . '" disabled>
|
||||
<input type="hidden" id="to_version_id" name="to_version_id" value="' . $to_version_fixed . '">';
|
||||
} else {
|
||||
// Show dropdown for TO version when editing
|
||||
$view .= '
|
||||
<label for="to_version_id"><i class="required">*</i>To Version</label>
|
||||
<select id="to_version_id" name="to_version_id" required>
|
||||
<option value="">Select To Version</option>';
|
||||
if (!empty($versions)) {
|
||||
foreach ($versions as $ver) {
|
||||
// Skip the FROM version from TO dropdown to prevent FROM = TO
|
||||
if ($path['from_version_id'] && $ver->rowID == $path['from_version_id']) {
|
||||
continue;
|
||||
}
|
||||
$selected = ($path['to_version_id'] == $ver->rowID) ? ' selected' : '';
|
||||
$view .= '<option value="' . $ver->rowID . '"' . $selected . '>' . htmlspecialchars($ver->name . ' (' . $ver->version . ')') . '</option>';
|
||||
}
|
||||
}
|
||||
$view .= ' </select>
|
||||
$view .= ' </select>';
|
||||
}
|
||||
$view .= '
|
||||
<label for="price">Price</label>
|
||||
<input id="price" type="number" step="0.01" name="price" placeholder="Price" value="' . htmlspecialchars($path['price']) . '">
|
||||
<label for="currency">Currency</label>
|
||||
@@ -207,6 +244,30 @@ $view .= ' </select>
|
||||
<input type="hidden" name="rowID" value="' . htmlspecialchars($path['rowID']) . '">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Validate that FROM and TO versions are different
|
||||
document.querySelector("form").addEventListener("submit", function(e) {
|
||||
const fromVersion = document.getElementById("from_version_id").value;
|
||||
const toVersion = document.getElementById("to_version_id").value;
|
||||
|
||||
if (fromVersion && toVersion && fromVersion === toVersion) {
|
||||
e.preventDefault();
|
||||
alert("Error: FROM version cannot be the same as TO version");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Dynamic filtering: Update dropdowns when selection changes
|
||||
const fromSelect = document.getElementById("from_version_id");
|
||||
const toSelect = document.getElementById("to_version_id");
|
||||
|
||||
if (fromSelect && toSelect && toSelect.tagName === "SELECT") {
|
||||
fromSelect.addEventListener("change", function() {
|
||||
// No need to dynamically filter since PHP already handles it
|
||||
});
|
||||
}
|
||||
</script>
|
||||
';
|
||||
|
||||
//OUTPUT
|
||||
|
||||
@@ -154,7 +154,7 @@ $view = '
|
||||
} else {
|
||||
foreach ($all_paths as $path){
|
||||
$view .= '
|
||||
<tr onclick="window.location.href=\'index.php?page=products_software_upgrade_paths_manage&id='.$path->rowID.'\'" style="cursor: pointer;">
|
||||
<tr onclick="window.location.href=\'index.php?page=products_software_upgrade_paths_manage&path_id='.$path->rowID.'\'" style="cursor: pointer;">
|
||||
<td>' . ($version_map[$path->from_version_id] ?? $path->from_version_id) . '</td>
|
||||
<td>' . ($version_map[$path->to_version_id] ?? $path->to_version_id) . '</td>
|
||||
<td>'.$path->price.'</td>
|
||||
|
||||
@@ -110,7 +110,6 @@ $view .= '</div>';
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#" class="active">'.$tab1.'</a>
|
||||
<a href="#">'.$tab3.'</a>
|
||||
</div>
|
||||
';
|
||||
|
||||
@@ -185,6 +184,11 @@ $view .= '
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#">'.$tab3.'</a>
|
||||
</div>
|
||||
';
|
||||
|
||||
$view .= '<div class="content-block tab-content">
|
||||
<div class="form responsive-width-100">
|
||||
<label for="productcode">'.$general_created.'</label>
|
||||
|
||||
@@ -93,9 +93,7 @@ $view .= '</div>';
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#" class="active">'.$tab1 .'</a>
|
||||
<a href="#">'.$tab3.'</a>
|
||||
</div>
|
||||
';
|
||||
</div>';
|
||||
|
||||
//Define Service and User enabled
|
||||
$view .= '<div class="content-block tab-content active">
|
||||
@@ -130,6 +128,10 @@ $view .=' </select>
|
||||
$view .= '</div>
|
||||
</div>';
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#">'.$tab3.'</a>
|
||||
</div>';
|
||||
|
||||
$view .= '<div class="content-block tab-content">
|
||||
<div class="form responsive-width-100">
|
||||
<label for="">'.$general_created.'</label>
|
||||
|
||||
66
settings.php
66
settings.php
@@ -59,38 +59,33 @@ function format_var_html($key, $value) {
|
||||
$html .= '<input type="' . $type . '" name="' . $key . '" id="' . $key . '" value="' . $value . '" placeholder="' . format_key($key) . '"' . $checked . '>';
|
||||
return $html;
|
||||
}
|
||||
// Format tabs
|
||||
function format_tabs($contents) {
|
||||
// Format tabs and content together (interleaved for collapsible functionality)
|
||||
function format_tabs_and_content($contents) {
|
||||
$rows = explode("\n", $contents);
|
||||
$tab = '<div class="tabs">';
|
||||
$tab .= '<a href="#" class="active">General</a>';
|
||||
for ($i = 0; $i < count($rows); $i++) {
|
||||
preg_match('/\/\*(.*?)\*\//', $rows[$i], $match);
|
||||
if ($match) {
|
||||
$tab .= '<a href="#">' . $match[1] . '</a>';
|
||||
}
|
||||
}
|
||||
$tab .= '</div>';
|
||||
return $tab;
|
||||
}
|
||||
// Format form
|
||||
function format_form($contents) {
|
||||
$rows = explode("\n", $contents);
|
||||
$form = '<div class="tab-content active">';
|
||||
for ($i = 0; $i < count($rows); $i++) {
|
||||
preg_match('/\/\*(.*?)\*\//', $rows[$i], $match);
|
||||
if ($match) {
|
||||
$form .= '</div><div class="tab-content">';
|
||||
}
|
||||
preg_match('/define\(\'(.*?)\', ?(.*?)\)/', $rows[$i], $match);
|
||||
if ($match) {
|
||||
$form .= format_var_html($match[1], $match[2]);
|
||||
}
|
||||
}
|
||||
$form .= '</div>';
|
||||
$output = '';
|
||||
|
||||
return $form;
|
||||
// Start with General tab and its content
|
||||
$output .= '<div class="tabs"><a href="#" class="active">General</a></div>';
|
||||
$output .= '<div class="content-block tab-content active"><div class="form responsive-width-100">';
|
||||
|
||||
for ($i = 0; $i < count($rows); $i++) {
|
||||
preg_match('/\/\*(.*?)\*\//', $rows[$i], $match);
|
||||
if ($match) {
|
||||
// Close previous content and start new tab
|
||||
$output .= '</div></div>';
|
||||
$output .= '<div class="tabs"><a href="#">' . $match[1] . '</a></div>';
|
||||
$output .= '<div class="content-block tab-content"><div class="form responsive-width-100">';
|
||||
}
|
||||
preg_match('/define\(\'(.*?)\', ?(.*?)\)/', $rows[$i], $define_match);
|
||||
if ($define_match) {
|
||||
$output .= format_var_html($define_match[1], $define_match[2]);
|
||||
}
|
||||
}
|
||||
$output .= '</div></div>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
if (isset($_POST['submit']) && !empty($_POST)) {
|
||||
// Update the configuration file with the new keys and values
|
||||
foreach ($_POST as $k => $v) {
|
||||
@@ -99,8 +94,6 @@ if (isset($_POST['submit']) && !empty($_POST)) {
|
||||
}
|
||||
file_put_contents($file, $contents);
|
||||
|
||||
|
||||
|
||||
//Return succesmessage
|
||||
header('Location: index.php?page=settings&success_msg=1');
|
||||
exit;
|
||||
@@ -144,18 +137,9 @@ if (isset($success_msg)){
|
||||
</div>';
|
||||
}
|
||||
|
||||
$view .= format_tabs($contents);
|
||||
$view .= '<div class="content-block">
|
||||
<div class="form responsive-width-100">
|
||||
';
|
||||
$view .= format_form($contents);
|
||||
$view .= format_tabs_and_content($contents);
|
||||
|
||||
$view .= '
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
<script>
|
||||
document.querySelectorAll("input[type=\'checkbox\']").forEach(checkbox => {
|
||||
checkbox.onclick = () => checkbox.value = checkbox.checked ? "true" : "false";
|
||||
|
||||
@@ -123,6 +123,12 @@ $main_menu = [
|
||||
"icon" => "fas fa-tachometer-alt",
|
||||
"name" => "menu_firmwaretool"
|
||||
] ,
|
||||
"softwaretool" => [
|
||||
"url" => "softwaretool",
|
||||
"selected" => "softwaretool",
|
||||
"icon" => "fas fa-download",
|
||||
"name" => "menu_softwaretool"
|
||||
] ,
|
||||
"equipments_mass_update" => [
|
||||
"url" => "equipments_mass_update",
|
||||
"selected" => "equipments_mass_update",
|
||||
|
||||
@@ -4,6 +4,7 @@ $menu_assets = 'Assets';
|
||||
$menu_service_reports = 'Service Reports';
|
||||
$menu_history = 'History';
|
||||
$menu_firmwaretool = 'Firmwaretool';
|
||||
$menu_softwaretool = 'Softwaretool';
|
||||
$menu_equipments_mass_update = 'Mass updates';
|
||||
$menu_products = 'Products';
|
||||
$menu_sales = 'Sales';
|
||||
@@ -327,6 +328,21 @@ $firmwaretool_step_5 = 'When firmwware available: The status bar will show "<i>F
|
||||
$firmwaretool_step_6 = 'When firmwware available: Ensure SN and HW are read from the device and confirm this by selecting the checkbox "I confirm SN and HW are read from device"';
|
||||
$firmwaretool_step_7 = 'Press <i>"Update firmware"</i> button to start the firmware update dialog and follow the onscreen instructions';
|
||||
$firmwaretool_step_8 = '<b>Be aware: This process cannot be stopped and needs to finish.</b>';
|
||||
$softwaretool_h2 = 'Softwaretool';
|
||||
$softwaretool_p = 'Software upgrade options.';
|
||||
$softwaretool_step = 'Instructions';
|
||||
$softwaretool_step_1 = 'Connect the device to the computer by USB.(found under battery cover)';
|
||||
$softwaretool_step_2 = 'Press "<i>connect</i>" button"';
|
||||
$softwaretool_step_3 = 'A popup will appear asking to select a device. Select the device by clicking on it and the press the connect button.';
|
||||
$softwaretool_step_4 = 'After pop-up disappears the device will be read, status bar will show progress';
|
||||
$softwaretool_step_5 = 'Available software upgrades will be displayed with Name, Description and Price';
|
||||
$softwaretool_step_6 = 'Select a free upgrade (price = 0) to download and install';
|
||||
$softwaretool_step_7 = 'For paid upgrades, please contact support';
|
||||
$softwaretool_step_8 = '<b>Be aware: This process cannot be stopped and needs to finish.</b>';
|
||||
$softwaretool_no_updates = 'No software updates found';
|
||||
$softwaretool_checking = 'Checking for software updates...';
|
||||
$softwaretool_available = 'Software updates available';
|
||||
$softwaretool_select_upgrade = 'Select an upgrade option:';
|
||||
$newuser_subject = 'CustomerPortal user created';
|
||||
$newuser_header = 'Dear CustomerPortal user';
|
||||
$newuser_text = 'Your CustomerPortal administrator has provided access to the CustomerPortal. To complete your account you need to update your password via the link below.';
|
||||
|
||||
162
softwaretool.php
Normal file
162
softwaretool.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
defined(page_security_key) or exit;
|
||||
|
||||
if (debug && debug_id == $_SESSION['id']){
|
||||
ini_set('display_errors', '1');
|
||||
ini_set('display_startup_errors', '1');
|
||||
error_reporting(E_ALL);
|
||||
}
|
||||
|
||||
$page = 'softwaretool';
|
||||
//Check if allowed
|
||||
if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){
|
||||
header('location: index.php');
|
||||
exit;
|
||||
}
|
||||
$bearertoken = createCommunicationToken($_SESSION['userkey']);
|
||||
|
||||
|
||||
template_header('Softwaretool', 'softwaretool','view');
|
||||
|
||||
$view = '
|
||||
<div class="content-title">
|
||||
<div class="title">
|
||||
<i class="fa-solid fa-box-open"></i>
|
||||
<div class="txt">
|
||||
<h2>'.$softwaretool_h2 .'</h2>
|
||||
<p>'.$softwaretool_p.'</p>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
|
||||
if (isset($_GET['equipmentID'])){$returnpage = 'equipment&equipmentID='.$_GET['equipmentID']; } else {$returnpage = 'dashboard';}
|
||||
|
||||
|
||||
//SHOW BACK BUTTON ONLY FOR PORTAL USERS
|
||||
if (isAllowed('dashboard',$_SESSION['profile'],$_SESSION['permission'],'R') != 0){
|
||||
$view .= '
|
||||
<div class="title-actions">
|
||||
<a href="index.php?page='.$returnpage.'" class="btn alt mar-right-2"><i class="fa-solid fa-arrow-left"></i></a>
|
||||
<button class="btn" onclick="showInstructions()" style="">
|
||||
<i class="fa-solid fa-circle-question"></i>
|
||||
</button>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
|
||||
$view .= '
|
||||
</div>';
|
||||
|
||||
$view .= '<div class="content-block">
|
||||
|
||||
<p id="servicetoken" value="" hidden>'.$bearertoken.'</p>
|
||||
|
||||
<div id="connectdevice" style="display:flex; gap: 10px; margin-bottom: 20px;">
|
||||
<button class="btn" id="connectButton" onClick="connectDeviceForSoftware()" style="min-width: 150px;">
|
||||
<i class="fa-solid fa-plug"></i>Connect
|
||||
</button>
|
||||
<div id="readStatus" style="flex: 1; background-color: #f1f1f1; border-radius: 4px; overflow: hidden;">
|
||||
<div id="readBar" style="height: 100%; transition: width 0.3s ease;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="Device_output" style="display:none;">
|
||||
<div id="serialResults" style="font-family: monospace;white-space: pre;padding: 10px;background-color:#f1f1f1;display:none;"></div>
|
||||
|
||||
<div id="softwareCheckStatus" style="margin: 20px 0; padding: 20px; background-color:#f8f9fa; border-radius: 8px; text-align: center; display:none;">
|
||||
<i class="fa-solid fa-spinner fa-spin" style="font-size: 24px; color: #04AA6D; margin-bottom: 10px;"></i>
|
||||
<p id="checkingMessage" style="margin: 0; font-size: 16px;">'.$softwaretool_checking.'</p>
|
||||
</div>
|
||||
|
||||
<div id="softwareOptions" style="margin-top: 20px; display:none;">
|
||||
<h3 style="margin-bottom: 20px; color: #333;">'.$softwaretool_select_upgrade.'</h3>
|
||||
<div id="softwareOptionsGrid" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="noUpdatesMessage" style="margin: 20px 0; padding: 30px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 12px; text-align: center; display:none;">
|
||||
<i class="fa-solid fa-check-circle" style="font-size: 48px; margin-bottom: 15px;"></i>
|
||||
<p style="margin: 0; font-size: 18px; font-weight: 500;"><strong>'.$softwaretool_no_updates.'</strong></p>
|
||||
<p style="margin: 10px 0 0 0; opacity: 0.9;">Your device is up to date</p>
|
||||
</div>
|
||||
|
||||
<div id="uploadSection" style="margin-top: 10px; display:none;">
|
||||
<button class="btn" id="uploadSoftware" style="display:none;"
|
||||
emergencyPlug-update verify
|
||||
board="emergencyPlug"
|
||||
port-filters= "[{usbVendorId: 1027, usbProductId: 24597}];"
|
||||
disabled>Install Software
|
||||
<span class="upload-progress"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Help Modal -->
|
||||
<div id="helpModal" style="display:none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; align-items: center; justify-content: center;">
|
||||
<div style="background: white; border-radius: 12px; max-width: 600px; max-height: 80vh; overflow-y: auto; margin: 20px; box-shadow: 0 10px 40px rgba(0,0,0,0.3);">
|
||||
<div style="padding: 25px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center;">
|
||||
<h3 style="margin: 0; color: #333;"><i class="fa-solid fa-circle-question"></i> '.$softwaretool_step.'</h3>
|
||||
<button onclick="closeInstructions()" style="background: transparent; border: none; font-size: 20px; cursor: pointer; color: #666;"><i class="fa-solid fa-times"></i></button>
|
||||
</div>
|
||||
<div style="padding: 25px;">
|
||||
<ol style="line-height: 1.8; color: #555;">
|
||||
<li style="margin-bottom: 15px;">'.$softwaretool_step_1.'</li>
|
||||
<li style="margin-bottom: 15px;">'.$softwaretool_step_2.'</li>
|
||||
<li style="margin-bottom: 15px;">'.$softwaretool_step_3.'</li>
|
||||
<li style="margin-bottom: 15px;">'.$softwaretool_step_4.'</li>
|
||||
<li style="margin-bottom: 15px;">'.$softwaretool_step_5.'</li>
|
||||
<li style="margin-bottom: 15px;">'.$softwaretool_step_6.'</li>
|
||||
<li style="margin-bottom: 15px;">'.$softwaretool_step_7.'</li>
|
||||
<li style="margin-bottom: 15px;">'.$softwaretool_step_8.'</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
|
||||
$view .= '
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
|
||||
$view .= '</div>';
|
||||
//OUTPUT
|
||||
echo $view;
|
||||
|
||||
|
||||
echo '
|
||||
<script src="assets/upload.js?'.script_version.'"></script>
|
||||
<script src="assets/softwaretool.js?'.script_version.'"></script>
|
||||
<script>
|
||||
var link = "'.$baseurl.'";
|
||||
var DEBUG = '.(debug ? 'true' : 'false').';
|
||||
var port, textEncoder, writableStreamClosed, writer, historyIndex = -1;
|
||||
const lineHistory = [];
|
||||
|
||||
// Modal functions - defined globally for inline onclick
|
||||
window.showInstructions = function() {
|
||||
const modal = document.getElementById("helpModal");
|
||||
if (modal) {
|
||||
modal.style.display = "flex";
|
||||
}
|
||||
};
|
||||
|
||||
window.closeInstructions = function() {
|
||||
const modal = document.getElementById("helpModal");
|
||||
if (modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
};
|
||||
|
||||
// Close modal on background click
|
||||
document.addEventListener("click", function(e) {
|
||||
const modal = document.getElementById("helpModal");
|
||||
if (modal && e.target === modal) {
|
||||
closeInstructions();
|
||||
}
|
||||
});
|
||||
</script>';
|
||||
|
||||
template_footer();
|
||||
?>
|
||||
@@ -1002,8 +1002,7 @@ main .manage-order-table .delete-item:hover {
|
||||
.table {
|
||||
overflow-x: auto;
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.table table {
|
||||
|
||||
17
user.php
17
user.php
@@ -185,11 +185,7 @@ $view .= '</div>';
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#" class="active">'.$tab1 .'</a>
|
||||
<a href="#">'.$tab2.'</a>
|
||||
<a href="#">'.$tab3.'</a>
|
||||
'.(($update_allowed === 1 && $user_ID !='')? '<a href="#">'.$general_actions.'</a>':"").'
|
||||
</div>
|
||||
';
|
||||
</div>';
|
||||
|
||||
//Define Service and User enabled
|
||||
$view .= '<div class="content-block tab-content active">
|
||||
@@ -263,6 +259,10 @@ $view .=' </select>
|
||||
$view .= '</div>
|
||||
</div>';
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#">'.$tab2.'</a>
|
||||
</div>';
|
||||
|
||||
//GET PARTNERDATA
|
||||
$partner_data = json_decode($user['partnerhierarchy'])?? json_decode($_SESSION['partnerhierarchy']) ;
|
||||
//BUID UP DROPDOWNS
|
||||
@@ -289,6 +289,10 @@ $view .= '
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#">'.$tab3.'</a>
|
||||
</div>';
|
||||
|
||||
|
||||
//SUPERUSERS AND ADMINS CAN RESET BLOCKED USERS
|
||||
if ($_SESSION['permission'] == 3 || $_SESSION['permission'] == 4){
|
||||
@@ -313,6 +317,9 @@ $view .= '<div class="content-block tab-content">
|
||||
</div>';
|
||||
|
||||
if ($update_allowed === 1 && $user_ID !=''){
|
||||
$view .= '<div class="tabs">
|
||||
<a href="#">'.$general_actions.'</a>
|
||||
</div>';
|
||||
$view .= '<div class="content-block tab-content">
|
||||
<div class="form responsive-width-100">
|
||||
<label for="service">'.$User_pw_reset .'</label>
|
||||
|
||||
Reference in New Issue
Block a user