Add user roles management page and update user permissions handling

- Created a new `user_roles.php` file for managing user roles and permissions.
- Implemented pagination, filtering, and sorting for user roles.
- Updated `users.php` to use the new authorization structure for permissions.
- Changed API version from v1 to v2 in `users.php` for user data retrieval.
- Modified `webhook_mollie.php` to include account hierarchy in license creation.
- Refactored invoice generation and email sending logic in `webhook_mollie.php`.
- Introduced a new `webhook_paypal.php` file to handle PayPal webhook notifications.
- Implemented payment status updates and license creation logic in `webhook_paypal.php`.
- Added helper functions for PayPal webhook signature verification and access token retrieval.
This commit is contained in:
“VeLiTi”
2026-01-29 19:31:10 +01:00
199 changed files with 19222 additions and 3386 deletions

13
.gitignore vendored
View File

@@ -15,3 +15,16 @@ settings/soveliti/soveliti_settings.php
assets/database/dev_schema.sql
assets/database/migration.sql
assets/database/prod_schema.sql
migration.sql
assets/database/migration_triggers.sql
assets/database/migration_v2.sql
assets/database/migration_v3.sql
.DS_Store
api/.DS_Store
api/v1/.DS_Store
api/v2/.DS_Store
api/.DS_Store
assets/.DS_Store
assets/images/.DS_Store
assets/database/ManualUpdates.sql
assets/database/migration_users_to_rbac.sql

View File

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

View File

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

188
access_element.php Normal file
View File

@@ -0,0 +1,188 @@
<?php
defined(page_security_key) or exit;
if (debug && debug_id == $_SESSION['authorization']['id']){
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
}
include_once './assets/functions.php';
include_once './settings/settings_redirector.php';
//SET ORIGIN FOR NAVIGATION
$_SESSION['prev_origin_access_element'] = $_SERVER['REQUEST_URI'];
$page = 'access_element';
//Check if allowed
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}
//PAGE Security
$page_manage = 'access_element_manage';
$update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$update_allowed_edit = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
//GET Details from URL
$GET_VALUES = urlGETdetails($_GET) ?? '';
//CALL TO API FOR General information
$api_url = '/v2/access_elements/'.$GET_VALUES;
$responses = ioServer($api_url,'');
//Decode Payload
if (!empty($responses)){$responses = json_decode($responses);}else{$responses = null;}
$responses = $responses[0];
$element_id = $responses->rowID;
//CALL TO API FOR Roles using this access element
$api_url = '/v2/role_access_permissions/access_id='.$element_id;
$role_permissions = ioServer($api_url,'');
//Decode Payload
if (!empty($role_permissions)){$role_permissions = json_decode($role_permissions);}else{$role_permissions = null;}
//------------------------------
//Variables
//------------------------------
$status_text = ($responses->is_active == 1) ? ($enabled ?? 'Active') : ($disabled ?? 'Inactive');
$status_class = ($responses->is_active == 1) ? 'id1' : 'id0';
// Handle success messages
if (isset($_GET['success_msg'])) {
if ($_GET['success_msg'] == 1) {
$success_msg = ($message_access_1 ?? 'Access element created successfully');
}
if ($_GET['success_msg'] == 2) {
$success_msg = ($message_access_2 ?? 'Access element updated successfully');
}
if ($_GET['success_msg'] == 3) {
$success_msg = ($message_access_3 ?? 'Access element deleted successfully');
}
}
template_header(($access_element_title ?? 'Access Element'), 'access_element', 'view');
$view = '
<div class="content-title responsive-flex-wrap responsive-pad-bot-3">
<h2 class="responsive-width-100">'.($view_access_h2 ?? 'Access Element').' - '.$responses->access_name.'</h2>
<a href="index.php?page='.$_SESSION['origin'].'&p='.$_SESSION['p'].$_SESSION['status'].$_SESSION['sort'].$_SESSION['search'].'" class="btn alt mar-right-2">←</a>
';
if ($update_allowed_edit === 1){
$view .= '<a href="index.php?page=access_element_manage&rowID='.$responses->rowID.'" class="btn">✏️</a>';
}
$view .= '</div>';
if (isset($success_msg)){
$view .= ' <div class="msg success">
<i class="fas fa-check-circle"></i>
<p>'.$success_msg.'</p>
<i class="fas fa-times"></i>
</div>';
}
$view .= '<div class="content-block-wrapper">';
// Access Element Information Block
$view .= ' <div class="content-block order-details">
<div class="block-header">
<i class="fa-solid fa-circle-info"></i>'.($view_access_information ?? 'Access Element Information').'
</div>
<div class="order-detail">
<h3>'.($general_status ?? 'Status').'</h3>
<p><span class="status '.$status_class.'">'.$status_text.'</span></p>
</div>
<div class="order-detail">
<h3>'.($access_element_name ?? 'Name').'</h3>
<p>'.$responses->access_name.'</p>
</div>
<div class="order-detail">
<h3>'.($access_element_path ?? 'Path').'</h3>
<p>'.$responses->access_path.'</p>
</div>
<div class="order-detail">
<h3>'.($access_element_group ?? 'Group').'</h3>
<p>'.($responses->access_group ?? '-').'</p>
</div>
<div class="order-detail">
<h3>'.($role_description ?? 'Description').'</h3>
<p>'.($responses->description ?? '-').'</p>
</div>
</div>
';
$view .= '</div>'; // Close content-block-wrapper
// Roles Using This Access Element
$view .= '<div class="content-block">
<div class="block-header">
<i class="fa-solid fa-user-shield fa-sm"></i>'.($view_access_roles ?? 'Roles Using This Element').'
</div>
<div class="table">
<table>
<thead>
<tr>
<th>'.($role_name ?? 'Role Name').'</th>
<th>'.($permission_create ?? 'C').'</th>
<th>'.($permission_read ?? 'R').'</th>
<th>'.($permission_update ?? 'U').'</th>
<th>'.($permission_delete ?? 'D').'</th>
</tr>
</thead>
<tbody>';
if (!empty($role_permissions)){
foreach ($role_permissions as $role_perm){
$can_create = ($role_perm->can_create == 1) ? '<i class="fa-solid fa-check" style="color:green;"></i>' : '<i class="fa-solid fa-times" style="color:red;"></i>';
$can_read = ($role_perm->can_read == 1) ? '<i class="fa-solid fa-check" style="color:green;"></i>' : '<i class="fa-solid fa-times" style="color:red;"></i>';
$can_update = ($role_perm->can_update == 1) ? '<i class="fa-solid fa-check" style="color:green;"></i>' : '<i class="fa-solid fa-times" style="color:red;"></i>';
$can_delete = ($role_perm->can_delete == 1) ? '<i class="fa-solid fa-check" style="color:green;"></i>' : '<i class="fa-solid fa-times" style="color:red;"></i>';
$view .= '<tr onclick="window.location.href=\'index.php?page=user_role&rowID='.$role_perm->role_id.'\'" style="cursor: pointer;">
<td>'.$role_perm->role_name.'</td>
<td>'.$can_create.'</td>
<td>'.$can_read.'</td>
<td>'.$can_update.'</td>
<td>'.$can_delete.'</td>
</tr>';
}
} else {
$view .= '<tr>
<td colspan="5" style="text-align:center;">'.($no_roles_using ?? 'No roles are using this access element').'</td>
</tr>';
}
$view .= ' </tbody>
</table>
</div>
</div>
';
// Metadata Block
$view .= '<div class="content-block">
<div class="block-header">
<i class="fa-solid fa-bars fa-sm"></i>'.($tab3 ?? 'Details').'
</div>
<div class="table order-table">
<table>
<tr>
<td style="width:25%;">'.($general_created ?? 'Created').'</td>
<td>'.getRelativeTime($responses->created).'</td>
</tr>
<tr>
<td style="width:25%;">'.($general_updated ?? 'Updated').'</td>
<td>'.getRelativeTime($responses->updated).'</td>
</tr>
</table>
</div>
</div>
';
//OUTPUT
echo $view;
template_footer()
?>

168
access_element_manage.php Normal file
View File

@@ -0,0 +1,168 @@
<?php
defined(page_security_key) or exit;
if (debug && debug_id == $_SESSION['authorization']['id']){
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
}
include_once './assets/functions.php';
include_once './settings/settings_redirector.php';
$page = 'access_element_manage';
//Check if allowed
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}
//PAGE Security
$update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
// Default input values
$element = [
'rowID' => '',
'access_name' => '',
'access_path' => '',
'access_group' => '',
'description' => '',
'is_active' => 1,
'created' => '',
'createdby' => $_SESSION['authorization']['clientID'],
'updated' => '',
'updatedby' => ''
];
$element_ID = $_GET['rowID'] ?? '';
if ($element_ID !=''){
$url = 'index.php?page=access_element&rowID='.$element_ID.'';
} else {
$url = 'index.php?page=access_elements';
}
if (isset($_GET['rowID'])) {
// ID param exists, edit an existing element
//CALL TO API
$api_url = '/v2/access_elements/rowID='.$element_ID;
$responses = ioServer($api_url,'');
//Decode Payload
if (!empty($responses)){$responses = json_decode($responses,true);}else{$responses = null;}
$element = $responses[0];
if ($update_allowed === 1){
if (isset($_POST['submit'])) {
//GET ALL POST DATA
$data = json_encode($_POST, JSON_UNESCAPED_UNICODE);
//API call
$responses = ioServer('/v2/access_elements', $data);
if ($responses === 'NOK'){
} else {
header('Location: index.php?page=access_element&rowID='.$element_ID.'&success_msg=2');
exit;
}
}
}
if ($delete_allowed === 1){
if (isset($_POST['delete'])) {
//GET ALL POST DATA
$data = json_encode($_POST , JSON_UNESCAPED_UNICODE);
//API call
$responses = ioServer('/v2/access_elements', $data);
// Redirect and delete element
if ($responses === 'NOK'){
} else {
header('Location: index.php?page=access_elements&success_msg=3');
exit;
}
}
}
} else {
// Create a new element
if (isset($_POST['submit']) && $create_allowed === 1) {
//GET ALL POST DATA
$data = json_encode($_POST, JSON_UNESCAPED_UNICODE);
//API call
$responses = ioServer('/v2/access_elements', $data);
if ($responses === 'NOK'){
} else {
header('Location: index.php?page=access_elements&success_msg=1');
exit;
}
}
}
template_header(($access_element_title ?? 'Access Element'), 'access_element', 'manage');
$label_h2 = (($element_ID !='')? ($manage_access_h2 ?? 'Edit Access Element') : ($button_create_access ?? 'Create Access Element'));
$view ='
<form action="" method="post">
<div class="content-title responsive-flex-wrap responsive-pad-bot-3">
<h2 class="responsive-width-100">'.$label_h2.'</h2>
<a href="'.$url.'" class="btn alt mar-right-2">←</a>
';
if ($delete_allowed === 1 && $element_ID != ''){
$view .= '<input type="submit" name="delete" value="X" class="btn red mar-right-2" onclick="return confirm(\''.($confirm_delete_access ?? 'Are you sure you want to delete this access element?').'\')">';
}
if ($update_allowed === 1 || ($create_allowed === 1 && $element_ID == '')){
$view .= '<input type="submit" name="submit" value="💾" class="btn">';
}
$view .= '</div>';
$view .= '<div class="tabs">
<a href="#" class="active">'.($tab1 ?? 'General').'</a>
</div>
<div class="content-block tab-content active">
<div class="form responsive-width-100">
<label for="is_active">'.($general_status ?? 'Status').'</label>
<select id="is_active" name="is_active">
<option value="1" '.($element['is_active']==1?' selected':'').'>'.($enabled ?? 'Active').'</option>
<option value="0" '.($element['is_active']==0?' selected':'').'>'.($disabled ?? 'Inactive').'</option>
</select>
<label for="access_name">'.($access_element_name ?? 'Name').' <i class="required">*</i></label>
<input id="access_name" type="text" name="access_name" placeholder="'.($access_element_name ?? 'Name').'" value="'.$element['access_name'].'" required>
<label for="access_path">'.($access_element_path ?? 'Path').' <i class="required">*</i></label>
<input id="access_path" type="text" name="access_path" placeholder="'.($access_element_path_placeholder ?? 'e.g., equipments, equipment_manage').'" value="'.$element['access_path'].'" required>
<label for="access_group">'.($access_element_group ?? 'Group').'</label>
<input id="access_group" type="text" name="access_group" placeholder="'.($access_element_group_placeholder ?? 'e.g., Views, API, Admin').'" value="'.($element['access_group'] ?? '').'">
<label for="description">'.($role_description ?? 'Description').'</label>
<textarea id="description" name="description" placeholder="'.($role_description ?? 'Description').'" style="height: 100px;">'.$element['description'].'</textarea>
<input type="hidden" name="rowID" value="'.$element_ID.'">
</div>
</div>';
//DISPLAY TAB 2 - Metadata
if ($element_ID != ''){
$view .= '<div class="tabs">
<a href="#">'.($tab3 ?? 'Details').'</a>
</div>
<div class="content-block tab-content">
<div class="form responsive-width-100">
<label for="created">'.($general_created ?? 'Created').'</label>
<input id="created" type="text" name="" placeholder="'.($general_created ?? 'Created').'" value="'.$element['created'].'" readonly>
<label for="createdby">'.($general_createdby ?? 'Created By').'</label>
<input id="createdby" type="text" name="" placeholder="'.($general_createdby ?? 'Created By').'" value="'.$element['createdby'].'" readonly>
<label for="updated">'.($general_updated ?? 'Updated').'</label>
<input id="updated" type="text" name="" placeholder="'.($general_updated ?? 'Updated').'" value="'.$element['updated'].'" readonly>
<label for="updatedby">'.($general_updatedby ?? 'Updated By').'</label>
<input id="updatedby" type="text" name="" placeholder="'.($general_updatedby ?? 'Updated By').'" value="'.$element['updatedby'].'" readonly>
</div>
</div>';
}
$view .= '</form>';
//Output
echo $view;
template_footer()?>

317
access_elements.php Normal file
View File

@@ -0,0 +1,317 @@
<?php
defined(page_security_key) or exit;
if (debug && debug_id == $_SESSION['authorization']['id']){
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
}
include_once './assets/functions.php';
include_once './settings/settings_redirector.php';
//SET PAGE ORIGIN FOR NAVIGATION AND SECURITY
$prev_page = $_SESSION['prev_origin'] ?? '';
$page = $_SESSION['origin'] = 'access_elements';
//create backbutton to prev_origin
$back_btn_orgin = ($prev_page != '')? '<a href="'.$prev_page.'" class="btn alt mar-right-2">←</a>':'';
//Check if allowed
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}
//PAGE Security
$page_manage = 'access_element_manage';
$update_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
// Function to scan project for new PHP files and add to access_elements
function scan_and_update_access_elements() {
$new_elements = [];
$base_path = dirname(__FILE__);
// Scan root PHP files (excluding index, login, logout)
$root_files = glob($base_path . '/*.php');
foreach ($root_files as $file) {
$filename = basename($file, '.php');
if (!in_array($filename, ['index', 'login', 'logout'])) {
// Only add if not already in array (first occurrence wins)
if (!isset($new_elements[$filename])) {
$new_elements[$filename] = [
'name' => ucwords(str_replace('_', ' ', $filename)),
'path' => $filename,
'group' => 'Views',
'description' => 'Auto-scanned: ' . $filename
];
}
}
}
// Scan API v2 get folder - only add if not already found in root
$get_files = glob($base_path . '/api/v2/get/*.php');
foreach ($get_files as $file) {
$filename = basename($file, '.php');
if (!isset($new_elements[$filename])) {
$new_elements[$filename] = [
'name' => ucwords(str_replace('_', ' ', $filename)),
'path' => $filename,
'group' => 'API',
'description' => 'Auto-scanned: ' . $filename
];
}
}
// Scan API v2 post folder - only add if not already found
$post_files = glob($base_path . '/api/v2/post/*.php');
foreach ($post_files as $file) {
$filename = basename($file, '.php');
if (!isset($new_elements[$filename])) {
$new_elements[$filename] = [
'name' => ucwords(str_replace('_', ' ', $filename)),
'path' => $filename,
'group' => 'API',
'description' => 'Auto-scanned: ' . $filename
];
}
}
// Get existing access elements from API
$api_url = '/v2/access_elements/';
$existing = ioServer($api_url, '');
$existing_paths = [];
if (!empty($existing)) {
$existing_data = json_decode($existing);
foreach ($existing_data as $element) {
$existing_paths[] = $element->access_path;
}
}
// Filter out elements that already exist
$elements_to_add = [];
foreach ($new_elements as $path => $element) {
if (!in_array($path, $existing_paths)) {
$elements_to_add[] = $element;
}
}
// Add new elements via API
$added_count = 0;
foreach ($elements_to_add as $element) {
$data = json_encode([
'access_name' => $element['name'],
'access_path' => $element['path'],
'access_group' => $element['group'],
'description' => $element['description'],
'is_active' => 1
], JSON_UNESCAPED_UNICODE);
$response = ioServer('/v2/access_elements', $data);
if ($response !== 'NOK') {
$added_count++;
}
}
return $added_count;
}
// Handle scan request
if (isset($_POST['scan_elements']) && $create_allowed === 1) {
$added_count = scan_and_update_access_elements();
header('Location: index.php?page=access_elements&elements_added=' . $added_count);
exit;
}
//GET PARAMETERS && STORE in SESSION for FURTHER USE/NAVIGATION
$pagination_page = $_SESSION['p'] = isset($_GET['p']) ? $_GET['p'] : 1;
$status = $_SESSION['status'] = isset($_GET['status']) ? '&status='.$_GET['status'] : '';
$sort = $_SESSION['sort'] = isset($_GET['sort']) ? '&sort='.$_GET['sort'] : '';
$search = $_SESSION['search'] = isset($_GET['search']) ? '&search='.$_GET['search'] : '';
//GET PARAMETERS FOR FILTERS
$filter = urlGETdetailsFilter($_GET) ?? '';
// Determine the URL
$url = 'index.php?page=access_elements'.$status.$search.$sort;
//GET Details from URL
$GET_VALUES = urlGETdetails($_GET) ?? '';
//CALL TO API
$api_url = '/v2/access_elements/'.$GET_VALUES;
$responses = ioServer($api_url,'');
//Decode Payload
if (!empty($responses)){$responses = json_decode($responses);}else{$responses = null;}
//Return QueryTotal from API
$total_url = ((!empty($GET_VALUES) && $GET_VALUES !='') ? '&totals=' : 'totals=' );
$api_url = '/v2/access_elements/'.$GET_VALUES.$total_url;
$query_total = ioServer($api_url,'');
//Decode Payload
if (!empty($query_total)){$query_total = json_decode($query_total);}else{$query_total = null;}
// Handle success messages
if (isset($_GET['success_msg'])) {
if ($_GET['success_msg'] == 1) {
$success_msg = ($message_access_1 ?? 'Access element created successfully');
}
if ($_GET['success_msg'] == 2) {
$success_msg = ($message_access_2 ?? 'Access element updated successfully');
}
if ($_GET['success_msg'] == 3) {
$success_msg = ($message_access_3 ?? 'Access element deleted successfully');
}
}
// Handle elements added message from scan
if (isset($_GET['elements_added'])) {
$added_count = (int)$_GET['elements_added'];
if ($added_count > 0) {
$success_msg = $added_count . ' ' . ($message_elements_added ?? 'new access elements added');
} else {
$success_msg = ($message_no_new_elements ?? 'No new elements found. All elements are up to date.');
}
}
template_header(($access_elements_title ?? 'Access Elements'), 'access_elements','view');
$view = '
<div class="content-title">
<div class="title">
<i class="fa-solid fa-lock"></i>
<div class="txt">
<h2>'.($access_elements_h2 ?? 'Access Elements').' ('.$query_total.')</h2>
<p>'.($access_elements_p ?? 'Manage system access elements and paths').'</p>
</div>
</div>
<div class="title-actions">
'.$back_btn_orgin;
// Scan button - only show if user has create permission
if ($create_allowed === 1){
$view .= '
<form action="" method="post" style="display:inline;">
<button type="submit" name="scan_elements" class="btn alt" title="'.($scan_elements_title ?? 'Scan for new files').'">
<i class="fa-solid fa-sync-alt"></i>
</button>
</form>';
$view .= '<a href="index.php?page=access_element_manage" class="btn">+</a>';
}
$view .= '<button id="filter-toggle" class="btn alt" onclick="toggleFilters()">
<i class="fa-solid fa-search"></i>
</button>
</div>
</div>';
if (isset($success_msg)){
$view .= ' <div class="msg success">
<i class="fas fa-check-circle"></i>
<p>'.$success_msg.'</p>
<i class="fas fa-times"></i>
</div>';
}
$view .= '
<div id="filter-panel" class="filter-panel" style="display: none;">
<div class="filter-content">
<form action="" method="get">
'.$filter.'
<div class="filter-row">
<div class="filter-group">
<select name="status">
<option value="" disabled selected>'.($general_status ?? 'Status').'</option>
<option value="1"'.(isset($_GET['status']) && $_GET['status']==1?' selected':'').'>'.($enabled ?? 'Active').'</option>
<option value="0"'.(isset($_GET['status']) && $_GET['status']==0?' selected':'').'>'.($disabled ?? 'Inactive').'</option>
</select>
</div>
<div class="filter-group">
<select name="sort">
<option value="" disabled selected>'.($general_sort ?? 'Sort').'</option>
<option value="1"'.(isset($_GET['sort']) && $_GET['sort']==1?' selected':'').'>'.($access_element_name ?? 'Name').' '.($general_sort_type_1 ?? 'ASC').'</option>
<option value="2"'.(isset($_GET['sort']) && $_GET['sort']==2?' selected':'').'>'.($access_element_name ?? 'Name').' '.($general_sort_type_2 ?? 'DESC').'</option>
<option value="3"'.(isset($_GET['sort']) && $_GET['sort']==3?' selected':'').'>'.($access_element_path ?? 'Path').' '.($general_sort_type_1 ?? 'ASC').'</option>
<option value="4"'.(isset($_GET['sort']) && $_GET['sort']==4?' selected':'').'>'.($access_element_path ?? 'Path').' '.($general_sort_type_2 ?? 'DESC').'</option>
</select>
</div>
<div class="filter-group search-group">
<input type="text" name="search" placeholder="'.($access_search ?? 'Search access elements...').'" value="">
</div>
</div>
<div class="filter-actions">
<button type="submit" class="btn"><i class="fas fa-level-down-alt fa-rotate-90"></i></button>
<a class="btn alt" href="index.php?page=access_elements">X</a>
</div>
</form>
</div>
</div>
';
$view .= '
<div class="content-block">
<div class="table">
<table class="sortable">
<thead>
<tr>
<th>'.($access_element_name ?? 'Name').'</th>
<th>'.($access_element_path ?? 'Path').'</th>
<th>'.($access_element_group ?? 'Group').'</th>
<th class="responsive-hidden">'.($role_description ?? 'Description').'</th>
<th>'.($general_status ?? 'Status').'</th>
<th class="responsive-hidden">'.($general_created ?? 'Created').'</th>
</tr>
</thead>
<tbody>
';
if (empty($responses)){
$view .= '
<tr>
<td colspan="6" style="text-align:center;">'.($message_no_access_elements ?? 'No access elements found').'</td>
</tr>';
}
foreach ($responses as $response){
//Translate status INT to STR
$status_text = ($response->is_active == 1) ? ($enabled ?? 'Active') : ($disabled ?? 'Inactive');
$status_class = ($response->is_active == 1) ? 'id1' : 'id0';
$view .= '<tr onclick="window.location.href=\'index.php?page=access_element&rowID='.$response->rowID.'\'" style="cursor: pointer;">
<td>'.$response->access_name.'</td>
<td>'.$response->access_path.'</td>
<td>'.($response->access_group ?? '-').'</td>
<td class="responsive-hidden">'.($response->description ?? '-').'</td>
<td><span class="status '.$status_class.'">'.$status_text.'</span></td>
<td class="responsive-hidden">'.getRelativeTime($response->created).'</td>
</tr>
';
}
$view .= '
</tbody>
</table>
</div>
</div>
';
$page_rows = $page_rows_equipment ?? 20;
$view.='<div class="pagination">';
if ($pagination_page > 1) {
$page = $pagination_page-1;
$view .= '<a href="'.$url.'&p=1">'.($general_first ?? 'First').'</a>';
$view .= '<a href="'.$url.'&p='.$page.'">'.($general_prev ?? 'Prev').'</a>';
}
$totals = ceil($query_total / $page_rows) == 0 ? 1 : ceil($query_total / $page_rows);
$view .= '<span> '.($general_page ?? 'Page ').$pagination_page.($general_page_of ?? ' of ').$totals.'</span>';
if ($pagination_page * $page_rows < $query_total){
$page = $pagination_page+1;
$view .= '<a href="'.$url.'&p='.$page.'">'.($general_next ?? 'Next').'</a>';
$view .= '<a href="'.$url.'&p='.$totals.'">'.($general_last ?? 'Last').'</a>';
}
$view .= '</div>';
//OUTPUT
echo $view;
template_footer();
?>

View File

@@ -1,7 +1,7 @@
<?php
defined(page_security_key) or exit;
if (debug && debug_id == $_SESSION['id']){
if (debug && debug_id == $_SESSION['authorization']['id']){
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
@@ -15,15 +15,15 @@ $_SESSION['prev_origin'] = $_SERVER['REQUEST_URI'];
$page = $_SESSION['origin'] = 'account';
//Check if allowed
if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}
//PAGE Security
$update_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'C');
$healthindex_allowed = isAllowed('report_healthindex' ,$_SESSION['profile'],$_SESSION['permission'],'R');
$update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
$healthindex_allowed = isAllowed('report_healthindex' ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R');
//GET Details from URL
$GET_VALUES = urlGETdetails($_GET) ?? '';
@@ -239,13 +239,13 @@ $soldto_id = explode("-",$account_hierarchy->soldto) ?? '';
//DISPLAY RELATED COMMUNICATION RECORDS
$view_communication = '';
if ($_SESSION['permission'] == 3 || $_SESSION['permission'] == 4){
if (isAllowed('communications',$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 1){
$view_communication = ' <a href="index.php?page=communications&partnerid='.$soldto_id[0].'" class="btn">'.$button_partner_assigned_communication.'</a>';
}
//DISPLAY RELATED USERS
$view_users ='';
if ($_SESSION['permission'] == 3 || $_SESSION['permission'] == 4){
if (isAllowed('users',$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 1){
$view_users = ' <a href="index.php?page=users&partnerid='.$soldto_id[0].'" class="btn">'.$button_partner_assigned_users.'</a>';
}

View File

@@ -3,14 +3,14 @@ defined(page_security_key) or exit;
$page = 'account';
//Check if allowed
if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}
//PAGE Security
$update_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'C');
$update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
// Default input product values
$account = [
@@ -43,7 +43,7 @@ $account = [
'loghandleraccount' => ''
],
'created' => $date,
'createdby' => $_SESSION['username'],
'createdby' => $_SESSION['authorization']['clientID'],
'accounthierarchy' => [
'salesid' => '',
'soldto' => ''
@@ -140,7 +140,7 @@ if ($delete_allowed === 1){
$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 account?\')">';
}
if ($update_allowed === 1){
$view .= '<input type="submit" name="submit" value="💾+" class="btn">';
$view .= '<input type="submit" name="submit" value="💾" class="btn">';
}
$view .= '</div>';
@@ -271,8 +271,8 @@ $view .= '<div class="tabs">
//Dropdown
$partner_data = json_decode($_SESSION['partnerhierarchy']);
$soldto_dropdown = listPartner('soldto',$_SESSION['permission'],$accounthierarchy->soldto,'');
$partner_data = json_decode($_SESSION['authorization']['partnerhierarchy']);
$soldto_dropdown = listPartner('soldto',$_SESSION['authorization']['permission'],$accounthierarchy->soldto,'');
$view .= '<div class="tabs">
<a href="#">'.$tab3.'</a>

View File

@@ -1,7 +1,7 @@
<?php
defined(page_security_key) or exit;
if (debug && debug_id == $_SESSION['id']){
if (debug && debug_id == $_SESSION['authorization']['id']){
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
@@ -15,15 +15,15 @@ $_SESSION['prev_origin'] = '';
$page = 'accounts';
//Check if allowed
if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}
//PAGE Security
$page_manage = 'account_manage';
$update_allowed = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'U');
$delete_allowed = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'D');
$create_allowed = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'C');
$update_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
//GET PARAMETERS
$pagination_page = isset($_GET['p']) ? $_GET['p'] : 1;

15
api.php
View File

@@ -1,6 +1,7 @@
<?php
define('secure_34563$52', true);
//------------------------------------------
// Get DATA from API
//------------------------------------------
@@ -16,6 +17,17 @@ require_once './assets/functions.php';
include './settings/settings_redirector.php';
include './settings/config_redirector.php';
if (debug){
set_error_handler(function($errno, $errstr, $errfile, $errline) {
debuglog("PHP ERROR [$errno]: $errstr in $errfile on line $errline");
return false; // Let PHP handle as usual (optional)
});
set_exception_handler(function($exception) {
debuglog("PHP EXCEPTION: " . $exception->getMessage() . " in " . $exception->getFile() . " on line " . $exception->getLine());
});
}
//------------------------------------------
// Header security - enabled via config
//------------------------------------------
@@ -154,7 +166,8 @@ if($is_jwt_valid && str_contains($version, 'v')) {
// First check if endPoint is fileUpload
//------------------------------------------
$fileUploadEndpoints = [
'media_upload'
'media_upload',
'marketing_upload'
];
$isFileUploadEndpoint = in_array($collection, $fileUploadEndpoints);

BIN
api/.DS_Store vendored

Binary file not shown.

View File

@@ -15,7 +15,7 @@ $user_data = $stmt->fetch();
//Define User data
$partnerhierarchy = $user_data['partnerhierarchy'];
$permission = userRights($user_data['view']);
$profile= getProfile($user_data['settings'],$permission);
$profile= getUserPermissions($pdo, $user_data['id']);
$username = $user_data['username'];
$useremail = $user_data['email'];
$servicekey = $user_data['service'];

View File

@@ -56,7 +56,9 @@ if (!empty($post_content['sn']) && !empty($post_content['testdetails'])) {
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
$user = $username;
$account = $partnerhierarchy; //string
$current_date = date("Y-m-d");
$service_date = date("Y-m-d", strtotime("+" . SERVICE_MONTHS . " months"));
$warranty_date = date("Y-m-d", strtotime("+" . WARRANTY_MONTHS . " months"));
$order_send_date = date("Y-m-d");
$input_type = $post_content['type'];
$testdetails = json_encode($post_content['testdetails']);
$serial = $post_content['sn'];
@@ -187,9 +189,9 @@ if (!empty($post_content['sn']) && !empty($post_content['testdetails'])) {
// Create equipment when not exist +++++++++++++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
if ($equipmentCreate == 1 && $total_equipment == 0){
$sql = 'INSERT INTO equipment (productrowid,created,createdby,status,accounthierarchy,serialnumber,service_date,warranty_date) VALUES (?,?,?,?,?,?,?,?)';
$sql = 'INSERT INTO equipment (productrowid,created,createdby,status,accounthierarchy,serialnumber,service_date,warranty_date,order_send_date) VALUES (?,?,?,?,?,?,?,?,?)';
$stmt = $pdo->prepare($sql);
$stmt->execute([$productrowid,$date,$user,$status0,$account,$serial,$current_date,$current_date]);
$stmt->execute([$productrowid,$date,$user,$status0,$account,$serial,$service_date,$warranty_date,$order_send_date]);
$rowID = $pdo->lastInsertId();
}
@@ -311,7 +313,7 @@ if (!empty($post_content['sn']) && !empty($post_content['testdetails'])) {
//Update Equipment record
$sql = "UPDATE equipment SET service_date = ? $whereclause";
$stmt = $pdo->prepare($sql);
$stmt->execute([$current_date]);
$stmt->execute([$service_date]);
}
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++

BIN
api/v1/.DS_Store vendored

Binary file not shown.

View File

@@ -17,11 +17,13 @@ if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} el
//default whereclause
$whereclause = '';
switch ($permission) {
case '4':
$hierarchy_level = getHierarchyLevel($partner);
switch ($hierarchy_level) {
case '0':
$whereclause = '';
break;
case '3':
case '1':
$condition = '__salesid___'.$partner->salesid.'___soldto___%';
$whereclause = 'WHERE c.accounthierarchy like :condition AND u.view IN (4,5)';
break;
@@ -29,7 +31,11 @@ switch ($permission) {
$condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search;
$whereclause = 'WHERE c.accounthierarchy like :condition AND u.view IN (1,2,3)';
break;
default:
case '3':
$condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search.'___shipto___'.substr($partner->shipto, 0, strpos($partner->shipto, "-")).'%';
$whereclause = 'WHERE c.accounthierarchy like :condition AND u.view IN (1,2,3)';
break;
case '4':
$condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search.'___shipto___'.substr($partner->shipto, 0, strpos($partner->shipto, "-")).'%___location___'.substr($partner->location, 0, strpos($partner->location, "-")).'%';
$whereclause = 'WHERE c.accounthierarchy like :condition AND u.view IN (1,2,3)';
break;

View File

@@ -14,11 +14,13 @@ if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} el
//default whereclause
$whereclause = '';
switch ($permission) {
case '4':
$hierarchy_level = getHierarchyLevel($partner);
switch ($hierarchy_level) {
case '0':
$whereclause = '';
break;
case '3':
case '1':
$condition = '__salesid___'.$partner->salesid.'___soldto___%';
$whereclause = 'WHERE e.accounthierarchy like :condition ';
break;
@@ -26,11 +28,16 @@ switch ($permission) {
$condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search;
$whereclause = 'WHERE e.accounthierarchy like :condition AND (type = "'.$type1.'" or type = "'.$type2.'" or type = "'.$type3.'" or type = "'.$type9.'" or type = "'.$type14.'" or type = "'.$type16.'")';
break;
default:
case '3':
$condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search.'___shipto___'.substr($partner->shipto, 0, strpos($partner->shipto, "-")).'%___location___'.$soldto_search;
$whereclause = 'WHERE e.accounthierarchy like :condition AND (type = "'.$type1.'" or type = "'.$type2.'" or type = "'.$type3.'" or type = "'.$type14.'" or type = "'.$type16.'")';
break;
case '4':
$condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search.'___shipto___'.substr($partner->shipto, 0, strpos($partner->shipto, "-")).'%___location___'.substr($partner->location, 0, strpos($partner->location, "-")).'%';
$whereclause = 'WHERE e.accounthierarchy like :condition AND (type = "'.$type1.'" or type = "'.$type2.'" or type = "'.$type3.'" or type = "'.$type14.'" or type = "'.$type16.'")';
break;
}
//NEW ARRAY
$criterias = [];
$clause = '';

View File

@@ -17,7 +17,7 @@ if ($stmt->rowCount() == 1) {
//Define User data
$partnerhierarchy = $user_data['partnerhierarchy'];
$permission = userRights($user_data['view']);
$profile= getProfile($user_data['settings'],$permission);
$profile= getUserPermissions($pdo, $user_data['id']);
$username = $user_data['username'];
$useremail = $user_data['email'];
$servicekey = $user_data['service'];

View File

@@ -40,7 +40,7 @@ if ($id != ''){
$salesid_new = (($post_content['salesid'] != '' && $post_content['salesid'] != $accounthierarchy_old->salesid)? $post_content['salesid'] : $accounthierarchy_old->salesid);
$soldto_new = (($post_content['soldto'] != '' && $post_content['soldto'] != $accounthierarchy_old->soldto)? $post_content['soldto'] : $accounthierarchy_old->soldto);
if ($permission == 3 || $permission == 4){
if (getHierarchyLevel($partner) == 1 || getHierarchyLevel($partner) == 0){
//ADMIN ONLY ARE ALLOWED TO CHANGE SALES AND SOLD
$account = array(
"salesid"=>$salesid_new,

View File

@@ -15,7 +15,6 @@ if ($action !=''){
//Connect to DB
//------------------------------------------
$pdo = dbConnect($dbname);
$pdo2 = dbConnect($dbname);
//------------------------------------------
//CONTENT FROM API (POST)
@@ -28,11 +27,13 @@ if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} el
//default whereclause
$whereclause = 'WHERE';
switch ($permission) {
case '4':
$hierarchy_level = getHierarchyLevel($partner);
switch ($hierarchy_level) {
case '0':
$whereclause .= '';
break;
case '3':
case '1':
$condition = '__salesid___'.$partner->salesid.'___soldto___%';
$whereclause = ' e.accounthierarchy like "'.$condition.'" AND ';
break;
@@ -40,7 +41,11 @@ switch ($permission) {
$condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search;
$whereclause .= ' e.accounthierarchy like "'.$condition.'" AND ';
break;
default:
case '3':
$condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search.'___shipto___'.substr($partner->shipto, 0, strpos($partner->shipto, "-")).'%';
$whereclause .= ' e.accounthierarchy like "'.$condition.'" AND ';
break;
case '4':
$condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search.'___shipto___'.substr($partner->shipto, 0, strpos($partner->shipto, "-")).'%___location___'.substr($partner->location, 0, strpos($partner->location, "-")).'%';
$whereclause .= ' e.accounthierarchy like "'.$condition.'" AND ';
break;
@@ -86,10 +91,68 @@ switch ($action) {
$message_box = [];
$timestamp = date("Y-m-d H:i:s");
// --------------------------------------------
// Check if multiple serialnumbers are provided
// --------------------------------------------
// Normalize input to always be an array
$serial_numbers = is_array($post_content['sn']) ? $post_content['sn'] : [$post_content['sn']];
foreach ($serial_numbers as $sn) {
// Get equipment ID based on serial number
$sql = 'SELECT rowID, warranty_date, order_send_date from equipment where serialnumber = ?';
$stmt = $pdo->prepare($sql);
$stmt->execute([$sn]);
$rowID = $stmt->fetch();
if (!$rowID['rowID']) {
// Serial number not recognized
$message_box[] = $sn . ' - ' . $register_message_1;
continue;
}
// Check if under warranty
$warranty_types = [$type9, $type10, $type11, $type12];
$warranty_condition = 'equipmentid="' . $rowID['rowID'] . '" && (type="' . implode('" || type="', $warranty_types) . '")';
$warranty = getrowID($dbname, 'rowID', 'equipment_history', $warranty_condition);
if ($warranty) {
// Already under contract
$message_box[] = $sn . ' - ' . $register_message_2;
$communication_check = 1;
continue;
}
//define warranty_end_date
$order_send_date = $rowID['order_send_date'] ?? $rowID['warranty_date'];
// Check if order_send_date is available
if (empty($order_send_date)) {
// No valid date found - skip this serial number
$message_box[] = $sn . ' - ' . $register_message_1; // or create a specific message for missing date
continue;
}
// Calculate warranty end date based on eligibility window
$current_date = new DateTime();
$order_date = new DateTime($order_send_date);
$months_diff = $current_date->diff($order_date)->m + ($current_date->diff($order_date)->y * 12);
if ($months_diff <= WARRANTY_ELIGIBILITY_WINDOW) {
// Within eligibility window - apply extended warranty
$warranty_end_date = (clone $order_date)->modify('+' . WARRANTY_EXTENDED_MONTH . ' months')->format('Y-m-d');
} else {
// Outside eligibility window - apply standard warranty
$warranty_end_date = (clone $order_date)->modify('+' . WARRANTY_MONTHS . ' months')->format('Y-m-d');
}
// Not under warranty - process registration
$firmware_account_send = 1;
//Create history description
$history_description = [
"start_date"=>$timestamp,
"end_date"=>date("Y-m-d", strtotime("+730 days")),
"end_date"=> $warranty_end_date,
"organization"=>strip_tags(trim($post_content['organization'])),
"phone"=>strip_tags(trim($post_content['phone'])),
"city"=>strip_tags(trim($post_content['city'])),
@@ -99,42 +162,24 @@ switch ($action) {
];
$description = json_encode($history_description, JSON_UNESCAPED_UNICODE);
// --------------------------------------------
// Check if multiple serialnumbers are provided
// --------------------------------------------
if(is_array($post_content['sn'])){
foreach ($post_content['sn'] as $sn){
//Get equipmentid based on rowID
$rowID = getrowID($dbname,'rowID','equipment','serialnumber="'.$sn.'"');
if ($rowID){
//check if under warranty
$warranty = getrowID($dbname,'rowID','equipment_history','equipmentid="'.$rowID['rowID'].'" && (type="'.$type9.'" || type="'.$type10.'" || type="'.$type11.'" || type="'.$type12.'")');
if ($warranty){
// --------------------------------------------
// Already under contract
// --------------------------------------------
//Serialnumber under warranty
$message_box[] = $sn.' - '.$register_message_2;
$communication_check = 1;
} else
{
// --------------------------------------------
// Not under warranty
// --------------------------------------------
//Send user firmware account
$firmware_account_send = 1;
//create history
// Prepare queries
// Create history entry
$sql = 'INSERT INTO equipment_history (equipmentid, type, description, created, createdby, updatedby) VALUES (?,?,?,?,?,?)';
$stmt = $pdo->prepare($sql);
$stmt->execute([$rowID['rowID'],$type9,$description,$timestamp,$post_content['email'],$post_content['email']]);
$stmt->execute([
$rowID['rowID'],
$type9,
$description,
$timestamp,
$post_content['email'],
$post_content['email']
]);
//GET PARTNER DETAILS OF EQUIPMENT
// Get partner details of equipment
$partner_equipment = getrowID($dbname, 'accounthierarchy', 'equipment', 'rowID="' . $rowID['rowID'] . '"');
$partner_equipment = json_decode($partner_equipment['accounthierarchy']);
//Setup partnerhierarchy (salesID)
// Setup partner hierarchy
$partnerhierarchy = [
"salesid" => $partner_equipment->salesid,
"soldto" => $partner_equipment->soldto
@@ -142,181 +187,51 @@ switch ($action) {
// Setup variables for partner
$partnername = $post_content['organization'];
$partnernotes = 'created based on user registration';
$salesID = json_encode($partnerhierarchy, JSON_UNESCAPED_UNICODE);
$createdby = 'system';
//Check if shipto is empty and if empty search partner or create
if ($partner_equipment->shipto == ''){
$partner_shipto = getrowID($dbname,'partnerID','partner','partnername = "'.$partnername.'" && partnertype="'.$partnertype3.'"');
if ($partner_shipto){
//Partner exists - Use it
$partnerhierarchy['shipto'] = $partner_shipto['partnerID'].'-'.$partnername;
} else {
//Partner does not exist create
// Helper function to get or create partner
$getOrCreatePartner = function($partnertype) use ($dbname, $partnername, $salesID, $createdby, $pdo) {
$partner = getrowID($dbname, 'partnerID', 'partner', 'partnername = "' . $partnername . '" && partnertype="' . $partnertype . '"');
if ($partner) {
return $partner['partnerID'] . '-' . $partnername;
}
// Partner does not exist - create
$sql = 'INSERT INTO partner (partnertype, partnername, salesID, createdby, status) VALUES (?,?,?,?,?)';
$stmt = $pdo2->prepare($sql);
$stmt->execute([$partnertype3,$partnername,$salesID,$createdby,'1']);
$stmt = $pdo->prepare($sql);
$stmt->execute([$partnertype, $partnername, $salesID, $createdby, '1']);
//Get rowID of created partner and use it
$partner_rowid = $pdo2->lastInsertId();
$partnerhierarchy['shipto'] = $partner_rowid.'-'.$partnername;
}
} else {
// Shipto exist use it
$partnerhierarchy['shipto'] = $partner_equipment->shipto;
}
//Check if location is empty and if empty search partner or create
if ($partner_equipment->location == ''){
$partner_location = getrowID($dbname,'partnerID','partner','partnername = "'.$partnername.'" && partnertype="'.$partnertype4.'"');
if ($partner_location){
//Partner exists - Use it
$partnerhierarchy['location'] = $partner_location['partnerID'].'-'.$partnername;
$partner_rowid = $pdo->lastInsertId();
return $partner_rowid . '-' . $partnername;
};
} else {
//Partner does not exist create
$sql = 'INSERT INTO partner (partnertype,partnername,salesID,createdby,status) VALUES (?,?,?,?,?)';
$stmt = $pdo2->prepare($sql);
$stmt->execute([$partnertype4,$partnername,$salesID,$createdby,'1']);
// Handle shipto
$partnerhierarchy['shipto'] = empty($partner_equipment->shipto)
? $getOrCreatePartner($partnertype3)
: $partner_equipment->shipto;
//Get rowID of created partner and use it
$partner_rowid = $pdo2->lastInsertId();
$partnerhierarchy['location'] = $partner_rowid.'-'.$partnername;
}
// Handle location
$partnerhierarchy['location'] = empty($partner_equipment->location)
? $getOrCreatePartner($partnertype4)
: $partner_equipment->location;
} else {
// Location exist use it
$partnerhierarchy['location'] = $partner_equipment->location;
}
$partnerhierarchy_json = json_encode($partnerhierarchy, JSON_UNESCAPED_UNICODE);
$shipto = $partnerhierarchy['shipto'] ?? '';
$partnerhierarchy = json_encode($partnerhierarchy, JSON_UNESCAPED_UNICODE);
// --------------------------------------------
// Update equipment record warranty_date, partnerhierarchy, status equipment
// --------------------------------------------
// Update equipment record
$sql = 'UPDATE equipment SET status = ?, warranty_date = ?, accounthierarchy = ?, updatedby = ? WHERE rowID = ?';
$stmt = $pdo->prepare($sql);
$stmt->execute(['4',$warranty_extended,$partnerhierarchy,$username,$rowID['rowID']]);
$stmt->execute(['4', $warranty_end_date, $partnerhierarchy_json, $username, $rowID['rowID']]);
// Add warranty to changelog
$warranty_user = $post_content['email'] ?? 'system';
changelog($dbname,'equipment',$rowID['rowID'],'Warranty',$warranty_extended,$warranty_user);
changelog($dbname, 'equipment', $rowID['rowID'], 'Warranty', $warranty_end_date, $warranty_user);
// Serial number recognized
$message_box[] = $sn . ' - ' . $register_message_3;
$communication_check = 1;
}
} else {
//Serialnumber not recognized
$message_box[] = $sn.' - '.$register_message_1;
}
}
}
else {
// --------------------------------------------
//Get equipmentid based on rowID
// --------------------------------------------
$rowID = getrowID($dbname,'rowID','equipment','serialnumber="'.$post_content['sn'].'"');
if ($rowID){
//check if under warranty
$warranty = getrowID($dbname,'rowID','equipment_history','equipmentid="'.$rowID['rowID'].'" && (type="'.$type9.'" || type="'.$type10.'" || type="'.$type11.'" || type="'.$type12.'")');
if ($warranty){
// --------------------------------------------
// Already under contract
// --------------------------------------------
//Serialnumber not recognized
$message_box[] = $post_content['sn'].' - '.$register_message_2;
} else
{
// --------------------------------------------
// Not under warranty
// --------------------------------------------
$firmware_account_send = 1;
//create history
$sql = 'INSERT INTO equipment_history (equipmentid, type, description, created, createdby, updatedby) VALUES (?,?,?,?,?,?)';
$stmt = $pdo->prepare($sql);
$stmt->execute([$rowID['rowID'],$type9,$description,$timestamp,$post_content['email'],$post_content['email']]);
//GET PARTNER DETAILS OF EQUIPMENT
$partner_equipment = getrowID($dbname,'accounthierarchy','equipment','rowID="'.$rowID['rowID'].'"');
$partner_equipment = json_decode($partner_equipment['accounthierarchy']);
//Setup partnerhierarchy (salesID)
$partnerhierarchy =[
"salesid"=>$partner_equipment->salesid,
"soldto"=>$partner_equipment->soldto
];
//Setup variables for partner
$partnername = $post_content['organization'];
$partnernotes = 'created based on user registration';
$salesID = json_encode($partnerhierarchy, JSON_UNESCAPED_UNICODE);
$createdby = 'system';
//Check if shipto is empty and if empty search partner or create
if ($partner_equipment->shipto == ''){
$partner_shipto = getrowID($dbname,'partnerID','partner','partnername = "'.$partnername.'" && partnertype="'.$partnertype3.'"');
if ($partner_shipto){
//Partner exists - Use it
$partnerhierarchy['shipto'] = $partner_shipto['partnerID'].'-'.$partnername;
} else {
//Partner does not exist create
$sql = 'INSERT INTO partner (partnertype, partnername,salesID,createdby,status) VALUES (?,?,?,?,?)';
$stmt = $pdo2->prepare($sql);
$stmt->execute([$partnertype3,$partnername,$salesID,$createdby,'1']);
//Get rowID of created partner and use it
$partner_rowid = $pdo2->lastInsertId();
$partnerhierarchy['shipto'] = $partner_rowid.'-'.$partnername;
}
} else {
// Shipto exist use it
$partnerhierarchy['shipto'] = $partner_equipment->shipto;
}
//Check if location is empty and if empty search partner or create
if ($partner_equipment->location == ''){
$partner_location = getrowID($dbname,'partnerID','partner','partnername = "'.$partnername.'" && partnertype="'.$partnertype4.'"');
if ($partner_location){
//Partner exists - Use it
$partnerhierarchy['location'] = $partner_location['partnerID'].'-'.$partnername;
} else {
//Partner does not exist create
$sql = 'INSERT INTO partner (partnertype,partnername,salesID,createdby,status) VALUES (?,?,?,?,?)';
$stmt = $pdo2->prepare($sql);
$stmt->execute([$partnertype4,$partnername,$salesID,$createdby,'1']);
//Get rowID of created partner and use it
$partner_rowid = $pdo2->lastInsertId();
$partnerhierarchy['location'] = $partner_rowid.'-'.$partnername;
}
} else {
// Location exist use it
$partnerhierarchy['location'] = $partner_equipment->location;
}
$partnerhierarchy = json_encode($partnerhierarchy, JSON_UNESCAPED_UNICODE);
// --------------------------------------------
// Update equipment record warranty_date, partnerhierarchy, status equipment
// --------------------------------------------
$sql = 'UPDATE equipment SET status = ?, warranty_date = ?, accounthierarchy = ?, updatedby = ? WHERE rowID = ?';
$stmt = $pdo->prepare($sql);
$stmt->execute(['4',$warranty_extended,$partnerhierarchy,$username,$rowID['rowID']]);
//Add warranty to changelog
$warranty_user = $post_content['email'] ?? 'system';
changelog($dbname,'equipment',$rowID['rowID'],'Warranty',$warranty_extended,$warranty_user);
//Serialnumber recognized
$message_box[] = $post_content['sn'].' - '.$register_message_3;
}
}
else {
//Serialnumber not recognized
$message_box[] = $post_content['sn'].' - '.$register_message_1;
}
}
// --------------------------------------------
// Send generic account to user for software updates

View File

@@ -58,7 +58,7 @@ if ($id != ''){
$shipto_new = (($post_content['shipto'] != '' && $post_content['shipto'] != $contract_old->shipto)? $post_content['shipto'] : $contract_old->shipto);
$location_new = (($post_content['location'] != '' && $post_content['location'] != $contract_old->location)? $post_content['location'] : $contract_old->location);
if ($permission == 4){
if (getHierarchyLevel($partner) == 0){
//ADMIN+ ONLY ARE ALLOWED TO CHANGE SALES AND SOLD
$account = array(
"salesid"=>$salesid_new,
@@ -67,7 +67,7 @@ if ($id != ''){
"location"=>$location_new
);
}
elseif ($permission == 3) {
elseif (getHierarchyLevel($partner) == 1) {
//ADMIN ONLY ARE ALLOWED TO CHANGE SOLD
$account = array(
"salesid"=>$contract_old->salesid,
@@ -120,7 +120,7 @@ if ($id != ''){
}
else {
//ID is empty => INSERT / NEW RECORD
if ($permission == 4){
if (getHierarchyLevel($partner) == 0){
$account = array(
"salesid"=>$post_content['salesid'],
"soldto"=>$post_content['soldto'],
@@ -128,7 +128,7 @@ else {
"location"=>$post_content['location']
);
}
elseif ($permission == 3){
elseif (getHierarchyLevel($partner) == 1){
$account = array(
"salesid"=>$partner->salesid,
"soldto"=>$post_content['soldto'],
@@ -160,7 +160,7 @@ if (isset($post_content['servicetool'])){
if (isset($post_content['ignore_list'])){
$post_content['ignore_list'] = json_encode($post_content['ignore_list'], JSON_UNESCAPED_UNICODE);
//ONLY ADMINS ARE ALLOWED TO UPDATE IGNORE LIST
if ($permission != 3 && $permission != 4){
if (getHierarchyLevel($partner) != 1 && getHierarchyLevel($partner) != 0){
unset($post_content['ignore_list']);
}
}

View File

@@ -47,7 +47,7 @@ if ($id != ''){
$owner_equipment = (($equipment_data['createdby'] == $username)? 1 : 0);
if ($permission == 4){
if (getHierarchyLevel($partner) == 0){
//ADMIN+ ONLY ARE ALLOWED TO CHANGE SALES AND SOLD
$account = array(
"salesid"=>$salesid_new,
@@ -57,7 +57,7 @@ if ($id != ''){
"section"=>$section_new
);
}
elseif ($permission == 3) {
elseif (getHierarchyLevel($partner) == 1) {
//ADMIN ONLY ARE ALLOWED TO CHANGE SOLD
$account = array(
"salesid"=>$equipment_old->salesid,
@@ -79,7 +79,7 @@ if ($id != ''){
}
else {
//ID is empty => INSERT / NEW RECORD
if ($permission == 4){
if (getHierarchyLevel($partner) == 0){
$account = array(
"salesid"=>$post_content['salesid'],
"soldto"=>$post_content['soldto'],
@@ -89,7 +89,7 @@ else {
);
}
elseif ($permission == 3){
elseif (getHierarchyLevel($partner) == 1){
$account = array(
"salesid"=>$partner->salesid,
"soldto"=>$post_content['soldto'],

View File

@@ -41,7 +41,7 @@ if ($id != ''){
$salesid_new = (($post_content['salesid'] != '' && $post_content['salesid'] != $partnerhierarchy_old->salesid)? $post_content['salesid'] : $partnerhierarchy_old->salesid);
$soldto_new = (($post_content['soldto'] != '' && $post_content['soldto'] != $partnerhierarchy_old->soldto)? $post_content['soldto'] : $partnerhierarchy_old->soldto);
if ($permission == 3 || $permission == 4){
if (getHierarchyLevel($partner) == 1 || getHierarchyLevel($partner) == 0){
//ADMIN ONLY ARE ALLOWED TO CHANGE SALES AND SOLD
$account = array(
"salesid"=>$salesid_new,
@@ -56,7 +56,7 @@ if ($id != ''){
}
else {
//ID is empty => INSERT / NEW RECORD
if ($permission == 3 || $permission == 4){
if (getHierarchyLevel($partner) == 1 || getHierarchyLevel($partner) == 0){
//ADMIN ONLY ARE ALLOWED TO CHANGE SALES AND SOLD
$account = array(
"salesid"=>$partner->salesid,

View File

@@ -50,7 +50,7 @@ $soldto_new = ((isset($post_content['soldto']) && $post_content['soldto'] != ''
$shipto_new = (($post_content['shipto'] != '' && $post_content['shipto'] != $partnerhierarchy_old->shipto)? $post_content['shipto'] : $partnerhierarchy_old->shipto);
$location_new = (($post_content['location'] != '' && $post_content['location'] != $partnerhierarchy_old->location)? $post_content['location'] : $partnerhierarchy_old->location);
if ($permission == 4){
if (getHierarchyLevel($partner) == 0){
//ADMIN+ ONLY ARE ALLOWED TO CHANGE SALES AND SOLD
$account = array(
"salesid"=>$salesid_new,
@@ -58,7 +58,7 @@ $location_new = (($post_content['location'] != '' && $post_content['location'] !
"shipto"=>$shipto_new,
"location"=>$location_new
);
}elseif ($permission == 3) {
}elseif (getHierarchyLevel($partner) == 1) {
//ADMIN ONLY ARE ALLOWED TO CHANGE SOLD
$account = array(
"salesid"=>$partner->salesid,
@@ -77,7 +77,7 @@ $location_new = (($post_content['location'] != '' && $post_content['location'] !
}
} elseif ($command == 'insert') {
//ID is empty => INSERT / NEW RECORD
if ($permission == 4){
if (getHierarchyLevel($partner) == 0){
//ADMIN+ ONLY ARE ALLOWED TO CHANGE SALES AND SOLD
$account = array(
"salesid"=>$post_content['salesid'],
@@ -86,7 +86,7 @@ $location_new = (($post_content['location'] != '' && $post_content['location'] !
"location"=>$post_content['location']
);
}
elseif ($permission == 3){
elseif (getHierarchyLevel($partner) == 1){
//ADMIN ONLY ARE ALLOWED TO CHANGE SOLD
$account = array(
"salesid"=>$partner->salesid,

BIN
api/v2/.DS_Store vendored

Binary file not shown.

BIN
api/v2/get/.DS_Store vendored

Binary file not shown.

View File

@@ -0,0 +1,158 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// Access Elements
//------------------------------------------
//Connect to DB
$pdo = dbConnect($dbname);
//------------------------------------------
//NEW ARRAY
//------------------------------------------
$criterias = [];
$clause = '';
//------------------------------------------
//Check for $_GET variables and build up clause
//------------------------------------------
if(isset($get_content) && $get_content!=''){
//GET VARIABLES FROM URL
$requests = explode("&", $get_content);
//Check for keys and values
foreach ($requests as $y){
$v = explode("=", $y);
//INCLUDE VARIABLES IN ARRAY
$criterias[$v[0]] = $v[1];
if ($v[0] == 'page' || $v[0] =='p' || $v[0] =='totals' || $v[0] =='success_msg' || $v[0] =='sort' || $v[0] =='all'){
//do nothing
}
elseif ($v[0] == 'rowid') {
//build up search by ID
$clause .= ' AND a.rowID = :'.$v[0];
}
elseif ($v[0] == 'status') {
//Update status based on status
$clause .= ' AND a.is_active = :'.$v[0];
}
elseif ($v[0] == 'search') {
//build up search
$clause .= ' AND (a.access_name LIKE :'.$v[0].' OR a.access_path LIKE :'.$v[0].' OR a.description LIKE :'.$v[0].')';
}
elseif ($v[0] == 'access_path') {
//build up path search
$clause .= ' AND a.access_path = :'.$v[0];
}
else {
//create clause
$clause .= ' AND a.'.$v[0].' = :'.$v[0];
}
}
}
//Build WHERE clause
$whereclause = '';
if ($clause != ''){
$whereclause = 'WHERE '.substr($clause, 4);
}
// GET SORT INDICATOR
$sort_indicator = $criterias['sort'] ?? '';
switch ($sort_indicator){
case 1:
$sort = ' a.access_name ASC ';
break;
case 2:
$sort = ' a.access_name DESC ';
break;
case 3:
$sort = ' a.access_path ASC ';
break;
case 4:
$sort = ' a.access_path DESC ';
break;
default:
$sort = ' a.access_name ASC ';
break;
}
if (isset($criterias['totals']) && $criterias['totals'] ==''){
//Request for total rows
$sql = 'SELECT count(*) as count FROM access_elements a '.$whereclause;
}
elseif (isset($criterias['all']) && $criterias['all'] ==''){
//Return all records (no paging)
$sql = 'SELECT a.* FROM access_elements a '.$whereclause.' ORDER BY '.$sort;
}
else {
//SQL
$sql = 'SELECT a.* FROM access_elements a '.$whereclause.' ORDER BY '.$sort.' LIMIT :page,:num_rows';
}
$stmt = $pdo->prepare($sql);
//------------------------------------------
//Bind to query
//------------------------------------------
if (!empty($criterias)){
foreach ($criterias as $key => $value){
$key_condition = ':'.$key;
if (str_contains($sql, $key_condition)){
if ($key == 'search'){
$search_value = '%'.$value.'%';
$stmt->bindValue($key, $search_value, PDO::PARAM_STR);
}
elseif ($key == 'p'){
//Do nothing (bug)
}
else {
$stmt->bindValue($key, $value, PDO::PARAM_STR);
}
}
}
}
//------------------------------------------
// Debuglog
//------------------------------------------
if (debug){
$message = $date.';'.$sql.';'.$username;
debuglog($message);
}
//------------------------------------------
//Add paging details
//------------------------------------------
$page_rows = $page_rows_equipment ?? 20;
if(isset($criterias['totals']) && $criterias['totals']==''){
$stmt->execute();
$messages = $stmt->fetch();
$messages = $messages[0];
}
elseif(isset($criterias['all']) && $criterias['all']==''){
//Return all records (no paging)
$stmt->execute();
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
else {
$current_page = isset($criterias['p']) && is_numeric($criterias['p']) ? (int)$criterias['p'] : 1;
$stmt->bindValue('page', ($current_page - 1) * $page_rows, PDO::PARAM_INT);
$stmt->bindValue('num_rows', $page_rows, PDO::PARAM_INT);
//Execute Query
$stmt->execute();
//Get results
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
//------------------------------------------
//JSON_EnCODE
//------------------------------------------
$messages = json_encode($messages, JSON_UNESCAPED_UNICODE);
//------------------------------------------
//Send results
//------------------------------------------
echo $messages;
?>

View File

@@ -17,11 +17,13 @@ if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} el
//default whereclause
$whereclause = '';
switch ($permission) {
case '4':
$hierarchy_level = getHierarchyLevel($partner);
switch ($hierarchy_level) {
case '0':
$whereclause = '';
break;
case '3':
case '1':
$condition = '__salesid___'.$partner->salesid.'___soldto___%';
$whereclause = 'WHERE accounthierarchy like :condition AND u.view IN (4,5)';
break;
@@ -29,7 +31,11 @@ switch ($permission) {
$condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search;
$whereclause = 'WHERE accounthierarchy like :condition AND u.view IN (1,2,3)';
break;
default:
case '3':
$condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search.'___shipto___'.substr($partner->shipto, 0, strpos($partner->shipto, "-")).'%';
$whereclause = 'WHERE accounthierarchy like :condition AND u.view IN (1,2,3)';
break;
case '4':
$condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search.'___shipto___'.substr($partner->shipto, 0, strpos($partner->shipto, "-")).'%___location___'.substr($partner->location, 0, strpos($partner->location, "-")).'%';
$whereclause = 'WHERE accounthierarchy like :condition AND u.view IN (1,2,3)';
break;

View File

@@ -0,0 +1,116 @@
<?php
defined($security_key) or exit;
// Database connection
$pdo = dbConnect($dbname);
// ============================================
// Input Validation & Sanitization
// ============================================
$filters = [
'serialnumber' => isset($_GET['serialnumber']) ? trim($_GET['serialnumber']) : null,
'type' => isset($_GET['type']) ? trim($_GET['type']) : null,
'start' => isset($_GET['start']) ? trim($_GET['start']) : date("Y-m-d", strtotime("-270 days")),
'end' => isset($_GET['end']) ? trim($_GET['end']) : date("Y-m-d", strtotime("+1 days"))
];
// ============================================
// Build Query with Prepared Statements
// ============================================
$whereClauses = [];
$params = [];
// Serial Number Filter
if ($filters['serialnumber']) {
$whereClauses[] = 'h.description LIKE :serialnumber';
$params[':serialnumber'] = "%historycreated%SN%:" . $filters['serialnumber'] . "%";
$whereClauses[] = 'h.type != :excluded_type';
$params[':excluded_type'] = 'SRIncluded';
}
// Type Filter
if ($filters['type']) {
if ($filters['type'] === 'latest') {
// Get only the latest record per equipment
if ($filters['serialnumber']) {
$whereClauses[] = 'h.rowID IN (
SELECT MAX(h2.rowID)
FROM equipment_history h2
GROUP BY h2.equipmentid
)';
} else {
$whereClauses[] = "h.description LIKE '%historycreated%'";
$whereClauses[] = 'h.rowID IN (
SELECT MAX(h2.rowID)
FROM equipment_history h2
WHERE h2.description LIKE :history_created
GROUP BY h2.equipmentid
)';
$params[':history_created'] = '%historycreated%';
}
} else {
// Specific type filter
$whereClauses[] = 'h.type = :type';
$params[':type'] = $filters['type'];
}
}
// Default filter if no other filters applied
if (empty($whereClauses)) {
$whereClauses[] = "h.description LIKE '%historycreated%'";
}
// Date Range Filter
$whereClauses[] = 'h.created BETWEEN :start_date AND :end_date';
$params[':start_date'] = $filters['start'];
$params[':end_date'] = $filters['end'];
// ============================================
// Execute Query
// ============================================
$whereClause = 'WHERE ' . implode(' AND ', $whereClauses);
$sql = "SELECT h.rowID, h.description
FROM equipment_history h
$whereClause
ORDER BY h.created DESC";
try {
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
// ============================================
// Format Response
// ============================================
$results = [];
foreach ($messages as $message) {
$record = json_decode($message['description'], true);
// Handle JSON decode errors
if (json_last_error() !== JSON_ERROR_NONE) {
continue; // Skip invalid JSON
}
$record['historyID'] = (int)$message['rowID'];
$results[] = $record;
}
// Set proper headers
header('Content-Type: application/json; charset=utf-8');
echo json_encode($results, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
} catch (PDOException $e) {
// Log error (don't expose to client in production)
error_log("Database error: " . $e->getMessage());
//header('Content-Type: application/json; charset=utf-8', true, 500);
echo json_encode([
'error' => 'An error occurred while processing your request'
]);
}
?>

View File

@@ -143,6 +143,10 @@ if(isset($get_content) && $get_content!=''){
$clause .= ' AND e.serialnumber IN (:'.$v[0].')';
}
}
elseif ($v[0] == 'validate') {
// Set validation mode flag
$validation_mode = true;
}
elseif ($v[0] == 'firmware') {
//Assets with firmaware upgrade = 0 (1=latest version, 2=No software)
$clause .= ' AND e.status != 5 AND e.sw_version_latest = 0';
@@ -161,7 +165,7 @@ if(isset($get_content) && $get_content!=''){
}
}
if ($sw_version_latest_update == 1){
if ($sw_version_latest_update == 1 || $clause == ''){
//------------------------------------------
//UPDATE SW_STATUS
//------------------------------------------
@@ -175,6 +179,10 @@ if (isset($criterias['download']) && $criterias['download'] ==''){
//Request for download
$sql = 'SELECT e.rowID as equipmentID, e.*, p.productcode, p.productname from equipment e LEFT JOIN products p ON e.productrowid = p.rowID '.$whereclause.' ORDER BY equipmentID';
}
elseif (isset($validation_mode) && $validation_mode === true) {
// Validation mode - return count only for serial validation
$sql = "SELECT count(rowID) as rowID from equipment e $whereclause";
}
elseif (isset($criterias['totals']) && $criterias['totals'] =='' && !isset($criterias['type'])){
//Request for total rows
$sql = 'SELECT count(*) as count from equipment e LEFT JOIN products p ON e.productrowid = p.rowID '.$whereclause.'';
@@ -267,7 +275,7 @@ else {
}
//SQL for Paging
$sql = 'SELECT e.rowID as equipmentID, e.*, p.productcode, p.productname, p.product_media from equipment e LEFT JOIN products p ON e.productrowid = p.rowID '.$whereclause.' ORDER BY '.$sort.' LIMIT :page,:num_products';
$sql = 'SELECT e.rowID as equipmentID, e.*, p.productcode, p.productname, p.product_media, psl.starts_at,psl.expires_at,psl.status as license_status from equipment e LEFT JOIN products p ON e.productrowid = p.rowID LEFT JOIN products_software_licenses psl ON e.sw_version_license = psl.license_key '.$whereclause.' ORDER BY '.$sort.' LIMIT :page,:num_products';
}
$stmt = $pdo->prepare($sql);
@@ -314,7 +322,19 @@ if (debug){
//------------------------------------------
//Add paging details
//------------------------------------------
if(isset($criterias['totals']) && $criterias['totals']==''){
if (isset($validation_mode) && $validation_mode === true) {
$stmt->execute();
$messages = $stmt->fetch();
if ($messages[0] == 1) {
echo json_encode(array('SN'=> TRUE));
}
else {
echo json_encode(array('SN'=> FALSE));
}
return;
}
elseif(isset($criterias['totals']) && $criterias['totals']==''){
$stmt->execute();
$messages = $stmt->fetch();
$messages = $messages[0];

View File

@@ -13,12 +13,13 @@ if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} el
//default whereclause
$whereclause = '';
$hierarchy_level = getHierarchyLevel($partner);
switch ($permission) {
case '4':
switch ($hierarchy_level) {
case '0':
$whereclause = '';
break;
case '3':
case '1':
$condition = '__salesid___'.$partner->salesid.'___soldto___%';
$whereclause = 'WHERE e.accounthierarchy like :condition ';
break;
@@ -26,14 +27,20 @@ switch ($permission) {
$condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search;
$whereclause = 'WHERE e.accounthierarchy like :condition AND (type = "'.$type1.'" or type = "'.$type2.'" or type = "'.$type3.'" or type = "'.$type9.'" or type = "'.$type14.'" or type = "'.$type16.'")';
break;
default:
case '3':
$condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search.'___shipto___'.substr($partner->shipto, 0, strpos($partner->shipto, "-")).'%___location___'.$soldto_search;
$whereclause = 'WHERE e.accounthierarchy like :condition AND (type = "'.$type1.'" or type = "'.$type2.'" or type = "'.$type3.'" or type = "'.$type14.'" or type = "'.$type16.'")';
break;
case '4':
$condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search.'___shipto___'.substr($partner->shipto, 0, strpos($partner->shipto, "-")).'%___location___'.substr($partner->location, 0, strpos($partner->location, "-")).'%';
$whereclause = 'WHERE e.accounthierarchy like :condition AND (type = "'.$type1.'" or type = "'.$type2.'" or type = "'.$type3.'" or type = "'.$type14.'" or type = "'.$type16.'")';
break;
}
//NEW ARRAY
$criterias = [];
$clause = '';
$type_check = false;
//Check for $_GET variables and build up clause
if(isset($get_content) && $get_content!=''){
@@ -64,11 +71,65 @@ if(isset($get_content) && $get_content!=''){
//build up search
$clause .= ' AND (h.rowID like :'.$v[0].' OR h.createdby like :'.$v[0].')';
}
elseif ($v[0] == 'type' && $v[1] == 'servicereport') {
elseif ($v[0] == 'serialnumber') {
//build up serialnumber
//check if multiple serialnumbers are provided
if (str_contains($v[1], ',')){
$inputs = explode(",",$v[1]);
$new_querystring = ''; //empty querystring
$x=0;
foreach($inputs as $input){
//create key
$new_key = $v[0].'_'.$x;
//inject new key/value to array
$criterias[$new_key] = $input;
$new_querystring .= ':'.$new_key.',';
$x++;
}
//remove obsolete last character from new_querystring
$new_querystring = substr($new_querystring,0, -1);
//add new_querystring to clause
$clause .= ' AND e.serialnumber IN ('.$new_querystring.')';
//remove original key/value from array
unset($criterias[$v[0]]);
}
else {
$clause .= ' AND e.serialnumber IN (:'.$v[0].')';
}
}
elseif ($v[0] == 'type') {
if ($v[1] == 'servicereport') {
//Filter out only relevant servicereports
$filter_key_1 = '"%serialnumber%"';
$filter_key_2 = '"ServiceReport"';
$clause .= ' AND h.type = '.$filter_key_2.' AND NOT e.productrowid = "31" AND h.description like '.$filter_key_1;
//remove from criterias to prevent double binding
unset($criterias[$v[0]]);
}
elseif (str_contains($v[1], ',')) {
//check if multiple types are provided
$inputs = explode(",",$v[1]);
$new_querystring = ''; //empty querystring
$x=0;
foreach($inputs as $input){
//create key
$new_key = $v[0].'_'.$x;
//inject new key/value to array
$criterias[$new_key] = $input;
$new_querystring .= ':'.$new_key.',';
$x++;
}
//remove obsolete last character from new_querystring
$new_querystring = substr($new_querystring,0, -1);
//add new_querystring to clause
$clause .= ' AND h.type IN ('.$new_querystring.')';
//remove original key/value from array
$type_check = true;
unset($criterias[$v[0]]);
}
else {
$clause .= ' AND h.type = :'.$v[0];
}
}
elseif ($v[0] == 'created') {
//build up search
@@ -89,6 +150,9 @@ if(isset($criterias['totals']) && $criterias['totals'] ==''){
//Request for total rows
$sql ='SELECT count(h.rowID) as historyID FROM equipment_history h LEFT JOIN equipment e ON h.equipmentid = e.rowID '.$whereclause.'';
}
elseif($type_check){
$sql ='SELECT h.rowID as historyID, e.rowID as equipmentID, e.serialnumber, h.type, h.description, h.created, h.createdby FROM equipment_history h LEFT JOIN equipment e ON h.equipmentid = e.rowID '.$whereclause.' ORDER BY h.created DESC';
}
else {
//request history
$sql ='SELECT h.rowID as historyID, e.rowID as equipmentID, e.serialnumber, h.type, h.description, h.created, h.createdby FROM equipment_history h LEFT JOIN equipment e ON h.equipmentid = e.rowID '.$whereclause.' ORDER BY h.created DESC LIMIT :page,:num_products';
@@ -125,6 +189,12 @@ if(isset($criterias['totals']) && $criterias['totals']==''){
$messages = $stmt->fetch();
$messages = $messages[0];
}
elseif($type_check){
//Excute Query
$stmt->execute();
//Get results
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
else {
$current_page = isset($criterias['p']) && is_numeric($criterias['p']) ? (int)$criterias['p'] : 1;
$stmt->bindValue('page', ($current_page - 1) * $page_rows_history, PDO::PARAM_INT);
@@ -136,10 +206,22 @@ else {
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Clean up nested JSON in description fields before final encoding
if (!isset($criterias['totals']) || $criterias['totals'] != '') {
foreach ($messages as &$message) {
if (isset($message['description']) && is_string($message['description'])) {
$decoded = json_decode($message['description'], true);
if (json_last_error() === JSON_ERROR_NONE) {
$message['description'] = json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
}
}
}
//------------------------------------------
//JSON_ENCODE
//------------------------------------------
$messages = json_encode($messages, JSON_UNESCAPED_UNICODE);
$messages = json_encode($messages, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
//Send results
echo $messages;

View File

@@ -51,7 +51,7 @@ elseif (isset($criterias['list']) && $criterias['list'] =='invoice'){
//SQL for Paging
$sql = 'SELECT tx.*, txi.item_id as item_id,txi.item_price as item_price, txi.item_quantity as item_quantity, txi.item_options as item_options, p.productcode, p.productname, inv.id as invoice, inv.created as invoice_created, i.language as user_language
FROM transactions tx
left join invoice inv ON tx.id = inv.txn_id
left join invoice inv ON tx.txn_id = inv.txn_id
left join transactions_items txi ON tx.id = txi.txn_id
left join products p ON p.rowID = txi.item_id
left join identity i ON i.userkey = tx.account_id '.$whereclause;

View File

@@ -0,0 +1,152 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// Marketing Files
//------------------------------------------
//Connect to DB
$pdo = dbConnect($dbname);
//SoldTo is empty
if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';}
//default whereclause
$whereclause = '';
//NEW ARRAY
$criterias = [];
$clause = '';
//Check for $_GET variables and build up clause
if(isset($get_content) && $get_content!=''){
//GET VARIABLES FROM URL
$requests = explode("&", $get_content);
//Check for keys and values
foreach ($requests as $y){
$v = explode("=", $y);
//INCLUDE VARIABLES IN ARRAY
$criterias[$v[0]] = $v[1];
if ($v[0] == 'page' || $v[0] =='p' || $v[0] =='totals' || $v[0] =='list' || $v[0] == 'action' || $v[0] =='success_msg' || $v[0] == '_t'){
//do nothing
}
elseif ($v[0] == 'folder_id') {
if ($v[1] === 'null' || $v[1] === '') {
$clause .= ' AND folder_id IS NULL';
} else {
$clause .= ' AND folder_id = :folder_id';
}
}
elseif ($v[0] == 'search') {
$clause .= ' AND (title LIKE :search OR original_filename LIKE :search)';
}
elseif ($v[0] == 'tag') {
$clause .= ' AND EXISTS (SELECT 1 FROM marketing_file_tags ft JOIN marketing_tags t ON ft.tag_id = t.id WHERE ft.file_id = mf.id AND t.tag_name = :tag)';
}
elseif ($v[0] == 'file_type') {
$clause .= ' AND file_type = :file_type';
}
else {
// Ignore unknown parameters
}
}
if ($whereclause == '' && $clause !=''){
$whereclause = 'WHERE '.substr($clause, 4);
} else {
$whereclause .= $clause;
}
}
//Set page
$pagina = 1;
if(isset($criterias['p']) && $criterias['p'] !='') {
$pagina = $criterias['p'];
}
//Set limit
$limit = 50;
if(isset($criterias['limit']) && $criterias['limit'] !='') {
$limit = intval($criterias['limit']);
}
$offset = ($pagina - 1) * $limit;
//check for totals call
if(isset($criterias['totals'])){
$sql = 'SELECT COUNT(*) as found FROM marketing_files mf '.$whereclause.' ';
$stmt = $pdo->prepare($sql);
// Bind parameters
if (!empty($criterias)) {
foreach ($criterias as $key => $value) {
if ($key !== 'totals' && $key !== 'page' && $key !== 'p' && $key !== 'limit' && $key !== 'action') {
if ($key == 'search') {
$stmt->bindValue(':'.$key, '%'.$value.'%');
} elseif ($key == 'folder_id' && ($value === 'null' || $value === '')) {
continue;
} else {
$stmt->bindValue(':'.$key, $value);
}
}
}
}
$stmt->execute();
$found = $stmt->fetchColumn();
echo $found;
exit;
}
// Main query
$sql = "SELECT
mf.*,
GROUP_CONCAT(mt.tag_name) as tags
FROM marketing_files mf
LEFT JOIN marketing_file_tags mft ON mf.id = mft.file_id
LEFT JOIN marketing_tags mt ON mft.tag_id = mt.id
" . $whereclause . "
GROUP BY mf.id
ORDER BY mf.created DESC
LIMIT " . $limit . " OFFSET " . $offset;
$stmt = $pdo->prepare($sql);
// Bind parameters
if (!empty($criterias)) {
foreach ($criterias as $key => $value) {
if ($key !== 'totals' && $key !== 'page' && $key !== 'p' && $key !== 'limit') {
if ($key == 'search') {
$stmt->bindValue(':'.$key, '%'.$value.'%');
} elseif ($key == 'folder_id' && ($value === 'null' || $value === '')) {
continue;
} else {
$stmt->bindValue(':'.$key, $value);
}
}
}
}
$stmt->execute();
$marketing_files = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Process each file
foreach ($marketing_files as &$file) {
// Process tags
$file['tags'] = $file['tags'] ? explode(',', $file['tags']) : [];
// Format file size
$bytes = $file['file_size'];
if ($bytes >= 1073741824) {
$file['file_size_formatted'] = number_format($bytes / 1073741824, 2) . ' GB';
} elseif ($bytes >= 1048576) {
$file['file_size_formatted'] = number_format($bytes / 1048576, 2) . ' MB';
} elseif ($bytes >= 1024) {
$file['file_size_formatted'] = number_format($bytes / 1024, 2) . ' KB';
} else {
$file['file_size_formatted'] = $bytes . ' B';
}
}
// Return result
echo json_encode($marketing_files, JSON_UNESCAPED_UNICODE);
exit;

View File

@@ -0,0 +1,165 @@
<?php
defined($security_key) or exit;
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
//------------------------------------------
// Marketing Folders
//------------------------------------------
//Connect to DB
$pdo = dbConnect($dbname);
// Function to build hierarchical tree structure
function buildFolderTree($folders, $parentId = null) {
$tree = [];
foreach ($folders as $folder) {
if ($folder['parent_id'] == $parentId) {
$children = buildFolderTree($folders, $folder['id']);
$folder['children'] = $children; // Always include children array, even if empty
$tree[] = $folder;
}
}
return $tree;
}
//SoldTo is empty
if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';}
//default whereclause
$whereclause = '';
//NEW ARRAY
$criterias = [];
$clause = '';
//Check for $_GET variables and build up clause
if(isset($get_content) && $get_content!=''){
//GET VARIABLES FROM URL
$requests = explode("&", $get_content);
//Check for keys and values
foreach ($requests as $y){
$v = explode("=", $y);
//INCLUDE VARIABLES IN ARRAY
$criterias[$v[0]] = $v[1];
if ($v[0] == 'page' || $v[0] =='p' || $v[0] =='totals' || $v[0] =='list' || $v[0] =='success_msg' || $v[0] == 'action' || $v[0] == 'tree'){
//do nothing - these are not SQL parameters
}
elseif ($v[0] == 'parent_id') {
if ($v[1] === 'null' || $v[1] === '') {
$clause .= ' AND parent_id IS NULL';
} else {
$clause .= ' AND parent_id = :parent_id';
}
}
elseif ($v[0] == 'search') {
$clause .= ' AND (folder_name LIKE :search OR description LIKE :search)';
}
else {//create clause
$clause .= ' AND '.$v[0].' = :'.$v[0];
}
}
if ($whereclause == '' && $clause !=''){
$whereclause = 'WHERE '.substr($clause, 4);
} else {
$whereclause .= $clause;
}
}
//Define Query
if(isset($criterias['totals']) && $criterias['totals'] ==''){
//Request for total rows
$sql = 'SELECT count(*) as count FROM marketing_folders '.$whereclause.'';
}
elseif (isset($criterias['list']) && $criterias['list'] =='') {
//SQL for list (no paging)
$sql = "SELECT
mf.*,
(SELECT COUNT(*) FROM marketing_files WHERE folder_id = mf.id) as file_count,
(SELECT COUNT(*) FROM marketing_folders WHERE parent_id = mf.id) as subfolder_count,
CASE
WHEN mf.parent_id IS NOT NULL THEN
(SELECT folder_name FROM marketing_folders WHERE id = mf.parent_id)
ELSE NULL
END as parent_folder_name
FROM marketing_folders mf
" . $whereclause . "
ORDER BY mf.folder_name ASC";
}
else {
//SQL for paging
$sql = "SELECT
mf.*,
(SELECT COUNT(*) FROM marketing_files WHERE folder_id = mf.id) as file_count,
(SELECT COUNT(*) FROM marketing_folders WHERE parent_id = mf.id) as subfolder_count,
CASE
WHEN mf.parent_id IS NOT NULL THEN
(SELECT folder_name FROM marketing_folders WHERE id = mf.parent_id)
ELSE NULL
END as parent_folder_name
FROM marketing_folders mf
" . $whereclause . "
ORDER BY mf.folder_name ASC
LIMIT :page,:num_folders";
}
$stmt = $pdo->prepare($sql);
if (!empty($criterias)){
foreach ($criterias as $key => $value){
$key_condition = ':'.$key;
if (str_contains($whereclause, $key_condition)){
if ($key == 'search'){
$search_value = '%'.$value.'%';
$stmt->bindValue($key, $search_value, PDO::PARAM_STR);
}
elseif ($key == 'parent_id' && ($value === 'null' || $value === '')) {
// Skip binding for NULL parent_id
continue;
}
else {
$stmt->bindValue($key, $value, PDO::PARAM_STR);
}
}
}
}
//Add paging details
if(isset($criterias['totals']) && $criterias['totals']==''){
$stmt->execute();
$messages = $stmt->fetch();
$messages = $messages[0];
}
elseif(isset($criterias['list']) && $criterias['list']==''){
//Execute Query
$stmt->execute();
//Get results
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
else {
$current_page = isset($criterias['p']) && is_numeric($criterias['p']) ? (int)$criterias['p'] : 1;
$stmt->bindValue('page', ($current_page - 1) * $page_rows_folders, PDO::PARAM_INT);
$stmt->bindValue('num_folders', $page_rows_folders, PDO::PARAM_INT);
//Execute Query
$stmt->execute();
//Get results
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Check if tree structure is requested
if (isset($criterias['tree']) && isset($messages) && is_array($messages)) {
// Build hierarchical tree structure
$messages = buildFolderTree($messages);
}
//------------------------------------------
//JSON_ENCODE
//------------------------------------------
$messages = json_encode($messages, JSON_UNESCAPED_UNICODE);
//Send results
echo $messages;

View File

@@ -0,0 +1,112 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// Marketing Tags
//------------------------------------------
//Connect to DB
$pdo = dbConnect($dbname);
//SoldTo is empty
if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';}
//default whereclause
$whereclause = '';
//NEW ARRAY
$criterias = [];
$clause = '';
//Check for $_GET variables and build up clause
if(isset($get_content) && $get_content!=''){
//GET VARIABLES FROM URL
$requests = explode("&", $get_content);
//Check for keys and values
foreach ($requests as $y){
$v = explode("=", $y);
//INCLUDE VARIABLES IN ARRAY
$criterias[$v[0]] = $v[1];
if ($v[0] == 'page' || $v[0] =='p' || $v[0] =='totals' || $v[0] =='list' || $v[0] =='success_msg' || $v[0] == 'action'){
//do nothing
}
elseif ($v[0] == 'search') {
$clause .= ' AND tag_name LIKE :search';
}
elseif ($v[0] == 'used_only') {
if ($v[1] === 'true') {
$clause .= ' AND id IN (SELECT DISTINCT tag_id FROM marketing_file_tags)';
}
}
else {//create clause
$clause .= ' AND '.$v[0].' = :'.$v[0];
}
}
if ($whereclause == '' && $clause !=''){
$whereclause = 'WHERE '.substr($clause, 4);
} else {
$whereclause .= $clause;
}
}
//Set page
$pagina = 1;
if(isset($criterias['p']) && $criterias['p'] !='') {
$pagina = $criterias['p'];
}
//check for totals call
if(isset($criterias['totals'])){
$sql = 'SELECT COUNT(*) as found FROM marketing_tags mt '.$whereclause.' ';
$stmt = $pdo->prepare($sql);
// Bind parameters
if (!empty($criterias)) {
foreach ($criterias as $key => $value) {
if ($key !== 'totals' && $key !== 'page' && $key !== 'p' && $key !== 'used_only') {
if ($key == 'search') {
$stmt->bindValue(':'.$key, '%'.$value.'%');
} else {
$stmt->bindValue(':'.$key, $value);
}
}
}
}
$stmt->execute();
$found = $stmt->fetchColumn();
echo $found;
exit;
}
// Main query
$sql = "SELECT
mt.*,
COUNT(mft.file_id) as usage_count
FROM marketing_tags mt
LEFT JOIN marketing_file_tags mft ON mt.id = mft.tag_id
" . $whereclause . "
GROUP BY mt.id
ORDER BY mt.tag_name ASC";
$stmt = $pdo->prepare($sql);
// Bind parameters
if (!empty($criterias)) {
foreach ($criterias as $key => $value) {
if ($key !== 'totals' && $key !== 'page' && $key !== 'p' && $key !== 'used_only') {
if ($key == 'search') {
$stmt->bindValue(':'.$key, '%'.$value.'%');
} else {
$stmt->bindValue(':'.$key, $value);
}
}
}
}
$stmt->execute();
$marketing_tags = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Return result
echo json_encode($marketing_tags, JSON_UNESCAPED_UNICODE);

View File

@@ -49,7 +49,7 @@ if (!$transaction) {
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
$sql = 'SELECT * FROM transactions_items WHERE txn_id = ? LIMIT 1';
$stmt = $pdo->prepare($sql);
$stmt->execute([$payment_id]);
$stmt->execute([$transaction['id']]);
$item = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$item) {

View File

@@ -12,7 +12,7 @@ $pdo = dbConnect($dbname);
if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';}
//default whereclause
list($whereclause,$condition) = getWhereclauselvl2("software_upgrade_paths",$permission,$partner,'get');
list($whereclause,$condition) = getWhereclauselvl2("",$permission,$partner,'get');
//NEW ARRAY
$criterias = [];

View File

@@ -12,7 +12,7 @@ $pdo = dbConnect($dbname);
if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';}
//default whereclause
list($whereclause,$condition) = getWhereclauselvl2("software_versions",$permission,$partner,'get');
list($whereclause,$condition) = getWhereclauselvl2("",$permission,$partner,'get');
//NEW ARRAY
$criterias = [];

View File

@@ -0,0 +1,152 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// Report Builder - GET Endpoints
//------------------------------------------
// Set content type to JSON
header('Content-Type: application/json');
// Connect to DB
$pdo = dbConnect($dbname);
// Get the action parameter from URL
$criterias = [];
if (isset($get_content) && $get_content != '') {
$requests = explode("&", $get_content);
foreach ($requests as $y) {
$v = explode("=", $y);
if (isset($v[1])) {
$criterias[$v[0]] = urldecode($v[1]);
} else {
$criterias[$v[0]] = '';
}
}
}
$action = strtolower($criterias['action'] ?? '');
/**
* Validate table name - only allow alphanumeric, underscores, hyphens
*/
function sanitizeTableName($table) {
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $table)) {
return false;
}
return $table;
}
/**
* Get list of tables
*/
if ($action === 'gettables') {
try {
$result = $pdo->query("SHOW TABLES");
$tables = [];
while ($row = $result->fetch(PDO::FETCH_NUM)) {
$tables[] = $row[0];
}
$messages = json_encode([
'success' => true,
'tables' => $tables
], JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
http_response_code(500);
$messages = json_encode([
'success' => false,
'message' => 'Failed to fetch tables'
], JSON_UNESCAPED_UNICODE);
}
}
/**
* Get columns for a specific table
*/
elseif ($action === 'getcolumns') {
$table = sanitizeTableName($criterias['table'] ?? '');
if (!$table) {
http_response_code(400);
$messages = json_encode([
'success' => false,
'message' => 'Invalid table name'
], JSON_UNESCAPED_UNICODE);
} else {
try {
$result = $pdo->query("SHOW COLUMNS FROM `$table`");
$columns = [];
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
$columns[] = $row['Field'];
}
$messages = json_encode([
'success' => true,
'columns' => $columns
], JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
http_response_code(500);
$messages = json_encode([
'success' => false,
'message' => 'Failed to fetch columns'
], JSON_UNESCAPED_UNICODE);
}
}
}
/**
* Get table schema information
*/
elseif ($action === 'gettableschema') {
$table = sanitizeTableName($criterias['table'] ?? '');
if (!$table) {
http_response_code(400);
$messages = json_encode([
'success' => false,
'message' => 'Invalid table name'
], JSON_UNESCAPED_UNICODE);
} else {
try {
$result = $pdo->query("DESCRIBE `$table`");
$schema = [];
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
$schema[] = [
'field' => $row['Field'],
'type' => $row['Type'],
'null' => $row['Null'],
'key' => $row['Key'],
'default' => $row['Default'],
'extra' => $row['Extra']
];
}
$messages = json_encode([
'success' => true,
'schema' => $schema
], JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
http_response_code(500);
$messages = json_encode([
'success' => false,
'message' => 'Failed to fetch table schema'
], JSON_UNESCAPED_UNICODE);
}
}
}
/**
* Invalid or missing action
*/
else {
http_response_code(400);
$messages = json_encode([
'success' => false,
'message' => 'Invalid or missing action parameter'
], JSON_UNESCAPED_UNICODE);
}
// Send results
echo $messages;
?>

View File

@@ -0,0 +1,123 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// Role Access Permissions
//------------------------------------------
//Connect to DB
$pdo = dbConnect($dbname);
//------------------------------------------
//NEW ARRAY
//------------------------------------------
$criterias = [];
$clause = '';
//------------------------------------------
//Check for $_GET variables and build up clause
//------------------------------------------
if(isset($get_content) && $get_content!=''){
//GET VARIABLES FROM URL
$requests = explode("&", $get_content);
//Check for keys and values
foreach ($requests as $y){
$v = explode("=", $y);
//INCLUDE VARIABLES IN ARRAY
$criterias[$v[0]] = $v[1];
if ($v[0] == 'page' || $v[0] =='p' || $v[0] =='totals' || $v[0] =='success_msg'){
//do nothing
}
elseif ($v[0] == 'rowid') {
//build up search by ID
$clause .= ' AND rap.rowID = :'.$v[0];
}
elseif ($v[0] == 'role_id') {
//build up search by role_id
$clause .= ' AND rap.role_id = :'.$v[0];
}
elseif ($v[0] == 'access_id') {
//build up search by access_id
$clause .= ' AND rap.access_id = :'.$v[0];
}
else {
//create clause
$clause .= ' AND rap.'.$v[0].' = :'.$v[0];
}
}
}
//Build WHERE clause
$whereclause = '';
if ($clause != ''){
$whereclause = 'WHERE '.substr($clause, 4);
}
if (isset($criterias['totals']) && $criterias['totals'] ==''){
//Request for total rows
$sql = 'SELECT count(*) as count FROM role_access_permissions rap '.$whereclause;
}
else {
//SQL with joined tables for names
$sql = 'SELECT rap.*,
r.name as role_name,
ae.access_name,
ae.access_path
FROM role_access_permissions rap
LEFT JOIN user_roles r ON rap.role_id = r.rowID
LEFT JOIN access_elements ae ON rap.access_id = ae.rowID
'.$whereclause.'
ORDER BY ae.access_name ASC';
}
$stmt = $pdo->prepare($sql);
//------------------------------------------
//Bind to query
//------------------------------------------
if (!empty($criterias)){
foreach ($criterias as $key => $value){
$key_condition = ':'.$key;
if (str_contains($sql, $key_condition)){
if ($key == 'p'){
//Do nothing (bug)
}
else {
$stmt->bindValue($key, $value, PDO::PARAM_STR);
}
}
}
}
//------------------------------------------
// Debuglog
//------------------------------------------
if (debug){
$message = $date.';'.$sql.';'.$username;
debuglog($message);
}
//------------------------------------------
//Execute Query
//------------------------------------------
if(isset($criterias['totals']) && $criterias['totals']==''){
$stmt->execute();
$messages = $stmt->fetch();
$messages = $messages[0];
}
else {
//Execute Query
$stmt->execute();
//Get results
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
//------------------------------------------
//JSON_EnCODE
//------------------------------------------
$messages = json_encode($messages, JSON_UNESCAPED_UNICODE);
//------------------------------------------
//Send results
//------------------------------------------
echo $messages;
?>

82
api/v2/get/service.php Normal file
View File

@@ -0,0 +1,82 @@
<?php
defined($security_key) or exit;
//------------------------------------------
//Connect to DB
//------------------------------------------
$pdo = dbConnect($dbname);
//------------------------------------------
// Application related calls
//------------------------------------------
$request = explode('/', trim($_SERVER['PATH_INFO'],'/'));
$action = $request[2] ?? '';
if ($action == 'init'){
include './settings/systemservicetool_init.php';
echo json_encode($init);
}
elseif ($action == 'questions' && (isset($_GET['type']) && $_GET['type'] != '')){
include './settings/systemservicetool.php';
//build questions
switch ($_GET['type']) {
case 'visual':
$arrayQuestions = $arrayQuestions_visual;
break;
case 'final':
$arrayQuestions = $arrayQuestions_finalize;
break;
case 'cartest':
include './settings/systemcartest.php';
$arrayQuestions = $arrayQuestions_cartest;
break;
}
//Return JSON
echo json_encode($arrayQuestions);
}
elseif ($action == 'products') {
$sql = "SELECT * FROM products";
$stmt = $pdo->prepare($sql);
$stmt->execute();
//Get results
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($messages);
}
elseif ($action == 'equipments' && (isset($_GET['serialnumber']) && $_GET['serialnumber'] != '' && !isset($_GET['validate']))) {
$sql = "SELECT e.rowID as equipmentID, e.*, p.productcode, p.productname, p.product_media, psl.starts_at,psl.expires_at,psl.status as license_status from equipment e LEFT JOIN products p ON e.productrowid = p.rowID LEFT JOIN products_software_licenses psl ON e.sw_version_license = psl.license_key WHERE e.serialnumber = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$_GET['serialnumber']]);
//Get results
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($messages);
}
elseif ($action == 'equipments' && (isset($_GET['serialnumber']) && $_GET['serialnumber'] != '' && isset($_GET['validate']))){
$sql = "SELECT count(rowID) as rowID from equipment e WHERE e.serialnumber = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$_GET['serialnumber']]);
$messages = $stmt->fetch();
if ($messages[0] == 1) {
echo json_encode(array('SN'=> TRUE));
}
else {
echo json_encode(array('SN'=> FALSE));
}
}
else {
http_response_code(400);
}
?>

View File

@@ -62,6 +62,7 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
e.sw_version as current_sw_version,
e.hw_version,
e.sw_version_license,
e.sw_version_upgrade,
e.rowID as equipment_rowid
FROM equipment e
JOIN products p ON e.productrowid = p.rowID
@@ -78,6 +79,7 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
$current_sw_version = $equipment_data['current_sw_version'];
$hw_version = $equipment_data['hw_version'];
$sw_version_license = $equipment_data['sw_version_license'];
$sw_version_upgrade = $equipment_data['sw_version_upgrade'];
$equipment_rowid = $equipment_data['equipment_rowid'];
if (debug) {
@@ -85,7 +87,8 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
'product_rowid' => $product_rowid,
'productcode' => $productcode,
'current_sw_version_raw' => $current_sw_version,
'hw_version' => $hw_version
'hw_version' => $hw_version,
'sw_version_upgrade' => $sw_version_upgrade
];
}
@@ -119,6 +122,77 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
exit;
}
// Check if sw_version_upgrade is set - this overrides normal availability check
if (!empty($sw_version_upgrade)) {
if (debug) {
$debug['sw_version_upgrade_check'] = [
'sw_version_upgrade_id' => $sw_version_upgrade,
'checking_override' => true
];
}
// Check if this version exists and is active
$sql = 'SELECT
psv.rowID as version_id,
psv.version,
psv.name,
psv.description,
psv.mandatory,
psv.latest,
psv.hw_version,
psv.file_path,
psv.status
FROM products_software_versions psv
WHERE psv.rowID = ?';
$stmt = $pdo->prepare($sql);
$stmt->execute([$sw_version_upgrade]);
$upgrade_version = $stmt->fetch(PDO::FETCH_ASSOC);
if ($upgrade_version && $upgrade_version['status'] == 1) {
// Valid override found - check if different from current version
$normalized_upgrade_version = strtolower(ltrim($upgrade_version['version'], '0'));
if (debug) {
$debug['sw_version_upgrade_check']['found_version'] = [
'version' => $upgrade_version['version'],
'name' => $upgrade_version['name'],
'normalized' => $normalized_upgrade_version,
'status' => $upgrade_version['status'],
'is_different_from_current' => ($current_sw_version != $normalized_upgrade_version)
];
}
if ($current_sw_version && $normalized_upgrade_version == $current_sw_version) {
// Override version is same as current - no upgrade available
$software_available = "no";
if (debug) {
$debug['sw_version_upgrade_check']['decision'] = 'Override version is same as current version';
}
} else {
// Override version is different - upgrade is available
$software_available = "yes";
if (debug) {
$debug['sw_version_upgrade_check']['decision'] = 'Override version is available';
}
}
$messages = ["software_available" => $software_available];
if (debug) {
debuglog(json_encode($debug));
}
echo json_encode($messages, JSON_UNESCAPED_UNICODE);
exit;
} else {
// Override version not found or inactive - fall back to standard check
if (debug) {
$debug['sw_version_upgrade_check']['found_version'] = $upgrade_version ? 'found but inactive' : 'not found';
$debug['sw_version_upgrade_check']['decision'] = 'Falling back to standard check';
}
}
}
//GET ALL ACTIVE SOFTWARE ASSIGNMENTS for this product with matching HW version
$sql = 'SELECT
psv.rowID as version_id,
@@ -161,6 +235,7 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
$debug['decision'] = 'No active software assignments found';
}
} else {
$available_upgrades = 0;
$has_priced_options = false;
$has_latest_version_different = false;
@@ -219,7 +294,8 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
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 (LOWER(TRIM(LEADING "0" FROM from_ver.version)) = ?
OR pup.from_version_id = 9999999)
AND pup.is_active = 1';
$stmt = $pdo->prepare($sql);
$stmt->execute([$version['version_id'], $current_sw_version]);
@@ -242,6 +318,8 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
}
if ($show_version) {
$available_upgrades++;
//Check if there's a valid license for this upgrade
if ($final_price > 0 && $sw_version_license) {
//Check if the license is valid
@@ -286,23 +364,18 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
}
}
// Apply the logic:
// 1. If there are priced options -> "yes"
// 2. If no priced options but current version != latest flagged version -> "yes"
// 3. Default -> "no"
if ($has_priced_options) {
// Simple logic: if any upgrades are available to show, return "yes"
if ($available_upgrades > 0) {
$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";
$availability_reason = "Software upgrades available";
} else {
$software_available = "no";
$availability_reason = "No upgrades available or already on latest";
$availability_reason = "No upgrades available";
}
if (debug) {
$debug['final_decision'] = [
'available_upgrades' => $available_upgrades,
'has_priced_options' => $has_priced_options,
'has_latest_version_different' => $has_latest_version_different,
'software_available' => $software_available,

View File

@@ -55,16 +55,20 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
}
//GET EQUIPMENT AND PRODUCT DATA BASED ON SERIAL NUMBER
$sql = 'SELECT
$sql = "SELECT
p.rowID as product_rowid,
p.productcode,
e.sw_version as current_sw_version,
e.hw_version,
e.sw_version_license,
e.rowID as equipment_rowid
e.sw_version_upgrade,
e.rowID as equipment_rowid,
partner.*
FROM equipment e
JOIN products p ON e.productrowid = p.rowID
WHERE e.serialnumber = ?';
LEFT JOIN partner ON partner.partnerID = SUBSTRING_INDEX(JSON_UNQUOTE(JSON_EXTRACT(e.accounthierarchy, '$.soldto')), '-', 1)
AND partner.is_dealer = 1 AND partner.status = 1
WHERE e.serialnumber = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$criterias['sn']]);
$equipment_data = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -77,15 +81,28 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
$current_sw_version = $equipment_data['current_sw_version'];
$hw_version = $equipment_data['hw_version'];
$sw_version_license = $equipment_data['sw_version_license'];
$sw_version_upgrade = $equipment_data['sw_version_upgrade'];
$equipment_rowid = $equipment_data['equipment_rowid'];
$dealer_info = [
'is_dealer' => $equipment_data['is_dealer'] ?? 0,
'name' => $equipment_data['name'] ?? '',
'address' => $equipment_data['address'] ?? '',
'city' => $equipment_data['city'] ?? '',
'postalcode' => $equipment_data['postalcode'] ?? '',
'country' => $equipment_data['country'] ?? '',
'email' => $equipment_data['email'] ?? '',
'phone' => $equipment_data['phone'] ?? ''
];
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
'sw_version_license' => $sw_version_license,
'sw_version_upgrade' => $sw_version_upgrade
];
}
@@ -119,6 +136,95 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
exit;
}
// Check if sw_version_upgrade is set - this overrides normal availability check
if (!empty($sw_version_upgrade)) {
if (debug) {
$debug['sw_version_upgrade_check'] = [
'sw_version_upgrade_id' => $sw_version_upgrade,
'checking_override' => true
];
}
// Check if this version exists and is active
$sql = 'SELECT
psv.rowID as version_id,
psv.version,
psv.name,
psv.description,
psv.mandatory,
psv.latest,
psv.hw_version,
psv.file_path,
psv.status
FROM products_software_versions psv
WHERE psv.rowID = ?';
$stmt = $pdo->prepare($sql);
$stmt->execute([$sw_version_upgrade]);
$upgrade_version = $stmt->fetch(PDO::FETCH_ASSOC);
if ($upgrade_version && $upgrade_version['status'] == 1) {
// Valid override found - check if different from current version
$normalized_upgrade_version = strtolower(ltrim($upgrade_version['version'], '0'));
if (debug) {
$debug['sw_version_upgrade_check']['found_version'] = [
'version' => $upgrade_version['version'],
'name' => $upgrade_version['name'],
'normalized' => $normalized_upgrade_version,
'status' => $upgrade_version['status'],
'is_different_from_current' => ($current_sw_version != $normalized_upgrade_version)
];
}
if (!$current_sw_version || $current_sw_version == '' || $normalized_upgrade_version != $current_sw_version) {
// Override version is different from current (or no current) - return only this upgrade
$output[] = [
"productcode" => $productcode,
"name" => $upgrade_version['name'] ?? '',
"version" => $upgrade_version['version'],
"version_id" => $upgrade_version['version_id'],
"description" => $upgrade_version['description'] ?? '',
"hw_version" => $upgrade_version['hw_version'] ?? '',
"mandatory" => $upgrade_version['mandatory'] ?? '',
"latest" => $upgrade_version['latest'] ?? '',
"software" => $upgrade_version['file_path'] ?? '',
"source" => '',
"source_type" => '',
"price" => '0.00',
"currency" => '',
"is_current" => false
];
// Generate download token
$download_token = create_download_url_token($criterias['sn'], $upgrade_version['version_id']);
$download_url = 'https://'.$_SERVER['SERVER_NAME'].'/api.php/v2/software_download?token='.$download_token;
$output[0]['source'] = $download_url;
$output[0]['source_type'] = 'token_url';
if (debug) {
$debug['sw_version_upgrade_check']['decision'] = 'Override version returned as only upgrade';
$output[0]['_debug'] = $debug;
}
} else {
// Override version is same as current - no upgrades
if (debug) {
$debug['sw_version_upgrade_check']['decision'] = 'Override version is same as current version - no upgrades';
$output = ['message' => 'No upgrades available', 'debug' => $debug];
}
}
$messages = $output;
echo json_encode($messages, JSON_UNESCAPED_UNICODE);
exit;
} else {
// Override version not found or inactive - fall back to standard check
if (debug) {
$debug['sw_version_upgrade_check']['found_version'] = $upgrade_version ? 'found but inactive' : 'not found';
$debug['sw_version_upgrade_check']['decision'] = 'Falling back to standard check';
}
}
}
//GET ALL ACTIVE SOFTWARE ASSIGNMENTS for this product with matching HW version
$sql = 'SELECT
psv.rowID as version_id,
@@ -212,16 +318,13 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
$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
//Check if this is the current version - always show it
if ($is_current_version) {
$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)';
$decision_reason = 'Showing as CURRENT - always show current version';
} 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
@@ -242,26 +345,28 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
} else {
//Part of an upgrade path system
//Only show if there's an explicit path FROM current version TO this version
// OR a wildcard path (from_version_id = 9999999)
$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 (LOWER(TRIM(LEADING "0" FROM from_ver.version)) = ?
OR pup.from_version_id = 9999999)
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
//Valid upgrade path found FROM current version or wildcard
$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;
$decision_reason = 'Showing - found upgrade path FROM current (' . $current_sw_version . ') or wildcard with price: ' . $final_price . ' ' . $final_currency;
} else {
$decision_reason = 'Skipped - has upgrade paths but none FROM current version (' . $current_sw_version . ')';
$decision_reason = 'Skipped - has upgrade paths but none FROM current version (' . $current_sw_version . ') or wildcard';
}
//If no path from current version exists, don't show (show_version stays false)
//If no path from current version or wildcard exists, don't show (show_version stays false)
}
}
}
@@ -310,7 +415,7 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
}
}
$output[] = [
$entry = [
"productcode" => $productcode,
"name" => $version['name'] ?? '',
"version" => $version['version'],
@@ -324,8 +429,11 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
"source_type" => '',
"price" => $final_price,
"currency" => $final_currency,
"is_current" => $is_current
"is_current" => $is_current,
"dealer_info" => $dealer_info
];
$output[] = $entry;
}
if (debug) {
@@ -360,6 +468,16 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
];
}
// Sort output: is_current = true first, then by price low to high
usort($output, function($a, $b) {
// First priority: is_current (true comes before false)
if ($a['is_current'] !== $b['is_current']) {
return $b['is_current'] - $a['is_current'];
}
// Second priority: price (low to high)
return floatval($a['price']) - floatval($b['price']);
});
$messages = $output;
if (debug && !empty($output)) {

View File

@@ -19,7 +19,7 @@ if ($stmt->rowCount() == 1) {
//Define User data
$partnerhierarchy = $user_data['partnerhierarchy'];
$permission = userRights($user_data['view']);
$profile= getProfile($user_data['settings'],$permission);
$profile= getUserPermissions($pdo, $user_data['id']); //getProfile($user_data['settings'],$permission);
$username = $user_data['username'];
$useremail = $user_data['email'];
$servicekey = $user_data['service'];

View File

@@ -0,0 +1,70 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// User Role Assignments
//------------------------------------------
//Connect to DB
$pdo = dbConnect($dbname);
//------------------------------------------
//NEW ARRAY
//------------------------------------------
$criterias = [];
$user_permissions = [];
//------------------------------------------
//Check for $_GET variables and build up clause
//------------------------------------------
if(isset($get_content) && $get_content!=''){
//GET VARIABLES FROM URL
$requests = explode("&", $get_content);
//Check for keys and values
foreach ($requests as $y){
$v = explode("=", $y);
//INCLUDE VARIABLES IN ARRAY
$criterias[$v[0]] = $v[1];
}
}
$user_key = $criterias['userkey'] ?? null;
if (!$user_key) {
http_response_code(400);
exit(json_encode(['error_code' => 'API_INPUT_1','error' => 'user_key is required']));
}
//GET USER_DATA
$stmt = $pdo->prepare('SELECT * FROM users WHERE userkey = ?');
$stmt->execute([$user_key]);
if ($stmt->rowCount() == 1) {
//Get results
$user_data = $stmt->fetch();
//GET DATA
$user_permissions['id'] = $user_data['id'];
$user_permissions['email'] = $user_data['email'];
$user_permissions['partnerhierarchy'] = $user_data['partnerhierarchy']; //clean;
$user_permissions['permission'] = userRights($user_data['view']);
$user_permissions['profile'] = getProfile($user_data['settings'],userRights($user_data['view']));
//NEW DATA REPLACING PROFILE AND LATER PERMISSION ABOVE
$user_permissions['permissions'] = getUserPermissions($pdo, $user_data['id']);
if (!$user_permissions['permissions']) {
http_response_code(404);
exit(json_encode(['error_code' => 'API_NOT_FOUND','error' => 'No permissions found']));
}
//+++++++++++++++++++++++++++++++++++++++++++
//Return as JSON
//+++++++++++++++++++++++++++++++++++++++++++
echo json_encode($user_permissions);
}
else {
http_response_code(404);
exit(json_encode(['error_code' => 'API_NOT_FOUND','error' => 'User not found']));
}
?>

View File

@@ -0,0 +1,128 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// User Role Assignments
//------------------------------------------
//Connect to DB
$pdo = dbConnect($dbname);
//------------------------------------------
//NEW ARRAY
//------------------------------------------
$criterias = [];
$clause = '';
//------------------------------------------
//Check for $_GET variables and build up clause
//------------------------------------------
if(isset($get_content) && $get_content!=''){
//GET VARIABLES FROM URL
$requests = explode("&", $get_content);
//Check for keys and values
foreach ($requests as $y){
$v = explode("=", $y);
//INCLUDE VARIABLES IN ARRAY
$criterias[$v[0]] = $v[1];
if ($v[0] == 'page' || $v[0] =='p' || $v[0] =='totals' || $v[0] =='success_msg'){
//do nothing
}
elseif ($v[0] == 'rowid') {
//build up search by ID
$clause .= ' AND ura.rowID = :'.$v[0];
}
elseif ($v[0] == 'role_id') {
//build up search by role_id
$clause .= ' AND ura.role_id = :'.$v[0];
}
elseif ($v[0] == 'user_id') {
//build up search by user_id
$clause .= ' AND ura.user_id = :'.$v[0];
}
elseif ($v[0] == 'status') {
//Update status based on status
$clause .= ' AND ura.is_active = :'.$v[0];
}
else {
//create clause
$clause .= ' AND ura.'.$v[0].' = :'.$v[0];
}
}
}
//Build WHERE clause
$whereclause = '';
if ($clause != ''){
$whereclause = 'WHERE '.substr($clause, 4);
}
if (isset($criterias['totals']) && $criterias['totals'] ==''){
//Request for total rows
$sql = 'SELECT count(*) as count FROM user_role_assignments ura '.$whereclause;
}
else {
//SQL with joined tables for names
$sql = 'SELECT ura.*,
u.username,
u.email,
r.name as role_name,
r.description as role_description
FROM user_role_assignments ura
LEFT JOIN users u ON ura.user_id = u.id
LEFT JOIN user_roles r ON ura.role_id = r.rowID
'.$whereclause.'
ORDER BY u.username ASC';
}
$stmt = $pdo->prepare($sql);
//------------------------------------------
//Bind to query
//------------------------------------------
if (!empty($criterias)){
foreach ($criterias as $key => $value){
$key_condition = ':'.$key;
if (str_contains($sql, $key_condition)){
if ($key == 'p'){
//Do nothing (bug)
}
else {
$stmt->bindValue($key, $value, PDO::PARAM_STR);
}
}
}
}
//------------------------------------------
// Debuglog
//------------------------------------------
if (debug){
$message = $date.';'.$sql.';'.$username;
debuglog($message);
}
//------------------------------------------
//Execute Query
//------------------------------------------
if(isset($criterias['totals']) && $criterias['totals']==''){
$stmt->execute();
$messages = $stmt->fetch();
$messages = $messages[0];
}
else {
//Execute Query
$stmt->execute();
//Get results
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
//------------------------------------------
//JSON_EnCODE
//------------------------------------------
$messages = json_encode($messages, JSON_UNESCAPED_UNICODE);
//------------------------------------------
//Send results
//------------------------------------------
echo $messages;
?>

167
api/v2/get/user_roles.php Normal file
View File

@@ -0,0 +1,167 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// User Roles
//------------------------------------------
//Connect to DB
$pdo = dbConnect($dbname);
//------------------------------------------
//NEW ARRAY
//------------------------------------------
$criterias = [];
$clause = '';
//------------------------------------------
//Check for $_GET variables and build up clause
//------------------------------------------
if(isset($get_content) && $get_content!=''){
//GET VARIABLES FROM URL
$requests = explode("&", $get_content);
//Check for keys and values
foreach ($requests as $y){
$v = explode("=", $y);
//INCLUDE VARIABLES IN ARRAY
$criterias[$v[0]] = $v[1];
if ($v[0] == 'page' || $v[0] =='p' || $v[0] =='totals' || $v[0] =='success_msg' || $v[0] =='sort' || $v[0] =='all'){
//do nothing
}
elseif ($v[0] == 'rowid') {
//build up search by ID
$clause .= ' AND r.rowID = :'.$v[0];
}
elseif ($v[0] == 'status') {
//Update status based on status
$clause .= ' AND r.is_active = :'.$v[0];
}
elseif ($v[0] == 'search') {
//build up search
$clause .= ' AND (r.name LIKE :'.$v[0].' OR r.description LIKE :'.$v[0].')';
}
elseif ($v[0] == 'name') {
//build up name search
$clause .= ' AND r.name = :'.$v[0];
}
else {
//create clause
$clause .= ' AND r.'.$v[0].' = :'.$v[0];
}
}
}
//Filter system roles for users without delete permission on user_roles
if (isAllowed('user_roles', $profile, $permission, 'D') !== 1) {
$clause .= ' AND r.is_system != 1';
}
//Build WHERE clause
$whereclause = '';
if ($clause != ''){
$whereclause = 'WHERE '.substr($clause, 4);
}
// GET SORT INDICATOR
$sort_indicator = $criterias['sort'] ?? '';
switch ($sort_indicator){
case 1:
$sort = ' r.name ASC ';
break;
case 2:
$sort = ' r.name DESC ';
break;
case 3:
$sort = ' r.created ASC ';
break;
case 4:
$sort = ' r.created DESC ';
break;
default:
$sort = ' r.rowID ';
break;
}
if (isset($criterias['totals']) && $criterias['totals'] ==''){
//Request for total rows
$sql = 'SELECT count(*) as count FROM user_roles r '.$whereclause;
}
elseif (isset($criterias['all']) && $criterias['all'] ==''){
//Return all records (no paging)
$sql = 'SELECT r.*,
(SELECT COUNT(*) FROM role_access_permissions WHERE role_id = r.rowID) as permission_count
FROM user_roles r '.$whereclause.' ORDER BY '.$sort;
}
else {
//SQL with permission count
$sql = 'SELECT r.*,
(SELECT COUNT(*) FROM role_access_permissions WHERE role_id = r.rowID) as permission_count
FROM user_roles r '.$whereclause.' ORDER BY '.$sort.' LIMIT :page,:num_rows';
}
$stmt = $pdo->prepare($sql);
//------------------------------------------
//Bind to query
//------------------------------------------
if (!empty($criterias)){
foreach ($criterias as $key => $value){
$key_condition = ':'.$key;
if (str_contains($sql, $key_condition)){
if ($key == 'search'){
$search_value = '%'.$value.'%';
$stmt->bindValue($key, $search_value, PDO::PARAM_STR);
}
elseif ($key == 'p'){
//Do nothing (bug)
}
else {
$stmt->bindValue($key, $value, PDO::PARAM_STR);
}
}
}
}
//------------------------------------------
// Debuglog
//------------------------------------------
if (debug){
$message = $date.';'.$sql.';'.$username;
debuglog($message);
}
//------------------------------------------
//Add paging details
//------------------------------------------
$page_rows = $page_rows_equipment ?? 20;
if(isset($criterias['totals']) && $criterias['totals']==''){
$stmt->execute();
$messages = $stmt->fetch();
$messages = $messages[0];
}
elseif(isset($criterias['all']) && $criterias['all']==''){
//Return all records (no paging)
$stmt->execute();
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
else {
$current_page = isset($criterias['p']) && is_numeric($criterias['p']) ? (int)$criterias['p'] : 1;
$stmt->bindValue('page', ($current_page - 1) * $page_rows, PDO::PARAM_INT);
$stmt->bindValue('num_rows', $page_rows, PDO::PARAM_INT);
//Execute Query
$stmt->execute();
//Get results
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
//------------------------------------------
//JSON_EnCODE
//------------------------------------------
$messages = json_encode($messages, JSON_UNESCAPED_UNICODE);
//------------------------------------------
//Send results
//------------------------------------------
echo $messages;
?>

BIN
api/v2/post/.DS_Store vendored

Binary file not shown.

View File

@@ -0,0 +1,79 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// Access Elements
//------------------------------------------
//Connect to DB
$pdo = dbConnect($dbname);
//CONTENT FROM API (POST)
$post_content = json_decode($input,true);
//SET PARAMETERS FOR QUERY
$id = $post_content['rowID'] ?? '';
$command = ($id == '')? 'insert' : 'update';
if (isset($post_content['delete'])){$command = 'delete';}
$date = date('Y-m-d H:i:s');
//CREATE EMPTY STRINGS
$clause = '';
$clause_insert ='';
$input_insert = '';
$execute_input = [];
$criterias = [];
//ADD STANDARD PARAMETERS TO ARRAY BASED ON INSERT OR UPDATE
if ($command == 'update'){
$post_content['updatedby'] = $username;
$post_content['updated'] = $date;
}
elseif ($command == 'insert'){
$post_content['created'] = $date;
$post_content['createdby'] = $username;
}
//CREAT NEW ARRAY AND MAP TO CLAUSE
if(isset($post_content) && $post_content!=''){
foreach ($post_content as $key => $var){
if ($key == 'submit' || $key == 'rowID' || str_contains($key, 'old_')){
//do nothing
}
else {
$criterias[$key] = $var;
$clause .= ' , '.$key.' = ?';
$clause_insert .= ' , '.$key.'';
$input_insert .= ', ?';
$execute_input[]= $var;
}
}
}
//CLEAN UP INPUT
$clause = substr($clause, 2);
$clause_insert = substr($clause_insert, 2);
$input_insert = substr($input_insert, 1);
//QUERY AND VERIFY ALLOWED
if ($command == 'update' && isAllowed('access_element_manage',$profile,$permission,'U') === 1){
$sql = 'UPDATE access_elements SET '.$clause.' WHERE rowID = ?';
$execute_input[] = $id;
$stmt = $pdo->prepare($sql);
$stmt->execute($execute_input);
}
elseif ($command == 'insert' && isAllowed('access_element_manage',$profile,$permission,'C') === 1){
$sql = 'INSERT INTO access_elements ('.$clause_insert.') VALUES ('.$input_insert.')';
$stmt = $pdo->prepare($sql);
$stmt->execute($execute_input);
}
elseif ($command == 'delete' && isAllowed('access_element_manage',$profile,$permission,'D') === 1){
//Delete role permissions using this access element first (foreign key constraint)
$stmt = $pdo->prepare('DELETE FROM role_access_permissions WHERE access_id = ?');
$stmt->execute([$id]);
//Delete access element
$stmt = $pdo->prepare('DELETE FROM access_elements WHERE rowID = ?');
$stmt->execute([$id]);
}
?>

View File

@@ -38,7 +38,7 @@ if ($id != ''){
$salesid_new = ((isset($post_content['salesid']) && $post_content['salesid'] != '' && $post_content['salesid'] != $accounthierarchy_old->salesid)? $post_content['salesid'] : $accounthierarchy_old->salesid);
$soldto_new = ((isset($post_content['soldto']) && $post_content['soldto'] != '' && $post_content['soldto'] != $accounthierarchy_old->soldto)? $post_content['soldto'] : $accounthierarchy_old->soldto);
if ($permission == 3 || $permission == 4){
if (getHierarchyLevel($partner) == 1 || getHierarchyLevel($partner) == 0){
//ADMIN ONLY ARE ALLOWED TO CHANGE SALES AND SOLD
$account = array(
"salesid"=>$salesid_new,

View File

@@ -58,7 +58,7 @@ if ($id != ''){
$shipto_new = ((isset($post_content['shipto']) && $post_content['shipto'] != '' && $post_content['shipto'] != $contract_old->shipto)? $post_content['shipto'] : $contract_old->shipto);
$location_new = ((isset($post_content['location']) && $post_content['location'] != '' && $post_content['location'] != $contract_old->location)? $post_content['location'] : $contract_old->location);
if ($permission == 4){
if (getHierarchyLevel($partner) == 0){
//ADMIN+ ONLY ARE ALLOWED TO CHANGE SALES AND SOLD
$account = array(
"salesid"=>$salesid_new,
@@ -67,7 +67,7 @@ if ($id != ''){
"location"=>$location_new
);
}
elseif ($permission == 3) {
elseif (getHierarchyLevel($partner) == 1) {
//ADMIN ONLY ARE ALLOWED TO CHANGE SOLD
$account = array(
"salesid"=>$contract_old->salesid,
@@ -120,7 +120,7 @@ if ($id != ''){
}
else {
//ID is empty => INSERT / NEW RECORD
if ($permission == 4){
if (getHierarchyLevel($partner) == 0){
$account = array(
"salesid"=>$post_content['salesid'],
"soldto"=>$post_content['soldto'],
@@ -128,7 +128,7 @@ else {
"location"=>$post_content['location']
);
}
elseif ($permission == 3){
elseif (getHierarchyLevel($partner) == 1){
$account = array(
"salesid"=>$partner->salesid,
"soldto"=>$post_content['soldto'],
@@ -161,7 +161,7 @@ if (isset($post_content['ignore_list'])){
$post_content['ignore_list'] = json_encode($post_content['ignore_list'], JSON_UNESCAPED_UNICODE);
//ONLY ADMINS ARE ALLOWED TO UPDATE IGNORE LIST
if ($permission != 3 && $permission != 4){
if (getHierarchyLevel($partner) != 1 && getHierarchyLevel($partner) != 0){
unset($post_content['ignore_list']);
}
}

View File

@@ -47,7 +47,7 @@ if ($id != ''){
$owner_equipment = (($equipment_data['createdby'] == $username)? 1 : 0);
if ($permission == 4){
if (getHierarchyLevel($partner) == 0){
//ADMIN+ ONLY ARE ALLOWED TO CHANGE SALES AND SOLD
$account = array(
"salesid"=>$salesid_new,
@@ -57,7 +57,7 @@ if ($id != ''){
"section"=>$section_new
);
}
elseif ($permission == 3) {
elseif (getHierarchyLevel($partner) == 1) {
//ADMIN ONLY ARE ALLOWED TO CHANGE SOLD
$account = array(
"salesid"=>$equipment_old->salesid,
@@ -79,7 +79,7 @@ if ($id != ''){
}
else {
//ID is empty => INSERT / NEW RECORD
if ($permission == 4){
if (getHierarchyLevel($partner) == 0){
$account = array(
"salesid"=>$post_content['salesid'],
"soldto"=>$post_content['soldto'],
@@ -89,7 +89,7 @@ else {
);
}
elseif ($permission == 3){
elseif (getHierarchyLevel($partner) == 1){
$account = array(
"salesid"=>$partner->salesid,
"soldto"=>$post_content['soldto'],
@@ -148,9 +148,9 @@ if ($command == 'update'){
//RESET WARRANTY AND SERVICE DATES WHEN STATUS IS CHANGED TO SEND(3)
if (isset($post_content['status']) && $post_content['status'] == 3 && $equipment_data['status'] != 3)
{
$post_content['service_date'] = $date;
$post_content['warranty_date'] = $date;
$post_content['service_date'] = date("Y-m-d", strtotime("+" . SERVICE_MONTHS . " months"));
$post_content['warranty_date'] = date("Y-m-d", strtotime("+" . WARRANTY_MONTHS . " months"));
$post_content['order_send_date'] = $date;
}
//UPDATE CHANGELOG BASED ON STATUS CHANGE
if (isset($post_content['status']) && $post_content['status'] != $equipment_data['status'])
@@ -188,8 +188,15 @@ elseif ($command == 'insert'){
$post_content['created'] = $date;
$post_content['createdby'] = $username;
$post_content['accounthierarchy'] = $accounthierarchy;
$post_content['service_date'] = $date;
$post_content['warranty_date'] = $date;
$post_content['service_date'] = date("Y-m-d", strtotime("+" . SERVICE_MONTHS . " months"));
$post_content['warranty_date'] = date("Y-m-d", strtotime("+" . WARRANTY_MONTHS . " months"));
if (isset($post_content['status']) && $post_content['status'] == 3)
{
$post_content['order_send_date'] = $date;
}
}
else {
//do nothing

View File

@@ -1,5 +1,6 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// History
//------------------------------------------
@@ -23,7 +24,11 @@ function checkSerial($serialinput){
}
//CHECK IF SN AND PAYLOAD IS SEND => FROM EXTERNAL APPS
if (isset($post_content['sn']) && isset($post_content['payload'])){
if (isset($post_content['sn']) && (isset($post_content['payload']) || isset($post_content['testdetails']))){
if (!isset($post_content['payload'])) {
$post_content['payload'] = $post_content['testdetails'];
}
if (!empty($post_content['sn']) && !empty($post_content['payload'])) {
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
@@ -39,12 +44,16 @@ if (isset($post_content['sn']) && isset($post_content['payload'])){
$updateObject_visual = 0; //update visual inspection object
$sendServiceReport = 0; //send service report via email
$transfercartest = 0; //Update cartest table with incoming data
$create_software_license = 0; //Create software license
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
//SET DEFAULT PARAMETERS
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
$user = $username;
$account = $partnerhierarchy; //string
$current_date = date("Y-m-d");
$service_date = date("Y-m-d", strtotime("+" . SERVICE_MONTHS . " months"));
$warranty_date = date("Y-m-d", strtotime("+" . WARRANTY_MONTHS . " months"));
$order_send_date = date("Y-m-d");
$input_type = $post_content['type'];
$testdetails = json_encode($post_content['payload']);
$serial = $post_content['sn'];
@@ -141,6 +150,11 @@ if (isset($post_content['sn']) && isset($post_content['payload'])){
$transfercartest = 1;
break;
case 12: //customer_consent
$historytype = 'Customer_consent';
$create_software_license = 1;
break;
case 'firmware': //update from Portal
$historytype = $HistoryType_2;
$equipmentUpdate = 1;
@@ -148,6 +162,10 @@ if (isset($post_content['sn']) && isset($post_content['payload'])){
$sn_service = $post_content['sn_service'];
break;
case 'customer': //update from Portal
$historytype = 'Customer';
break;
default:
$historytype = 'Other';
break;
@@ -162,7 +180,7 @@ if (isset($post_content['sn']) && isset($post_content['payload'])){
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
//CHECK if EQUIPMENT EXISTS
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
$sql = "SELECT count(rowID) as total, rowID FROM equipment $whereclause";
$sql = "SELECT count(rowID) as total, rowID, hw_version FROM equipment $whereclause";
$stmt = $pdo->prepare($sql);
$stmt->execute();
$total = $stmt->fetchAll(PDO::FETCH_ASSOC);
@@ -173,9 +191,9 @@ if (isset($post_content['sn']) && isset($post_content['payload'])){
// Create equipment when not exist +++++++++++++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
if ($equipmentCreate == 1 && $total_equipment == 0){
$sql = 'INSERT INTO equipment (productrowid,created,createdby,status,accounthierarchy,serialnumber,service_date,warranty_date) VALUES (?,?,?,?,?,?,?,?)';
$sql = 'INSERT INTO equipment (productrowid,created,createdby,status,accounthierarchy,serialnumber,service_date,warranty_date,order_send_date) VALUES (?,?,?,?,?,?,?,?,?)';
$stmt = $pdo->prepare($sql);
$stmt->execute([$productrowid,$date,$user,$status0,$account,$serial,$current_date,$current_date]);
$stmt->execute([$productrowid,$date,$user,$status0,$account,$serial,$service_date,$warranty_date,$order_send_date]);
$rowID = $pdo->lastInsertId();
}
@@ -209,9 +227,8 @@ if (isset($post_content['sn']) && isset($post_content['payload'])){
if ($equipmentUpdate == 1){
//get HW + SW from PortalAPI
if ($post_content['type'] == 'firmware'){
$test = json_decode($post_content['payload']);
$hw_version = $test->HW;
$sw_version = $test->HEX_FW;
$hw_version = $post_content['payload']['HW'];
$sw_version = $post_content['payload']['HEX_FW'];
}
else {
//GET HW + SW from object
@@ -297,7 +314,7 @@ if (isset($post_content['sn']) && isset($post_content['payload'])){
//Update Equipment record
$sql = "UPDATE equipment SET service_date = ? $whereclause";
$stmt = $pdo->prepare($sql);
$stmt->execute([$current_date]);
$stmt->execute([$service_date]);
}
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
@@ -349,6 +366,50 @@ if (isset($post_content['sn']) && isset($post_content['payload'])){
if ($transfercartest == 1){
convertCartest();
}
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
// create software license ++++++++++++++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
if ($create_software_license == 1){
// Generate unique license key
$license_key = generateUniqueLicenseKey();
$sw_version_consent = strtolower($post_content['testdetails']['logdetails']['FW'] ?? '');// version_id
$eq_version_hw = strtolower($rowID['hw_version'] ?? '');
//GET VERSION_ID FROM VERSION TABLE
$sql = 'SELECT rowID FROM products_software_versions WHERE version = ? and hw_version = ?';
$stmt = $pdo->prepare($sql);
$stmt->execute([$sw_version_consent, $eq_version_hw]);
$version_row = $stmt->fetch(PDO::FETCH_ASSOC);
//GET VERSION_ID or use WILDCARD
$sw_version_consent = $version_row['rowID'] ?? '9999999';
// Create license
$sql = 'INSERT INTO products_software_licenses
(version_id, license_type, license_key, status, starts_at, expires_at, transaction_id, accounthierarchy,created, createdby)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
$stmt = $pdo->prepare($sql);
$stmt->execute([
$sw_version_consent,
1, // license_type (1 = upgrade)
$license_key,
1, // status = active
date('Y-m-d H:i:s'),
'2099-12-31 23:59:59', // effectively permanent
'Customer_consent',
$account,
date('Y-m-d H:i:s'),
$user
]);
// Update equipment.sw_version_license
$sql = 'UPDATE equipment SET sw_version_license = ? WHERE rowID = ?';
$stmt = $pdo->prepare($sql);
$stmt->execute([$license_key, $rowID]);
}
}
else
{

View File

@@ -0,0 +1,93 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// Marketing Files Delete
//------------------------------------------
//Connect to DB
$pdo = dbConnect($dbname);
//CONTENT FROM API (POST)
$post_content = json_decode($input,true);
//SoldTo is empty
if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';}
//default whereclause
list($whereclause,$condition) = getWhereclauselvl2("",$permission,$partner,'');
$file_id = $post_content['file_id'] ?? '';
if (empty($file_id)) {
echo json_encode(['error' => 'File ID is required']);
exit;
}
//QUERY AND VERIFY ALLOWED
if (isAllowed('marketing',$profile,$permission,'D') === 1){
// Get file information for cleanup
$file_sql = 'SELECT * FROM marketing_files WHERE id = ? AND accounthierarchy LIKE ?';
$stmt = $pdo->prepare($file_sql);
$stmt->execute([$file_id, '%' . $partner->soldto . '%']);
$file_info = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$file_info) {
echo json_encode(['error' => 'File not found or access denied']);
exit;
}
try {
$pdo->beginTransaction();
// Remove file tags
$delete_tags_sql = 'DELETE FROM marketing_file_tags WHERE file_id = ?';
$stmt = $pdo->prepare($delete_tags_sql);
$stmt->execute([$file_id]);
// Delete file record
$delete_file_sql = 'DELETE FROM marketing_files WHERE id = ? AND accounthierarchy LIKE ?';
$stmt = $pdo->prepare($delete_file_sql);
$stmt->execute([$file_id, '%' . $partner->soldto . '%']);
// Delete physical files
$base_path = dirname(__FILE__, 4) . "/";
$main_file = $base_path . $file_info['file_path'];
$thumbnail_file = $file_info['thumbnail_path'] ? $base_path . $file_info['thumbnail_path'] : null;
$files_deleted = [];
$files_failed = [];
if (file_exists($main_file)) {
if (unlink($main_file)) {
$files_deleted[] = $file_info['file_path'];
} else {
$files_failed[] = $file_info['file_path'];
}
}
if ($thumbnail_file && file_exists($thumbnail_file)) {
if (unlink($thumbnail_file)) {
$files_deleted[] = $file_info['thumbnail_path'];
} else {
$files_failed[] = $file_info['thumbnail_path'];
}
}
$pdo->commit();
echo json_encode([
'success' => true,
'message' => 'File deleted successfully',
'files_deleted' => $files_deleted,
'files_failed' => $files_failed
]);
} catch (Exception $e) {
$pdo->rollback();
echo json_encode(['error' => 'Failed to delete file: ' . $e->getMessage()]);
}
} else {
echo json_encode(['error' => 'Insufficient permissions']);
}
?>

View File

@@ -0,0 +1,105 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// Marketing Folders
//------------------------------------------
//Connect to DB
$pdo = dbConnect($dbname);
//CONTENT FROM API (POST)
$post_content = json_decode($input,true);
//SoldTo is empty
if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';}
//default whereclause
list($whereclause,$condition) = getWhereclauselvl2("",$permission,$partner,'');
//BUILD UP PARTNERHIERARCHY FROM USER
$partner_hierarchy = json_encode(array("salesid"=>$partner->salesid,"soldto"=>$partner->soldto), JSON_UNESCAPED_UNICODE);
$id = $post_content['id'] ?? ''; //check for rowID
$command = ($id == '')? 'insert' : 'update'; //IF rowID = empty then INSERT
if (isset($post_content['delete'])){$command = 'delete';} //change command to delete
$date = date('Y-m-d H:i:s');
//CREATE EMPTY STRINGS
$clause = '';
$clause_insert ='';
$input_insert = '';
if ($command == 'update'){
$post_content['updatedby'] = $username;
$post_content['updated'] = $date;
}
if ($command == 'insert'){
$post_content['createdby'] = $username;
$post_content['accounthierarchy'] = $partner_hierarchy;
}
//CREATE NEW ARRAY AND MAP TO CLAUSE
if(isset($post_content) && $post_content!=''){
foreach ($post_content as $key => $var){
if ($key == 'submit' || $key == 'id' || $key == 'delete'){
//do nothing
}
else {
// Handle empty parent_id as NULL for foreign key constraint
if ($key == 'parent_id' && $var === '') {
$var = null;
}
$criterias[$key] = $var;
$clause .= ' , '.$key.' = ?';
$clause_insert .= ' , '.$key.'';
$input_insert .= ', ?'; // ? for each insert item
$execute_input[]= $var; // Build array for input
}
}
}
//CLEAN UP INPUT
$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
//QUERY AND VERIFY ALLOWED
if ($command == 'update' && isAllowed('marketing',$profile,$permission,'U') === 1){
$sql = 'UPDATE marketing_folders SET '.$clause.' WHERE id = ? '.$whereclause.'';
$execute_input[] = $id;
$stmt = $pdo->prepare($sql);
$stmt->execute($execute_input);
echo json_encode(['success' => true, 'message' => 'Folder updated successfully']);
}
elseif ($command == 'insert' && isAllowed('marketing',$profile,$permission,'C') === 1){
$sql = 'INSERT INTO marketing_folders ('.$clause_insert.') VALUES ('.$input_insert.')';
$stmt = $pdo->prepare($sql);
$stmt->execute($execute_input);
$folder_id = $pdo->lastInsertId();
echo json_encode(['success' => true, 'rowID' => $folder_id, 'message' => 'Folder created successfully']);
}
elseif ($command == 'delete' && isAllowed('marketing',$profile,$permission,'D') === 1){
// Check if folder has subfolders
$subfolder_sql = 'SELECT COUNT(*) as count FROM marketing_folders WHERE parent_id = ? AND accounthierarchy LIKE ?';
$stmt = $pdo->prepare($subfolder_sql);
$stmt->execute([$id, '%' . $partner->soldto . '%']);
$subfolder_count = $stmt->fetch()['count'];
// Check if folder has files
$files_sql = 'SELECT COUNT(*) as count FROM marketing_files WHERE folder_id = ? AND accounthierarchy LIKE ?';
$stmt = $pdo->prepare($files_sql);
$stmt->execute([$id, '%' . $partner->soldto . '%']);
$files_count = $stmt->fetch()['count'];
if ($subfolder_count > 0 || $files_count > 0) {
echo json_encode(['error' => 'Cannot delete folder that contains subfolders or files']);
} else {
$stmt = $pdo->prepare('DELETE FROM marketing_folders WHERE id = ? '.$whereclause.'');
$stmt->execute([ $id ]);
echo json_encode(['success' => true, 'message' => 'Folder deleted successfully']);
}
} else {
echo json_encode(['error' => 'Insufficient permissions or invalid operation']);
}
?>

View File

@@ -0,0 +1,94 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// Marketing Update
//------------------------------------------
//Connect to DB
$pdo = dbConnect($dbname);
//SoldTo is empty
if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';}
//default whereclause
list($whereclause,$condition) = getWhereclauselvl2("",$permission,$partner,'');
//QUERY AND VERIFY ALLOWED
if (isAllowed('marketing',$profile,$permission,'U') === 1){
// Get JSON input
$input = json_decode(file_get_contents('php://input'), true);
$file_id = $input['file_id'] ?? '';
if (empty($file_id)) {
echo json_encode(['success' => false, 'error' => 'File ID is required']);
exit;
}
try {
// First verify the file exists and user has access
$check_sql = 'SELECT id FROM `marketing_files` WHERE `id` = ?';
$check_stmt = $pdo->prepare($check_sql);
$check_stmt->execute([$file_id]);
if ($check_stmt->rowCount() === 0) {
echo json_encode(['success' => false, 'error' => 'File not found or access denied']);
exit;
}
// Build dynamic UPDATE query for only changed fields
$update_fields = [];
$update_params = [];
if (isset($input['title'])) {
$update_fields[] = '`title` = ?';
$update_params[] = $input['title'];
}
if (isset($input['folder_id'])) {
$update_fields[] = '`folder_id` = ?';
$update_params[] = $input['folder_id'] ?: null;
}
// Always update updatedby if there are changes
if (!empty($update_fields)) {
$update_fields[] = '`updatedby` = ?';
$update_params[] = $username;
$update_params[] = $file_id;
$update_sql = 'UPDATE `marketing_files` SET ' . implode(', ', $update_fields) . ' WHERE `id` = ?';
$stmt = $pdo->prepare($update_sql);
$stmt->execute($update_params);
}
// Update tags only if provided
if (isset($input['tags'])) {
// Remove existing tags
$pdo->prepare('DELETE FROM `marketing_file_tags` WHERE `file_id` = ?')->execute([$file_id]);
// Parse and insert new tags
$tags_string = $input['tags'];
$tags_array = array_filter(array_map('trim', explode(',', $tags_string)));
if (!empty($tags_array)) {
$tag_sql = 'INSERT IGNORE INTO `marketing_tags` (`tag_name`) VALUES (?)';
$tag_stmt = $pdo->prepare($tag_sql);
$file_tag_sql = 'INSERT INTO `marketing_file_tags` (`file_id`, `tag_id`) SELECT ?, id FROM marketing_tags WHERE tag_name = ?';
$file_tag_stmt = $pdo->prepare($file_tag_sql);
foreach ($tags_array as $tag) {
$tag_stmt->execute([$tag]);
$file_tag_stmt->execute([$file_id, $tag]);
}
}
}
echo json_encode(['success' => true, 'message' => 'File updated successfully']);
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => 'Update failed: ' . $e->getMessage()]);
}
} else {
echo json_encode(['success' => false, 'error' => 'Insufficient permissions']);
}
?>

View File

@@ -0,0 +1,336 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// Marketing Upload
//------------------------------------------
//Connect to DB
$pdo = dbConnect($dbname);
//SoldTo is empty
if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';}
//default whereclause
list($whereclause,$condition) = getWhereclauselvl2("",$permission,$partner,'');
//BUILD UP PARTNERHIERARCHY FROM USER
$partner_hierarchy = $condition;
//QUERY AND VERIFY ALLOWED
if (isAllowed('marketing',$profile,$permission,'C') === 1){
if (!isset($_FILES['file'])) {
echo json_encode(['success' => false, 'error' => 'No file uploaded']);
exit;
}
$file = $_FILES['file'];
$folder_id = $_POST['folder_id'] ?? '';
$tags = isset($_POST['tags']) ? json_decode($_POST['tags'], true) : [];
$title = $_POST['title'] ?? pathinfo($file['name'], PATHINFO_FILENAME);
// Validate file type
$allowedTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'mp4', 'mov', 'avi'];
$filename = $file['name'];
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if (!in_array($ext, $allowedTypes)) {
echo json_encode(['success' => false, 'error' => 'Invalid file type. Allowed: ' . implode(', ', $allowedTypes)]);
exit;
}
$imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
$isImage = in_array($ext, $imageTypes);
// For images over 10MB, automatically compress
if ($isImage && $file['size'] > 10000000) {
$compressed = compressImage($file['tmp_name'], $ext, 10000000);
if ($compressed === false) {
echo json_encode(['success' => false, 'error' => 'Failed to compress large image. Please reduce file size manually.']);
exit;
}
// Update file size after compression
$file['size'] = filesize($file['tmp_name']);
}
// Non-images must be under 10MB
if (!$isImage && $file['size'] > 25000000) {
echo json_encode(['success' => false, 'error' => 'File too large. Maximum size is 25MB.']);
exit;
}
// Create unique filename
$unique_filename = uniqid() . '_' . time() . '.' . $ext;
$target_dir = dirname(__FILE__, 4) . "/marketing/uploads/";
$target_file = $target_dir . $unique_filename;
$logical_path = "marketing/uploads/" . $unique_filename;
// Ensure upload directory exists
if (!file_exists($target_dir)) {
mkdir($target_dir, 0755, true);
}
if (move_uploaded_file($file['tmp_name'], $target_file)) {
// Generate thumbnail
$thumbnail_path = null;
$thumb_dir = $target_dir . "thumbs/";
if (!file_exists($thumb_dir)) {
mkdir($thumb_dir, 0755, true);
}
// Generate thumbnail for images
if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'])) {
$thumbnail_file = $thumb_dir . $unique_filename;
if (generateThumbnail($target_file, $thumbnail_file, 200, 200)) {
$thumbnail_path = "marketing/uploads/thumbs/" . $unique_filename;
}
}
// Generate thumbnail for videos
elseif (in_array($ext, ['mp4', 'mov', 'avi'])) {
$thumbnail_filename = pathinfo($unique_filename, PATHINFO_FILENAME) . '.jpg';
$thumbnail_file = $thumb_dir . $thumbnail_filename;
if (generateVideoThumbnail($target_file, $thumbnail_file)) {
$thumbnail_path = "marketing/uploads/thumbs/" . $thumbnail_filename;
}
}
// Insert into database
$insert_sql = 'INSERT INTO `marketing_files` (`title`, `original_filename`, `file_path`, `thumbnail_path`, `file_type`, `file_size`, `folder_id`, `tags`, `createdby`, `accounthierarchy`) VALUES (?,?,?,?,?,?,?,?,?,?)';
$stmt = $pdo->prepare($insert_sql);
$stmt->execute([
$title,
$filename,
$logical_path,
$thumbnail_path,
$ext,
$file['size'],
$folder_id,
json_encode($tags),
$username,
$partner_hierarchy
]);
$file_id = $pdo->lastInsertId();
// Insert tags into separate table
if (!empty($tags)) {
$tag_sql = 'INSERT IGNORE INTO `marketing_tags` (`tag_name`) VALUES (?)';
$tag_stmt = $pdo->prepare($tag_sql);
$file_tag_sql = 'INSERT INTO `marketing_file_tags` (`file_id`, `tag_id`) SELECT ?, id FROM marketing_tags WHERE tag_name = ?';
$file_tag_stmt = $pdo->prepare($file_tag_sql);
foreach ($tags as $tag) {
$tag_stmt->execute([trim($tag)]);
$file_tag_stmt->execute([$file_id, trim($tag)]);
}
}
echo json_encode([
'success' => true,
'file_id' => $file_id,
'path' => $logical_path,
'thumbnail' => $thumbnail_path,
'message' => 'File uploaded successfully'
]);
} else {
echo json_encode(['success' => false, 'error' => 'Failed to move uploaded file']);
}
} else {
echo json_encode(['success' => false, 'error' => 'Insufficient permissions']);
}
// Function to compress large images
function compressImage($source, $ext, $maxSize) {
$info = @getimagesize($source);
if ($info === false) return false;
$mime = $info['mime'];
// Load image
switch ($mime) {
case 'image/jpeg':
$image = @imagecreatefromjpeg($source);
break;
case 'image/png':
$image = @imagecreatefrompng($source);
break;
case 'image/gif':
$image = @imagecreatefromgif($source);
break;
case 'image/webp':
$image = @imagecreatefromwebp($source);
break;
default:
return false;
}
if ($image === false) return false;
$width = imagesx($image);
$height = imagesy($image);
// Start with 90% quality and reduce dimensions if needed
$quality = 90;
$scale = 1.0;
$tempFile = $source . '.tmp';
// Try progressive compression
while (true) {
// Calculate new dimensions
$newWidth = (int)($width * $scale);
$newHeight = (int)($height * $scale);
// Create resized image
$resized = imagecreatetruecolor($newWidth, $newHeight);
// Preserve transparency for PNG/GIF
if ($mime === 'image/png' || $mime === 'image/gif') {
imagealphablending($resized, false);
imagesavealpha($resized, true);
$transparent = imagecolorallocatealpha($resized, 255, 255, 255, 127);
imagefilledrectangle($resized, 0, 0, $newWidth, $newHeight, $transparent);
}
imagecopyresampled($resized, $image, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
// Save with current quality
if ($ext === 'jpg' || $ext === 'jpeg') {
imagejpeg($resized, $tempFile, $quality);
} elseif ($ext === 'png') {
// PNG compression level (0-9, where 9 is best compression)
$pngQuality = (int)((100 - $quality) / 11);
imagepng($resized, $tempFile, $pngQuality);
} elseif ($ext === 'webp') {
imagewebp($resized, $tempFile, $quality);
} else {
imagegif($resized, $tempFile);
}
imagedestroy($resized);
$fileSize = filesize($tempFile);
// If file is small enough, use it
if ($fileSize <= $maxSize) {
imagedestroy($image);
rename($tempFile, $source);
return true;
}
// If we've reduced too much, give up
if ($quality < 50 && $scale < 0.5) {
imagedestroy($image);
@unlink($tempFile);
return false;
}
// Reduce quality or scale
if ($quality > 50) {
$quality -= 10;
} else {
$scale -= 0.1;
}
}
}
// Function to generate thumbnail
function generateThumbnail($source, $destination, $width, $height) {
$info = getimagesize($source);
if ($info === false) return false;
$mime = $info['mime'];
switch ($mime) {
case 'image/jpeg':
$image = imagecreatefromjpeg($source);
break;
case 'image/png':
$image = imagecreatefrompng($source);
break;
case 'image/gif':
$image = imagecreatefromgif($source);
break;
case 'image/webp':
$image = imagecreatefromwebp($source);
break;
default:
return false;
}
if ($image === false) return false;
$original_width = imagesx($image);
$original_height = imagesy($image);
// Calculate aspect ratio
$aspect_ratio = $original_width / $original_height;
if ($width / $height > $aspect_ratio) {
$new_width = $height * $aspect_ratio;
$new_height = $height;
} else {
$new_height = $width / $aspect_ratio;
$new_width = $width;
}
$thumbnail = imagecreatetruecolor($new_width, $new_height);
// Preserve transparency
imagealphablending($thumbnail, false);
imagesavealpha($thumbnail, true);
$transparent = imagecolorallocatealpha($thumbnail, 255, 255, 255, 127);
imagefilledrectangle($thumbnail, 0, 0, $new_width, $new_height, $transparent);
imagecopyresampled($thumbnail, $image, 0, 0, 0, 0, $new_width, $new_height, $original_width, $original_height);
// Save thumbnail
switch ($mime) {
case 'image/jpeg':
$result = imagejpeg($thumbnail, $destination, 85);
break;
case 'image/png':
$result = imagepng($thumbnail, $destination, 8);
break;
case 'image/gif':
$result = imagegif($thumbnail, $destination);
break;
case 'image/webp':
$result = imagewebp($thumbnail, $destination, 85);
break;
default:
$result = false;
}
imagedestroy($image);
imagedestroy($thumbnail);
return $result;
}
// Function to generate video thumbnail
function generateVideoThumbnail($source, $destination) {
// Check if ffmpeg is available
$ffmpeg = trim(shell_exec('which ffmpeg 2>/dev/null'));
if (empty($ffmpeg)) {
return false;
}
// Generate thumbnail from video at 1 second mark
// -i: input file
// -ss: seek to 1 second
// -vframes 1: extract one frame
// -vf: scale to 200x200 maintaining aspect ratio
$command = sprintf(
'%s -i %s -ss 00:00:01 -vframes 1 -vf "scale=200:200:force_original_aspect_ratio=decrease" %s 2>&1',
escapeshellarg($ffmpeg),
escapeshellarg($source),
escapeshellarg($destination)
);
exec($command, $output, $return_code);
return $return_code === 0 && file_exists($destination);
}
?>

View File

@@ -1,12 +1,9 @@
<?php
defined($security_key) or exit;
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
//------------------------------------------
// Payment Creation (for Software Upgrades)
//------------------------------------------
// This endpoint creates a Mollie payment and stores transaction data
// This endpoint creates a payment (Mollie or PayPal) and stores transaction data
//Connect to DB
$pdo = dbConnect($dbname);
@@ -25,6 +22,14 @@ if (empty($post_content['serial_number']) || empty($post_content['version_id']))
$serial_number = $post_content['serial_number'];
$version_id = $post_content['version_id'];
$user_data = $post_content['user_data'] ?? [];
// Read payment_provider from top level first, then fallback to user_data
$payment_provider = $post_content['payment_provider'] ?? $user_data['payment_provider'] ?? 'mollie';
// Extract tax information from user_data (sent from frontend)
$item_price = $user_data['item_price'] ?? null; // Price without VAT
$tax_amount = $user_data['tax_amount'] ?? 0; // VAT amount
$payment_amount = $user_data['payment_amount'] ?? null; // Total including VAT
$vat_number = $user_data['vat_number'] ?? null; // VAT number
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
// STEP 1: Get equipment data from serial_number
@@ -42,7 +47,8 @@ if (!$equipment) {
}
$equipment_id = $equipment['rowID'];
$current_sw_version = trim(strtolower(ltrim($equipment['sw_version'], '0')));
// Normalize software version for comparison (lowercase, trim leading zeros) - same as software_update.php line 96
$current_sw_version = strtolower(ltrim($equipment['sw_version'], '0'));
$sw_version_license = $equipment['sw_version_license'] ?? null;
$hw_version = $equipment['hw_version'] ?? '';
@@ -77,10 +83,13 @@ $path_count_result = $stmt->fetch(PDO::FETCH_ASSOC);
$has_upgrade_paths = ($path_count_result['path_count'] > 0);
if (!$has_upgrade_paths) {
// No upgrade paths defined = FREE (lines 240-242 in software_update.php)
// No upgrade paths defined = FREE (lines 328-331 in software_update.php)
$final_price = '0.00';
if (debug) {
debuglog("DEBUG: No upgrade paths defined for version_id $version_id - upgrade is FREE");
}
} else {
// Check for valid upgrade path FROM current version
// Check for valid upgrade path FROM current version (same logic as software_update.php lines 335-353)
$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
@@ -91,14 +100,28 @@ if (!$has_upgrade_paths) {
$stmt->execute([$version_id, $current_sw_version]);
$upgrade_path = $stmt->fetch(PDO::FETCH_ASSOC);
if (debug) {
debuglog("DEBUG: Looking for upgrade path TO version_id=$version_id FROM current_sw_version='$current_sw_version'");
debuglog("DEBUG: Upgrade path result: " . json_encode($upgrade_path));
}
if ($upgrade_path) {
$final_price = $upgrade_path['price'] ?? '0.00';
$final_currency = $upgrade_path['currency'] ?? 'EUR';
if (debug) {
debuglog("DEBUG: Found upgrade path - price: $final_price $final_currency");
}
} else {
// No upgrade path FROM current version
if (debug) {
debuglog("ERROR: No valid upgrade path from current version '$current_sw_version' to version_id $version_id");
}
http_response_code(400);
echo json_encode(['error' => 'No valid upgrade path from current version'], JSON_UNESCAPED_UNICODE);
echo json_encode([
'error' => 'No valid upgrade path from current version',
'current_version' => $current_sw_version,
'target_version_id' => $version_id
], JSON_UNESCAPED_UNICODE);
exit;
}
}
@@ -137,41 +160,125 @@ if ($final_price <= 0) {
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
// STEP 6: DEBUG MODE - Log but continue to real Mollie
// STEP 6: DEBUG MODE - Log
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
if (debug) {
debuglog("DEBUG MODE: Creating real Mollie payment for testing");
debuglog("DEBUG MODE: Creating $payment_provider payment for testing");
debuglog("DEBUG: Serial Number: $serial_number, Version ID: $version_id, Price: $final_price");
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
// STEP 7: Call Mollie API to create payment
// STEP 7: Create payment based on provider
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
try {
// Initialize Mollie
require dirname(__FILE__, 4).'/initialize.php';
// Use payment_amount (with tax) if provided, otherwise use final_price
$amount_to_charge = $payment_amount ? (float)$payment_amount : (float)$final_price;
// Format price for Mollie (must be string with 2 decimals)
$formatted_price = number_format((float)$final_price, 2, '.', '');
// Format price (must be string with 2 decimals)
$formatted_price = number_format($amount_to_charge, 2, '.', '');
if (debug) {
debuglog("DEBUG: Item Price (excl. VAT): " . ($item_price ?? $final_price));
debuglog("DEBUG: Tax Amount: " . $tax_amount);
debuglog("DEBUG: Total Amount (incl. VAT): " . $amount_to_charge);
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
// STEP 7A: Generate transaction ID BEFORE creating Mollie payment
// STEP 7A: Generate transaction ID BEFORE creating payment
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
// Generate unique transaction ID (same as placeorder.php)
$txn_id = strtoupper(uniqid('SC') . substr(md5(mt_rand()), 0, 5));
// Build webhook URL and redirect URL with actual transaction ID
// Build URLs
$protocol = 'https';
$hostname = $_SERVER['SERVER_NAME'];
$path = '/';
$webhook_url = "{$protocol}://{$hostname}{$path}webhook_mollie.php";
$redirect_url = "{$protocol}://{$hostname}{$path}?page=softwaretool&payment_return=1&order_id={$txn_id}";
if (debug) {
debuglog("DEBUG: Transaction ID: {$txn_id}");
debuglog("DEBUG: redirectUrl being sent to Mollie: " . $redirect_url);
debuglog("DEBUG: Redirect URL: " . $redirect_url);
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
// Create payment based on selected provider
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
if ($payment_provider === 'paypal') {
//==========================================
// PAYPAL PAYMENT CREATION
//==========================================
$cancel_url = "{$protocol}://{$hostname}{$path}?page=softwaretool&payment_return=cancelled&order_id={$txn_id}";
// Get PayPal access token
$access_token = getPayPalAccessToken();
// Create PayPal order
$order_data = [
'intent' => 'CAPTURE',
'purchase_units' => [[
'custom_id' => $txn_id,
'description' => "Software upgrade Order #{$txn_id}",
'amount' => [
'currency_code' => $final_currency ?: 'EUR',
'value' => $formatted_price
],
'payee' => [
'email_address' => email
]
]],
'application_context' => [
'return_url' => $redirect_url,
'cancel_url' => $cancel_url,
'brand_name' => site_name,
'user_action' => 'PAY_NOW'
]
];
$ch = curl_init(PAYPAL_URL . '/v2/checkout/orders');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($order_data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $access_token
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code != 200 && $http_code != 201) {
debuglog("PayPal API Error: HTTP $http_code - Response: $response");
throw new Exception("PayPal order creation failed: HTTP $http_code");
}
$paypal_order = json_decode($response, true);
$payment_id = $paypal_order['id'] ?? null;
// Extract approval URL
$checkout_url = '';
foreach ($paypal_order['links'] ?? [] as $link) {
if ($link['rel'] === 'approve') {
$checkout_url = $link['href'];
break;
}
}
if (!$checkout_url) {
throw new Exception("No approval URL received from PayPal");
}
$payment_method_id = 3; // PayPal
$payment_metadata = 'paypal_order_id';
} else {
//==========================================
// MOLLIE PAYMENT CREATION
//==========================================
// Initialize Mollie
require dirname(__FILE__, 4).'/initialize.php';
$webhook_url = "{$protocol}://{$hostname}{$path}webhook_mollie.php";
// Create payment with Mollie
$payment = $mollie->payments->create([
'amount' => [
@@ -189,15 +296,23 @@ try {
]
]);
$mollie_payment_id = $payment->id;
$payment_id = $payment->id;
$checkout_url = $payment->getCheckoutUrl();
if (debug) {
debuglog("DEBUG: Mollie payment created successfully");
debuglog("DEBUG: Payment ID: $mollie_payment_id");
debuglog("DEBUG: Payment ID: $payment_id");
debuglog("DEBUG: Redirect URL sent: $redirect_url");
debuglog("DEBUG: Redirect URL from Mollie object: " . $payment->redirectUrl);
debuglog("DEBUG: Full payment object: " . json_encode($payment));
debuglog("DEBUG: Checkout URL: $checkout_url");
}
$payment_method_id = 1; // Mollie
$payment_metadata = 'mollie_payment_id';
}
if (debug) {
debuglog("DEBUG: Payment created via $payment_provider");
debuglog("DEBUG: Payment ID: $payment_id");
debuglog("DEBUG: Checkout URL: $checkout_url");
}
@@ -213,13 +328,14 @@ try {
// BUILD UP PARTNERHIERARCHY FROM USER
$partner_product = json_encode(array("salesid"=>$partner->salesid,"soldto"=>$partner->soldto), JSON_UNESCAPED_UNICODE);
$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, payment_method, accounthierarchy, created)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
$sql = 'INSERT INTO transactions (txn_id, payment_amount, tax_amount, payment_status, payer_email, first_name, last_name,
address_street, address_city, address_state, address_zip, address_country, account_id, payment_method, accounthierarchy, created, vat_number)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?)';
$stmt = $pdo->prepare($sql);
$stmt->execute([
$txn_id, // Use generated transaction ID, not Mollie payment ID
$final_price,
$txn_id,
$amount_to_charge, // Total amount including tax
$tax_amount, // Tax amount
0, // 0 = pending
$user_data['email'] ?? '',
$first_name,
@@ -230,9 +346,10 @@ try {
$user_data['postal'] ?? '',
$user_data['country'] ?? '',
$serial_number,
0, // payment method
$payment_method_id, // 0 = Mollie, 1 = PayPal
$partner_product,
date('Y-m-d H:i:s')
date('Y-m-d H:i:s'),
$vat_number
]);
// Get the database ID
@@ -245,16 +362,19 @@ try {
'serial_number' => $serial_number,
'equipment_id' => $equipment_id,
'hw_version' => $hw_version,
'mollie_payment_id' => $mollie_payment_id // Store Mollie payment ID in options
$payment_metadata => $payment_id // Store payment provider ID
], JSON_UNESCAPED_UNICODE);
// Use item_price (without VAT) if provided, otherwise use final_price
$item_price_to_store = $item_price ? (float)$item_price : (float)$final_price;
$sql = 'INSERT INTO transactions_items (txn_id, item_id, item_price, item_quantity, item_options, created)
VALUES (?, ?, ?, ?, ?, ?)';
$stmt = $pdo->prepare($sql);
$stmt->execute([
$transaction_id, // Use database transaction ID (not txn_id string, not mollie_payment_id)
$transaction_id,
$version_id,
$final_price,
$item_price_to_store, // Price without VAT
1,
$item_options,
date('Y-m-d H:i:s')
@@ -265,7 +385,7 @@ try {
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
$messages = json_encode([
'checkout_url' => $checkout_url,
'payment_id' => $mollie_payment_id
'payment_id' => $payment_id
], JSON_UNESCAPED_UNICODE);
echo $messages;
@@ -275,4 +395,27 @@ try {
exit;
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
// Helper function to get PayPal access token
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
function getPayPalAccessToken() {
$ch = curl_init(PAYPAL_URL . '/v1/oauth2/token');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, 'grant_type=client_credentials');
curl_setopt($ch, CURLOPT_USERPWD, PAYPAL_CLIENT_ID . ':' . PAYPAL_CLIENT_SECRET);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code != 200) {
throw new Exception("Failed to get PayPal access token: HTTP $http_code");
}
$result = json_decode($response, true);
return $result['access_token'] ?? '';
}
?>

View File

@@ -46,7 +46,8 @@ if (isset($post_content['cart']) && isset($post_content['checkout_input']) && is
'address_state' => $post_content['customer_details']['address_state'] ?? '',
'address_zip' => $post_content['customer_details']['address_zip'] ?? '',
'address_country' => $post_content['customer_details']['address_country'] ?? '',
'address_phone' => $post_content['customer_details']['address_phone'] ?? ''
'address_phone' => $post_content['customer_details']['address_phone'] ?? '',
'vat_number' => $post_content['customer_details']['vat_number'] ?? ''
];
//Initialize calculator
@@ -75,7 +76,7 @@ if (isset($post_content['cart']) && isset($post_content['checkout_input']) && is
$txn_id = strtoupper(uniqid('SC') . substr(md5(mt_rand()), 0, 5));
// Insert transaction header
$stmt = $pdo->prepare('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, address_phone, account_id, payment_method, shipping_method, shipping_amount, discount_amount, discount_code, tax_amount,accounthierarchy) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)');
$stmt = $pdo->prepare('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, address_phone, account_id, payment_method, shipping_method, shipping_amount, discount_amount, discount_code, tax_amount,accounthierarchy, vat_number) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)');
$stmt->execute([
$txn_id,
$total,
@@ -96,7 +97,8 @@ if (isset($post_content['cart']) && isset($post_content['checkout_input']) && is
$discounttotal,
$checkout_input['discount_code'],
$taxtotal,
$partner_product
$partner_product,
$customer_details['vat_number']
]);
// Get order ID
$transaction_id = $pdo->lastInsertId();

View File

@@ -22,7 +22,7 @@ $command = ($id == '')? 'insert' : 'update'; //IF rowID = empty then INSERT
if (isset($post_content['delete'])){$command = 'delete';} //change command to delete
// Check for bulk creation
$is_bulk = isset($post_content['bulk']) && $post_content['bulk'] === true;
$is_bulk = isset($post_content['bulk']) && ($post_content['bulk'] === "true" || $post_content['bulk'] === true);
$date = date('Y-m-d H:i:s');
@@ -37,12 +37,24 @@ $input_insert = '';
if ($command == 'insert' && $is_bulk && isAllowed('products_software_licenses',$profile,$permission,'C') === 1){
$version_id = $post_content['version_id'] ?? '';
$serials = $post_content['serials'] ?? [];
$serials_input = $post_content['serials'] ?? '';
// Convert comma-separated string to array and trim whitespace
if (is_string($serials_input)) {
$serials = array_map('trim', explode(',', $serials_input));
} elseif (is_array($serials_input)) {
$serials = $serials_input;
} else {
$serials = [];
}
$transaction_id = $post_content['transaction_id'] ?? '';
$license_type = $post_content['license_type'] ?? 0;
$status = $post_content['status'] ?? 0;
$status = $post_content['status'] ?? 1;
$starts_at = $post_content['starts_at'] ?? date('Y-m-d H:i:s');
$expires_at = $post_content['expires_at'] ?? '2099-12-31 23:59:59'; // effectively permanent
if (empty($version_id) || empty($serials) || !is_array($serials)) {
if (empty($version_id) || empty($serials)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid parameters for bulk creation']);
exit;
@@ -51,8 +63,8 @@ if ($command == 'insert' && $is_bulk && isAllowed('products_software_licenses',$
$accounthierarchy = json_encode(array("salesid"=>$partner->salesid,"soldto"=>$partner->soldto), JSON_UNESCAPED_UNICODE);
// Prepare statement for bulk insert
$sql = 'INSERT INTO products_software_licenses (version_id, license_key, license_type, status, transaction_id, accounthierarchy, created, createdby)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)';
$sql = 'INSERT INTO products_software_licenses (version_id, license_key, license_type, status, starts_at, expires_at, transaction_id, accounthierarchy, created, createdby)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
$stmt = $pdo->prepare($sql);
$created_count = 0;
@@ -60,13 +72,7 @@ if ($command == 'insert' && $is_bulk && isAllowed('products_software_licenses',$
if (empty($serial)) continue;
// Generate UUID for license key
$license_key = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
$license_key = generateUniqueLicenseKey();
try {
$stmt->execute([
@@ -74,6 +80,8 @@ if ($command == 'insert' && $is_bulk && isAllowed('products_software_licenses',$
$license_key,
$license_type,
$status,
$starts_at,
$expires_at,
$transaction_id,
$accounthierarchy,
$date,
@@ -81,9 +89,9 @@ if ($command == 'insert' && $is_bulk && isAllowed('products_software_licenses',$
]);
// Assign license to equipment if serial number exists
$eq_sql = 'UPDATE equipment SET sw_version_license = ? WHERE serialnumber = ? AND accounthierarchy LIKE ?';
$eq_sql = 'UPDATE equipment SET sw_version_license = ? WHERE serialnumber = ? ';
$eq_stmt = $pdo->prepare($eq_sql);
$eq_stmt->execute([$license_key, $serial, '%'.$partner->soldto.'%']);
$eq_stmt->execute([$license_key, $serial]);
$created_count++;
} catch (Exception $e) {
@@ -104,17 +112,8 @@ if ($command == 'update'){
$post_content['updatedby'] = $username;
}
elseif ($command == 'insert'){
// Generate UUID for license key if not provided
if (empty($post_content['license_key'])) {
$post_content['license_key'] = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
}
// Generate UUID for license key
$post_content['license_key'] = generateUniqueLicenseKey();
$post_content['created'] = $date;
$post_content['createdby'] = $username;
$post_content['accounthierarchy'] = json_encode(array("salesid"=>$partner->salesid,"soldto"=>$partner->soldto), JSON_UNESCAPED_UNICODE);

View File

@@ -14,7 +14,7 @@ $post_content = json_decode($input,true);
if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';}
//default whereclause
list($whereclause,$condition) = getWhereclauselvl2("software_upgrade_paths",$permission,$partner,'');
list($whereclause,$condition) = getWhereclauselvl2("",$permission,$partner,'');
//SET PARAMETERS FOR QUERY
$id = $post_content['rowID'] ?? ''; //check for rowID

View File

@@ -0,0 +1,124 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// Report Builder - POST Endpoints
//------------------------------------------
// Set content type to JSON
header('Content-Type: application/json');
// Connect to DB
$pdo = dbConnect($dbname);
// Parse input data
$data = json_decode($input, true);
$action = strtolower($data['action'] ?? '');
/**
* Security check: Only allow SELECT queries
*/
function isSelectQuery($query) {
$query = trim($query);
$query = preg_replace('/\s+/', ' ', $query); // Normalize whitespace
// Only allow SELECT queries
if (!preg_match('/^SELECT\s/i', $query)) {
return false;
}
// Block dangerous keywords that could be used for injection
$dangerousPatterns = [
'/;\s*DROP\s/i',
'/;\s*DELETE\s/i',
'/;\s*UPDATE\s/i',
'/;\s*INSERT\s/i',
'/;\s*CREATE\s/i',
'/;\s*ALTER\s/i',
'/;\s*TRUNCATE\s/i',
'/INTO\s+OUTFILE\s/i',
'/LOAD_FILE\s*\(/i',
'/SLEEP\s*\(/i',
'/BENCHMARK\s*\(/i',
];
foreach ($dangerousPatterns as $pattern) {
if (preg_match($pattern, $query)) {
return false;
}
}
return true;
}
/**
* Execute a SELECT query
*/
if ($action === 'executequery') {
$query = $data['query'] ?? '';
if (empty($query)) {
http_response_code(400);
$messages = json_encode([
'success' => false,
'message' => 'Query parameter is required'
], JSON_UNESCAPED_UNICODE);
}
// Security check: only allow SELECT queries
elseif (!isSelectQuery($query)) {
http_response_code(400);
$messages = json_encode([
'success' => false,
'message' => 'Only SELECT queries are allowed'
], JSON_UNESCAPED_UNICODE);
} else {
try {
// Execute the query
$stmt = $pdo->query($query);
// Fetch all results
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Get row count
$rowCount = count($results);
// Limit results to prevent memory issues
$maxResults = 5000;
if ($rowCount > $maxResults) {
$results = array_slice($results, 0, $maxResults);
$message = "Query executed successfully. Showing first $maxResults of $rowCount rows.";
} else {
$message = "Query executed successfully. $rowCount rows returned.";
}
$messages = json_encode([
'success' => true,
'results' => $results,
'rowCount' => $rowCount,
'message' => $message
], JSON_UNESCAPED_UNICODE);
} catch (PDOException $e) {
http_response_code(400);
$messages = json_encode([
'success' => false,
'message' => 'Query execution failed: ' . $e->getMessage()
], JSON_UNESCAPED_UNICODE);
}
}
}
/**
* Invalid or missing action
*/
else {
http_response_code(400);
$messages = json_encode([
'success' => false,
'message' => 'Invalid or missing action parameter'
], JSON_UNESCAPED_UNICODE);
}
// Send results
echo $messages;
?>

View File

@@ -0,0 +1,75 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// Role Access Permissions
//------------------------------------------
//Connect to DB
$pdo = dbConnect($dbname);
//CONTENT FROM API (POST)
$post_content = json_decode($input,true);
//SET PARAMETERS FOR QUERY
$id = $post_content['rowID'] ?? '';
$command = ($id == '')? 'insert' : 'update';
if (isset($post_content['delete'])){$command = 'delete';}
$date = date('Y-m-d H:i:s');
//CREATE EMPTY STRINGS
$clause = '';
$clause_insert ='';
$input_insert = '';
$execute_input = [];
$criterias = [];
//ADD STANDARD PARAMETERS TO ARRAY BASED ON INSERT OR UPDATE
if ($command == 'update'){
$post_content['updatedby'] = $username;
$post_content['updated'] = $date;
}
elseif ($command == 'insert'){
$post_content['created'] = $date;
$post_content['createdby'] = $username;
}
//CREAT NEW ARRAY AND MAP TO CLAUSE
if(isset($post_content) && $post_content!=''){
foreach ($post_content as $key => $var){
if ($key == 'submit' || $key == 'rowID' || str_contains($key, 'old_')){
//do nothing
}
else {
$criterias[$key] = $var;
$clause .= ' , '.$key.' = ?';
$clause_insert .= ' , '.$key.'';
$input_insert .= ', ?';
$execute_input[]= $var;
}
}
}
//CLEAN UP INPUT
$clause = substr($clause, 2);
$clause_insert = substr($clause_insert, 2);
$input_insert = substr($input_insert, 1);
//QUERY AND VERIFY ALLOWED
if ($command == 'update' && isAllowed('user_role_manage',$profile,$permission,'U') === 1){
$sql = 'UPDATE role_access_permissions SET '.$clause.' WHERE rowID = ?';
$execute_input[] = $id;
$stmt = $pdo->prepare($sql);
$stmt->execute($execute_input);
}
elseif ($command == 'insert' && isAllowed('user_role_manage',$profile,$permission,'C') === 1){
$sql = 'INSERT INTO role_access_permissions ('.$clause_insert.') VALUES ('.$input_insert.')';
$stmt = $pdo->prepare($sql);
$stmt->execute($execute_input);
}
elseif ($command == 'delete' && isAllowed('user_role_manage',$profile,$permission,'D') === 1){
//Delete permission
$stmt = $pdo->prepare('DELETE FROM role_access_permissions WHERE rowID = ?');
$stmt->execute([$id]);
}
?>

View File

@@ -0,0 +1,141 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// User Role Assignments
//------------------------------------------
//Connect to DB
$pdo = dbConnect($dbname);
//CONTENT FROM API (POST)
$post_content = json_decode($input,true);
//SET PARAMETERS FOR QUERY
$id = $post_content['rowID'] ?? '';
$date = date('Y-m-d H:i:s');
//------------------------------------------
// BATCH UPDATE - Update all roles for a user
//------------------------------------------
if (isset($post_content['batch_update']) && isset($post_content['user_id']) && isAllowed('user_manage',$profile,$permission,'U') === 1){
$user_id = $post_content['user_id'];
$selected_roles = $post_content['roles'] ?? [];
//Get currently assigned active roles
$stmt = $pdo->prepare('SELECT role_id, rowID FROM user_role_assignments WHERE user_id = ? AND is_active = 1');
$stmt->execute([$user_id]);
$current_roles = [];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)){
$current_roles[$row['role_id']] = $row['rowID'];
}
//Remove roles that are no longer selected (soft delete)
foreach ($current_roles as $role_id => $assignment_id){
if (!in_array($role_id, $selected_roles)){
$stmt = $pdo->prepare('UPDATE user_role_assignments SET is_active = 0, updatedby = ?, updated = ? WHERE rowID = ?');
$stmt->execute([$username, $date, $assignment_id]);
}
}
//Add new roles that are selected but not currently assigned
foreach ($selected_roles as $role_id){
if (!array_key_exists($role_id, $current_roles)){
//Check if this user-role combination existed before (inactive)
$stmt = $pdo->prepare('SELECT rowID FROM user_role_assignments WHERE user_id = ? AND role_id = ? AND is_active = 0 LIMIT 1');
$stmt->execute([$user_id, $role_id]);
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
if ($existing){
//Reactivate existing assignment
$stmt = $pdo->prepare('UPDATE user_role_assignments SET is_active = 1, assigned_by = ?, assigned_at = ?, updatedby = ?, updated = ? WHERE rowID = ?');
$stmt->execute([$username, $date, $username, $date, $existing['rowID']]);
} else {
//Create new assignment
$stmt = $pdo->prepare('INSERT INTO user_role_assignments (user_id, role_id, is_active, assigned_by, assigned_at, created, createdby) VALUES (?, ?, 1, ?, ?, ?, ?)');
$stmt->execute([$user_id, $role_id, $username, $date, $date, $userkey]);
}
}
}
}
//------------------------------------------
// SINGLE OPERATIONS (for backward compatibility or direct API calls)
//------------------------------------------
else {
$command = ($id == '')? 'insert' : 'update';
if (isset($post_content['delete'])){$command = 'delete';}
//CREATE EMPTY STRINGS
$clause = '';
$clause_insert ='';
$input_insert = '';
$execute_input = [];
$criterias = [];
//ADD STANDARD PARAMETERS TO ARRAY BASED ON INSERT OR UPDATE
if ($command == 'update'){
$post_content['updatedby'] = $username;
$post_content['updated'] = $date;
}
elseif ($command == 'insert'){
$post_content['created'] = $date;
$post_content['createdby'] = $username;
$post_content['assigned_by'] = $username;
$post_content['assigned_at'] = $date;
}
//CREAT NEW ARRAY AND MAP TO CLAUSE
if(isset($post_content) && $post_content!=''){
foreach ($post_content as $key => $var){
if ($key == 'submit' || $key == 'rowID' || $key == 'delete' || $key == 'batch_update' || str_contains($key, 'old_')){
//do nothing
}
else {
$criterias[$key] = $var;
$clause .= ' , '.$key.' = ?';
$clause_insert .= ' , '.$key.'';
$input_insert .= ', ?';
$execute_input[]= $var;
}
}
}
//CLEAN UP INPUT
$clause = substr($clause, 2);
$clause_insert = substr($clause_insert, 2);
$input_insert = substr($input_insert, 1);
//QUERY AND VERIFY ALLOWED
if ($command == 'update' && isAllowed('user_manage',$profile,$permission,'U') === 1){
$sql = 'UPDATE user_role_assignments SET '.$clause.' WHERE rowID = ?';
$execute_input[] = $id;
$stmt = $pdo->prepare($sql);
$stmt->execute($execute_input);
}
elseif ($command == 'insert' && isAllowed('user_manage',$profile,$permission,'C') === 1){
//Check if this user-role combination already exists (including inactive ones)
$stmt = $pdo->prepare('SELECT rowID, is_active FROM user_role_assignments WHERE user_id = ? AND role_id = ? LIMIT 1');
$stmt->execute([$post_content['user_id'], $post_content['role_id']]);
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
if ($existing){
//If exists but inactive, reactivate it
if ($existing['is_active'] == 0){
$stmt = $pdo->prepare('UPDATE user_role_assignments SET is_active = 1, assigned_by = ?, assigned_at = ?, updatedby = ?, updated = ? WHERE rowID = ?');
$stmt->execute([$username, $date, $username, $date, $existing['rowID']]);
}
//If already active, do nothing (or could throw an error)
} else {
//Insert new assignment
$sql = 'INSERT INTO user_role_assignments ('.$clause_insert.') VALUES ('.$input_insert.')';
$stmt = $pdo->prepare($sql);
$stmt->execute($execute_input);
}
}
elseif ($command == 'delete' && isAllowed('user_manage',$profile,$permission,'D') === 1){
//Soft delete by setting is_active to 0
$stmt = $pdo->prepare('UPDATE user_role_assignments SET is_active = 0, updatedby = ?, updated = ? WHERE rowID = ?');
$stmt->execute([$username, $date, $id]);
}
}
?>

123
api/v2/post/user_roles.php Normal file
View File

@@ -0,0 +1,123 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// User Roles
//------------------------------------------
//Connect to DB
$pdo = dbConnect($dbname);
//CONTENT FROM API (POST)
$post_content = json_decode($input,true);
//SET PARAMETERS FOR QUERY
$id = $post_content['rowID'] ?? '';
$command = ($id == '')? 'insert' : 'update';
if (isset($post_content['delete'])){$command = 'delete';}
$date = date('Y-m-d H:i:s');
//CREATE EMPTY STRINGS
$clause = '';
$clause_insert ='';
$input_insert = '';
$execute_input = [];
$criterias = [];
//ADD STANDARD PARAMETERS TO ARRAY BASED ON INSERT OR UPDATE
if ($command == 'update'){
$post_content['updatedby'] = $username;
$post_content['updated'] = $date;
}
elseif ($command == 'insert'){
$post_content['created'] = $date;
$post_content['createdby'] = $username;
}
//CREAT NEW ARRAY AND MAP TO CLAUSE
if(isset($post_content) && $post_content!=''){
foreach ($post_content as $key => $var){
if ($key == 'submit' || $key == 'rowID' || $key == 'permissions' || str_contains($key, 'old_')){
//do nothing
}
else {
$criterias[$key] = $var;
$clause .= ' , '.$key.' = ?';
$clause_insert .= ' , '.$key.'';
$input_insert .= ', ?';
$execute_input[]= $var;
}
}
}
//CLEAN UP INPUT
$clause = substr($clause, 2);
$clause_insert = substr($clause_insert, 2);
$input_insert = substr($input_insert, 1);
//QUERY AND VERIFY ALLOWED
if ($command == 'update' && isAllowed('user_role_manage',$profile,$permission,'U') === 1){
$sql = 'UPDATE user_roles SET '.$clause.' WHERE rowID = ?';
$execute_input[] = $id;
$stmt = $pdo->prepare($sql);
$stmt->execute($execute_input);
//Handle permissions update
if (isset($post_content['permissions'])){
//First delete all existing permissions for this role
$stmt = $pdo->prepare('DELETE FROM role_access_permissions WHERE role_id = ?');
$stmt->execute([$id]);
//Insert new permissions
foreach ($post_content['permissions'] as $access_id => $perms){
$can_create = isset($perms['can_create']) ? 1 : 0;
$can_read = isset($perms['can_read']) ? 1 : 0;
$can_update = isset($perms['can_update']) ? 1 : 0;
$can_delete = isset($perms['can_delete']) ? 1 : 0;
//Only insert if at least one permission is set
if ($can_create || $can_read || $can_update || $can_delete){
$stmt = $pdo->prepare('INSERT INTO role_access_permissions (role_id, access_id, can_create, can_read, can_update, can_delete, created, createdby) VALUES (?, ?, ?, ?, ?, ?, ?, ?)');
$stmt->execute([$id, $access_id, $can_create, $can_read, $can_update, $can_delete, $date, $userkey]);
}
}
}
}
elseif ($command == 'insert' && isAllowed('user_role_manage',$profile,$permission,'C') === 1){
$sql = 'INSERT INTO user_roles ('.$clause_insert.') VALUES ('.$input_insert.')';
$stmt = $pdo->prepare($sql);
$stmt->execute($execute_input);
//Get the new role ID
$new_role_id = $pdo->lastInsertId();
//Handle permissions for new role
if (isset($post_content['permissions'])){
foreach ($post_content['permissions'] as $access_id => $perms){
$can_create = isset($perms['can_create']) ? 1 : 0;
$can_read = isset($perms['can_read']) ? 1 : 0;
$can_update = isset($perms['can_update']) ? 1 : 0;
$can_delete = isset($perms['can_delete']) ? 1 : 0;
//Only insert if at least one permission is set
if ($can_create || $can_read || $can_update || $can_delete){
$stmt = $pdo->prepare('INSERT INTO role_access_permissions (role_id, access_id, can_create, can_read, can_update, can_delete, created, createdby) VALUES (?, ?, ?, ?, ?, ?, ?, ?)');
$stmt->execute([$new_role_id, $access_id, $can_create, $can_read, $can_update, $can_delete, $date, $userkey]);
}
}
}
}
elseif ($command == 'delete' && isAllowed('user_role_manage',$profile,$permission,'D') === 1){
//Delete role permissions first (foreign key constraint)
$stmt = $pdo->prepare('DELETE FROM role_access_permissions WHERE role_id = ?');
$stmt->execute([$id]);
//Delete user role assignments
$stmt = $pdo->prepare('DELETE FROM user_role_assignments WHERE role_id = ?');
$stmt->execute([$id]);
//Delete role
$stmt = $pdo->prepare('DELETE FROM user_roles WHERE rowID = ?');
$stmt->execute([$id]);
}
?>

View File

@@ -44,12 +44,13 @@ $user_name_old = $user_data['username'];
$view_old = $user_data['view'];
$partnerhierarchy_old = json_decode($user_data['partnerhierarchy']);
$salesid_new = ((isset($post_content['salesid']) && $post_content['salesid'] != '' && $post_content['salesid'] != $partnerhierarchy_old->salesid)? $post_content['salesid'] : $partnerhierarchy_old->salesid);
$soldto_new = ((isset($post_content['soldto']) && $post_content['soldto'] != '' && $post_content['soldto'] != $partnerhierarchy_old->soldto)? $post_content['soldto'] : $partnerhierarchy_old->soldto);
$shipto_new = ((isset($post_content['shipto']) && $post_content['shipto'] != '' && $post_content['shipto'] != $partnerhierarchy_old->shipto)? $post_content['shipto'] : $partnerhierarchy_old->shipto);
$location_new = ((isset($post_content['location']) && $post_content['location'] != '' && $post_content['location'] != $partnerhierarchy_old->location)? $post_content['location'] : $partnerhierarchy_old->location);
// Allow clearing values by checking if key exists (even if empty)
$salesid_new = (array_key_exists('salesid', $post_content)) ? $post_content['salesid'] : ($partnerhierarchy_old->salesid ?? '');
$soldto_new = (array_key_exists('soldto', $post_content)) ? $post_content['soldto'] : ($partnerhierarchy_old->soldto ?? '');
$shipto_new = (array_key_exists('shipto', $post_content)) ? $post_content['shipto'] : ($partnerhierarchy_old->shipto ?? '');
$location_new = (array_key_exists('location', $post_content)) ? $post_content['location'] : ($partnerhierarchy_old->location ?? '');
if ($permission == 4){
if (getHierarchyLevel($partner) == 0){
//ADMIN+ ONLY ARE ALLOWED TO CHANGE SALES AND SOLD
$account = array(
"salesid"=>$salesid_new,
@@ -57,7 +58,7 @@ $location_new = ((isset($post_content['location']) && $post_content['location']
"shipto"=>$shipto_new,
"location"=>$location_new
);
}elseif ($permission == 3) {
}elseif (getHierarchyLevel($partner) == 1) {
//ADMIN ONLY ARE ALLOWED TO CHANGE SOLD
$account = array(
"salesid"=>$partner->salesid,
@@ -76,7 +77,7 @@ $location_new = ((isset($post_content['location']) && $post_content['location']
}
} elseif ($command == 'insert') {
//ID is empty => INSERT / NEW RECORD
if ($permission == 4){
if (getHierarchyLevel($partner) == 0){
//ADMIN+ ONLY ARE ALLOWED TO CHANGE SALES AND SOLD
$account = array(
"salesid"=>$post_content['salesid'],
@@ -85,7 +86,7 @@ $location_new = ((isset($post_content['location']) && $post_content['location']
"location"=>$post_content['location']
);
}
elseif ($permission == 3){
elseif (getHierarchyLevel($partner) == 1){
//ADMIN ONLY ARE ALLOWED TO CHANGE SOLD
$account = array(
"salesid"=>$partner->salesid,
@@ -153,12 +154,15 @@ else {
//+++++++++++++++++++++++++++++++++++++++++++++
//RESET VIEW/PERMISSION BASED ON USER PERMISSION
//+++++++++++++++++++++++++++++++++++++++++++++
$hierarchy_level = getHierarchyLevel($partner);
if($post_content['view']){
switch ($permission) {
case '4':
switch ($hierarchy_level) {
case '0':
//ADMIN+ no override
break;
case '3':
case '1':
//ADMINS cannot set ADMIN+ => reset to admin
$post_content['view'] = ($post_content['view'] == 5) ? 4 : $post_content['view'];
break;

View File

@@ -20,6 +20,66 @@ document.querySelector('.responsive-toggle').onclick = event => {
localStorage.setItem('admin_menu', 'closed');
}
};
// Menu header collapse/expand functionality
document.querySelectorAll('aside .menu-header').forEach(header => {
header.addEventListener('click', function(event) {
event.preventDefault();
// Toggle expanded state
this.classList.toggle('expanded');
// Find the next sibling .sub element and toggle display
const submenu = this.nextElementSibling;
if (submenu && submenu.classList.contains('sub')) {
submenu.classList.toggle('expanded');
// Update inline style for display
submenu.style.display = submenu.classList.contains('expanded') ? 'flex' : 'none';
}
// Rotate chevron
const chevron = this.querySelector('.menu-chevron');
if (chevron) {
chevron.style.transform = this.classList.contains('expanded') ? 'rotate(180deg)' : 'rotate(0deg)';
}
// Store expanded state in localStorage for persistence
const section = this.dataset.section;
if (section) {
const expandedSections = JSON.parse(localStorage.getItem('menu_expanded') || '{}');
expandedSections[section] = this.classList.contains('expanded');
localStorage.setItem('menu_expanded', JSON.stringify(expandedSections));
}
});
});
// Restore menu expanded states from localStorage on page load
(function restoreMenuState() {
const expandedSections = JSON.parse(localStorage.getItem('menu_expanded') || '{}');
document.querySelectorAll('aside .menu-header').forEach(header => {
const section = header.dataset.section;
const submenu = header.nextElementSibling;
const chevron = header.querySelector('.menu-chevron');
// If explicitly saved as expanded, apply it
if (section && expandedSections[section] === true) {
header.classList.add('expanded');
if (submenu && submenu.classList.contains('sub')) {
submenu.classList.add('expanded');
submenu.style.display = 'flex';
}
if (chevron) chevron.style.transform = 'rotate(180deg)';
}
// If has selected child, always expand (override localStorage)
if (submenu && submenu.querySelector('a.selected')) {
header.classList.add('expanded');
submenu.classList.add('expanded');
submenu.style.display = 'flex';
if (chevron) chevron.style.transform = 'rotate(180deg)';
}
});
})();
document.querySelectorAll('.tabs a').forEach((element, index) => {
element.onclick = event => {
event.preventDefault();
@@ -1159,13 +1219,21 @@ function sortTextVal(a, b) {
//------------------------------------------
function printDiv(div) {
var divContents = document.getElementById(div).innerHTML;
var a = window.open('', '', '');
a.document.write('<html>');
a.document.write('<body > ');
a.document.write(divContents);
a.document.write('</body></html>');
a.document.close();
a.print();
var printWindow = window.open('', '', 'height=600,width=800');
printWindow.document.write('<html><head><title>Print</title>');
printWindow.document.write('<style>');
printWindow.document.write('body { font-family: Arial, sans-serif; margin: 20px; }');
printWindow.document.write('table { border-collapse: collapse; width: 100%; }');
printWindow.document.write('th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }');
printWindow.document.write('th { background-color: #f2f2f2; }');
printWindow.document.write('</style>');
printWindow.document.write('</head><body>');
printWindow.document.write(divContents);
printWindow.document.write('</body></html>');
printWindow.document.close();
printWindow.focus();
printWindow.print();
printWindow.close();
}
//------------------------------------------

View File

@@ -0,0 +1,114 @@
-- Marketing System Database Tables
-- Run this script to create the necessary tables for the marketing file management system
--
-- Usage: Import this file into your MySQL database or run the commands individually
-- Make sure to select the correct database before running these commands
-- Disable foreign key checks temporarily to avoid constraint errors
SET FOREIGN_KEY_CHECKS = 0;
-- Create marketing_folders table
CREATE TABLE IF NOT EXISTS `marketing_folders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`folder_name` varchar(255) NOT NULL,
`parent_id` int(11) DEFAULT NULL,
`description` text DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`createdby` varchar(100) DEFAULT NULL,
`updated` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`updatedby` varchar(100) DEFAULT NULL,
`accounthierarchy` text DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `parent_id` (`parent_id`),
KEY `accounthierarchy_idx` (`accounthierarchy`(100))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create marketing_files table
CREATE TABLE IF NOT EXISTS `marketing_files` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`original_filename` varchar(255) NOT NULL,
`file_path` varchar(500) NOT NULL,
`thumbnail_path` varchar(500) DEFAULT NULL,
`file_type` varchar(10) NOT NULL,
`file_size` bigint(20) NOT NULL DEFAULT 0,
`folder_id` int(11) DEFAULT NULL,
`tags` json DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`createdby` varchar(100) DEFAULT NULL,
`updated` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`updatedby` varchar(100) DEFAULT NULL,
`accounthierarchy` text DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `folder_id` (`folder_id`),
KEY `file_type` (`file_type`),
KEY `accounthierarchy_idx` (`accounthierarchy`(100)),
KEY `created_idx` (`created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create marketing_tags table
CREATE TABLE IF NOT EXISTS `marketing_tags` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tag_name` varchar(100) NOT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `tag_name` (`tag_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create marketing_file_tags junction table
CREATE TABLE IF NOT EXISTS `marketing_file_tags` (
`file_id` int(11) NOT NULL,
`tag_id` int(11) NOT NULL,
PRIMARY KEY (`file_id`, `tag_id`),
KEY `file_id` (`file_id`),
KEY `tag_id` (`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Add foreign key constraints after all tables are created
ALTER TABLE `marketing_folders`
ADD CONSTRAINT `fk_marketing_folders_parent`
FOREIGN KEY (`parent_id`) REFERENCES `marketing_folders`(`id`) ON DELETE CASCADE;
ALTER TABLE `marketing_files`
ADD CONSTRAINT `fk_marketing_files_folder`
FOREIGN KEY (`folder_id`) REFERENCES `marketing_folders`(`id`) ON DELETE SET NULL;
ALTER TABLE `marketing_file_tags`
ADD CONSTRAINT `fk_marketing_file_tags_file`
FOREIGN KEY (`file_id`) REFERENCES `marketing_files`(`id`) ON DELETE CASCADE;
ALTER TABLE `marketing_file_tags`
ADD CONSTRAINT `fk_marketing_file_tags_tag`
FOREIGN KEY (`tag_id`) REFERENCES `marketing_tags`(`id`) ON DELETE CASCADE;
-- Re-enable foreign key checks
SET FOREIGN_KEY_CHECKS = 1;
-- Insert some default sample data (optional)
-- Uncomment the lines below if you want to start with sample folders and tags
-- INSERT INTO `marketing_folders` (`folder_name`, `description`, `createdby`) VALUES
-- ('Product Brochures', 'Marketing brochures and product information', 'system'),
-- ('Technical Specifications', 'Technical documentation and specifications', 'system'),
-- ('Images', 'Product images and photos', 'system'),
-- ('Videos', 'Product videos and demonstrations', 'system');
-- INSERT INTO `marketing_tags` (`tag_name`) VALUES
-- ('brochure'),
-- ('specification'),
-- ('manual'),
-- ('image'),
-- ('video'),
-- ('product'),
-- ('marketing'),
-- ('technical');
-- Create upload directories (Note: This requires manual creation on file system)
-- Create the following directories in your web server:
-- - ./marketing/uploads/
-- - ./marketing/uploads/thumbs/
--
-- Linux/macOS commands:
-- mkdir -p marketing/uploads/thumbs
-- chmod 755 marketing/uploads
-- chmod 755 marketing/uploads/thumbs

View File

@@ -0,0 +1,222 @@
-- ===================================================
-- PROFILE TO RBAC MIGRATION SCRIPT
-- Date: 2025-01-22
-- Description: Migrate from settingsprofiles.php to user_roles RBAC system
-- Note: Uses existing access_elements table (already populated)
-- ===================================================
START TRANSACTION;
-- ===================================================
-- PHASE 1: CREATE ROLES (matching existing profiles)
-- ===================================================
INSERT INTO `user_roles` (`name`, `description`, `is_active`, `created`, `createdby`) VALUES
('Standard', 'Basic user access - view equipment, history, service reports', 1, NOW(), 1),
('Superuser', 'Extended access - manage equipment, products, users', 1, NOW(), 1),
('Admin', 'Administrative access - full management capabilities', 1, NOW(), 1),
('AdminPlus', 'System administrator - complete system access', 1, NOW(), 1),
('Build', 'Build tool access only', 1, NOW(), 1),
('Commerce', 'E-commerce and catalog management', 1, NOW(), 1),
('Distribution', 'Distribution partner access', 1, NOW(), 1),
('Firmware', 'Firmware/software update access only', 1, NOW(), 1),
('Garage', 'Car testing and diagnostics', 1, NOW(), 1),
('Interface', 'API/Interface access', 1, NOW(), 1),
('Service', 'Service technician access', 1, NOW(), 1),
('Other', 'Miscellaneous access level', 1, NOW(), 1)
ON DUPLICATE KEY UPDATE `description` = VALUES(`description`);
-- ===================================================
-- PHASE 2: CREATE ROLE_ACCESS_PERMISSIONS MAPPINGS
-- ===================================================
-- Get role IDs
SET @role_standard = (SELECT rowID FROM user_roles WHERE name = 'Standard' LIMIT 1);
SET @role_superuser = (SELECT rowID FROM user_roles WHERE name = 'Superuser' LIMIT 1);
SET @role_admin = (SELECT rowID FROM user_roles WHERE name = 'Admin' LIMIT 1);
SET @role_adminplus = (SELECT rowID FROM user_roles WHERE name = 'AdminPlus' LIMIT 1);
SET @role_build = (SELECT rowID FROM user_roles WHERE name = 'Build' LIMIT 1);
SET @role_commerce = (SELECT rowID FROM user_roles WHERE name = 'Commerce' LIMIT 1);
SET @role_distribution = (SELECT rowID FROM user_roles WHERE name = 'Distribution' LIMIT 1);
SET @role_firmware = (SELECT rowID FROM user_roles WHERE name = 'Firmware' LIMIT 1);
SET @role_garage = (SELECT rowID FROM user_roles WHERE name = 'Garage' LIMIT 1);
SET @role_interface = (SELECT rowID FROM user_roles WHERE name = 'Interface' LIMIT 1);
SET @role_service = (SELECT rowID FROM user_roles WHERE name = 'Service' LIMIT 1);
SET @role_other = (SELECT rowID FROM user_roles WHERE name = 'Other' LIMIT 1);
-- ===================================================
-- STANDARD ROLE PERMISSIONS (Read-only)
-- Profile: application,firmwaretool,histories,history,servicereport,servicereports,dashboard,profile,equipment,equipments,products_software
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_standard, rowID, 0, 1, 0, 0 FROM access_elements WHERE access_path IN (
'application', 'firmwaretool', 'histories', 'history', 'servicereport', 'servicereports',
'dashboard', 'profile', 'equipment', 'equipments', 'products_software'
)
ON DUPLICATE KEY UPDATE can_read = 1;
-- ===================================================
-- SUPERUSER ROLE PERMISSIONS (Create, Read, Update)
-- Profile: application,assets,firmwaretool,histories,history,history_manage,marketing,partner,partners,
-- servicereport,servicereports,admin,dashboard,profile,equipment,equipment_manage,
-- equipment_manage_edit,equipments,equipments_mass_update,product,product_manage,products,
-- products_software,products_versions,user,user_manage,users
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_superuser, rowID, 1, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'firmwaretool', 'histories', 'history', 'history_manage',
'marketing', 'partner', 'partners', 'servicereport', 'servicereports',
'dashboard', 'profile', 'equipment', 'equipment_manage',
'equipments', 'equipments_mass_update', 'product', 'product_manage', 'products',
'products_software', 'products_versions', 'user', 'users'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1;
-- ===================================================
-- ADMIN ROLE PERMISSIONS (Full CRUD)
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_admin, rowID, 1, 1, 1, 1 FROM access_elements WHERE access_path IN (
'application', 'buildtool', 'cartest', 'cartest_manage', 'cartests',
'changelog', 'communication', 'communication_send', 'communications', 'firmwaretool',
'histories', 'history', 'history_manage', 'marketing', 'partner', 'partners',
'servicereport', 'servicereports', 'software_available', 'software_download',
'software_update', 'softwaretool', 'account', 'accounts', 'dashboard', 'profile',
'contract', 'contract_manage', 'contracts', 'equipment', 'equipment_data',
'equipment_healthindex', 'equipment_history', 'equipment_manage',
'equipments', 'equipments_mass_update', 'product', 'product_manage', 'products',
'products_software', 'products_software_assignment', 'products_software_assignments',
'products_software_licenses', 'products_versions', 'report_build',
'report_contracts_billing', 'report_healthindex', 'rma', 'rma_history',
'rma_manage', 'rmas', 'user', 'users'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1, can_delete = 1;
-- ===================================================
-- ADMINPLUS ROLE PERMISSIONS (Full access to everything)
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_adminplus, rowID, 1, 1, 1, 1 FROM access_elements WHERE is_active = 1
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1, can_delete = 1;
-- ===================================================
-- BUILD ROLE PERMISSIONS
-- Profile: application,buildtool,firmwaretool,dashboard,profile,products_software
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_build, rowID, 1, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'buildtool', 'firmwaretool', 'dashboard', 'profile', 'products_software'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1;
-- ===================================================
-- COMMERCE ROLE PERMISSIONS
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_commerce, rowID, 1, 1, 1, 1 FROM access_elements WHERE access_path IN (
'application', 'catalog', 'categories', 'category', 'checkout', 'discount', 'discounts',
'identity', 'invoice', 'media', 'media_manage', 'order', 'orders', 'partner', 'partners',
'placeorder', 'pricelists', 'pricelists_items', 'pricelists_manage', 'shipping',
'shipping_manage', 'shopping_cart', 'taxes', 'transactions', 'transactions_items',
'translation_manage', 'translations', 'translations_details', 'uploader',
'dashboard', 'profile', 'product', 'product_manage', 'products', 'products_attributes',
'products_attributes_items', 'products_attributes_manage', 'products_categories',
'products_configurations', 'products_media', 'products_software', 'products_versions',
'user', 'users'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1, can_delete = 1;
-- ===================================================
-- DISTRIBUTION ROLE PERMISSIONS
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_distribution, rowID, 1, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'firmwaretool', 'histories', 'history', 'history_manage',
'marketing', 'partner', 'partners', 'servicereport', 'servicereports',
'dashboard', 'profile', 'equipment', 'equipment_manage',
'equipments', 'equipments_mass_update', 'product', 'product_manage', 'products',
'products_software', 'products_versions', 'user', 'users'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1;
-- ===================================================
-- FIRMWARE ROLE PERMISSIONS
-- Profile: application,software_available,software_download,software_update,softwaretool,
-- transactions,transactions_items,products_software_versions
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_firmware, rowID, 0, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'software_available', 'software_download', 'software_update',
'softwaretool', 'transactions', 'transactions_items', 'products_software_versions'
)
ON DUPLICATE KEY UPDATE can_read = 1, can_update = 1;
-- ===================================================
-- GARAGE ROLE PERMISSIONS
-- Profile: application,cartest,cartest_manage,cartests,dashboard,profile,products_versions
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_garage, rowID, 1, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'cartest', 'cartest_manage', 'cartests', 'dashboard', 'profile', 'products_versions'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1;
-- ===================================================
-- INTERFACE ROLE PERMISSIONS
-- Profile: application,firmwaretool,invoice,payment,transactions,transactions_items,
-- contract,contracts,equipment_manage,equipments,products_software,products_versions,users
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_interface, rowID, 1, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'firmwaretool', 'invoice', 'payment', 'transactions', 'transactions_items',
'contract', 'contracts', 'equipment_manage', 'equipments', 'products_software',
'products_versions', 'users'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1;
-- ===================================================
-- SERVICE ROLE PERMISSIONS
-- Profile: application,assets,firmwaretool,histories,history,history_manage,marketing,partner,partners,
-- servicereport,servicereports,admin,dashboard,profile,equipment,equipment_manage,equipments,
-- products_software,user,user_manage,users
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_service, rowID, 1, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'firmwaretool', 'histories', 'history', 'history_manage',
'marketing', 'partner', 'partners', 'servicereport', 'servicereports',
'dashboard', 'profile', 'equipment', 'equipment_manage', 'equipments', 'products_software',
'user', 'users'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1;
-- ===================================================
-- OTHER ROLE PERMISSIONS
-- Profile: application,assets,firmwaretool,histories,history,history_manage,marketing,partner,partners,
-- servicereport,servicereports,admin,dashboard,profile,equipment,equipment_manage,equipments,products_software
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_other, rowID, 0, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'firmwaretool', 'histories', 'history', 'history_manage',
'marketing', 'partner', 'partners', 'servicereport', 'servicereports',
'dashboard', 'profile', 'equipment', 'equipment_manage', 'equipments', 'products_software'
)
ON DUPLICATE KEY UPDATE can_read = 1, can_update = 1;
-- ===================================================
-- VERIFICATION QUERIES
-- ===================================================
-- Check roles created
SELECT rowID, name, description, is_active FROM user_roles ORDER BY rowID;
-- Check permissions per role
SELECT ur.name as role_name, COUNT(rap.rowID) as permission_count
FROM user_roles ur
LEFT JOIN role_access_permissions rap ON ur.rowID = rap.role_id
GROUP BY ur.rowID, ur.name
ORDER BY ur.rowID;
-- ===================================================
-- Change ROLLBACK to COMMIT when ready to apply
-- ===================================================
COMMIT;

View File

@@ -0,0 +1,141 @@
-- ===================================================
-- USER TO RBAC ROLE ASSIGNMENT MIGRATION SCRIPT
-- Date: 2025-01-22
-- Description: Migrate users from settings/view fields to user_role_assignments
-- Prerequisites: Run migration_profiles_to_rbac.sql first to create roles
-- ===================================================
START TRANSACTION;
-- ===================================================
-- MAPPING REFERENCE:
--
-- users.settings field values -> role names:
-- 'admin_profile' or view=4 -> TSS_Admin
-- 'distribution' -> Distribution
-- 'service' -> Service
-- 'firmware' -> Software_Tool
-- 'interface' -> Interface
-- 'superuser_profile' or view=1 -> Service
-- All others (including empty/NULL) -> Service
--
-- IGNORED/REMOVED PROFILES:
-- 'standard_profile', 'adminplus_profile', 'build', 'commerce',
-- 'garage', 'other'
-- ===================================================
-- Get role IDs
SET @role_tss_admin = (SELECT rowID FROM user_roles WHERE name = 'TSS_Admin' LIMIT 1);
SET @role_distribution = (SELECT rowID FROM user_roles WHERE name = 'Distribution' LIMIT 1);
SET @role_service = (SELECT rowID FROM user_roles WHERE name = 'Service' LIMIT 1);
SET @role_software_tool = (SELECT rowID FROM user_roles WHERE name = 'Software_Tool' LIMIT 1);
SET @role_interface = (SELECT rowID FROM user_roles WHERE name = 'Interface' LIMIT 1);
-- ===================================================
-- PHASE 1: MIGRATE USERS BY SETTINGS FIELD (profile name)
-- ===================================================
-- Users with 'admin_profile' setting -> TSS_Admin
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_tss_admin, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'admin_profile'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'distribution' setting -> Distribution
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_distribution, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'distribution'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'service' setting -> Service
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_service, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'service'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'firmware' setting -> Software_Tool
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_software_tool, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'firmware'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'interface' setting -> Interface
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_interface, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'interface'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'superuser_profile' setting -> Service
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_service, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'superuser_profile'
ON DUPLICATE KEY UPDATE updated = NOW();
-- ===================================================
-- PHASE 2: MIGRATE USERS WITH EMPTY/NULL SETTINGS (use view field)
-- Only for users not already assigned a role
-- ===================================================
-- Users with view=4 (Admin) and no settings -> TSS_Admin
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT u.id, @role_tss_admin, 1, 'migration_script', NOW(), NOW(), 1
FROM users u
LEFT JOIN user_role_assignments ura ON u.id = ura.user_id AND ura.is_active = 1
WHERE (u.settings IS NULL OR u.settings = '')
AND u.view = '4'
AND ura.rowID IS NULL
ON DUPLICATE KEY UPDATE updated = NOW();
-- ===================================================
-- PHASE 3: CATCH-ALL - Any remaining users without role -> Service
-- ===================================================
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT u.id, @role_service, 1, 'migration_script', NOW(), NOW(), 1
FROM users u
LEFT JOIN user_role_assignments ura ON u.id = ura.user_id AND ura.is_active = 1
WHERE ura.rowID IS NULL
ON DUPLICATE KEY UPDATE updated = NOW();
-- ===================================================
-- VERIFICATION QUERIES
-- ===================================================
-- Check migration results: users per role
SELECT
ur.name as role_name,
COUNT(ura.user_id) as user_count
FROM user_roles ur
LEFT JOIN user_role_assignments ura ON ur.rowID = ura.role_id AND ura.is_active = 1
GROUP BY ur.rowID, ur.name
ORDER BY user_count DESC;
-- Check for users without role assignments (should be 0)
SELECT COUNT(*) as users_without_role
FROM users u
LEFT JOIN user_role_assignments ura ON u.id = ura.user_id AND ura.is_active = 1
WHERE ura.rowID IS NULL;
-- Compare old vs new: show users with their old settings and new role
SELECT
u.id,
u.username,
u.settings as old_profile,
u.view as old_view_level,
ur.name as new_role
FROM users u
LEFT JOIN user_role_assignments ura ON u.id = ura.user_id AND ura.is_active = 1
LEFT JOIN user_roles ur ON ura.role_id = ur.rowID
ORDER BY u.id
LIMIT 50;
-- ===================================================
-- Change ROLLBACK to COMMIT when ready to apply
-- ===================================================
COMMIT;

65
assets/database/user_rbac Normal file
View File

@@ -0,0 +1,65 @@
CREATE TABLE `user_roles` (
`rowID` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`description` text DEFAULT NULL,
`is_active` tinyint(1) NOT NULL DEFAULT 1,
`created` timestamp NULL DEFAULT current_timestamp(),
`createdby` int(11) DEFAULT NULL,
`updated` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`updatedby` int(11) DEFAULT NULL,
PRIMARY KEY (`rowID`),
UNIQUE KEY `unique_role_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `user_role_assignments` (
`rowID` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
`is_active` tinyint(1) NOT NULL DEFAULT 1,
`assigned_by` varchar(255) DEFAULT NULL,
`assigned_at` timestamp NULL DEFAULT current_timestamp(),
`expires_at` timestamp NULL DEFAULT NULL,
`created` timestamp NULL DEFAULT current_timestamp(),
`createdby` int(11) DEFAULT NULL,
`updated` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`updatedby` int(11) DEFAULT NULL,
PRIMARY KEY (`rowID`),
UNIQUE KEY `unique_user_role_active` (`user_id`,`role_id`,`is_active`),
KEY `role_id` (`role_id`),
CONSTRAINT `user_role_assignments_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`rowID`) ON DELETE CASCADE,
CONSTRAINT `user_role_assignments_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `user_roles` (`rowID`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
REATE TABLE `role_access_permissions` (
`rowID` int(11) NOT NULL AUTO_INCREMENT,
`role_id` int(11) NOT NULL,
`access_id` int(11) NOT NULL,
`can_create` tinyint(1) NOT NULL DEFAULT 0,
`can_read` tinyint(1) NOT NULL DEFAULT 1,
`can_update` tinyint(1) NOT NULL DEFAULT 0,
`can_delete` tinyint(1) NOT NULL DEFAULT 0,
`created` timestamp NULL DEFAULT current_timestamp(),
`createdby` int(11) DEFAULT NULL,
`updated` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`updatedby` int(11) DEFAULT NULL,
PRIMARY KEY (`rowID`),
UNIQUE KEY `unique_role_view` (`role_id`,`access_id`),
KEY `access_id` (`access_id`),
CONSTRAINT `role_view_permissions_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `user_roles` (`rowID`) ON DELETE CASCADE,
CONSTRAINT `role_view_permissions_ibfk_2` FOREIGN KEY (`access_id`) REFERENCES `system_views` (`rowID`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=112 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `access_elements` (
`rowID` int(11) NOT NULL AUTO_INCREMENT,
`access_group` varchar(100) NOT NULL,
`access_name` varchar(100) NOT NULL,
`access_path` varchar(255) NOT NULL,
`description` text DEFAULT NULL,
`is_active` tinyint(1) NOT NULL DEFAULT 1,
`created` timestamp NULL DEFAULT current_timestamp(),
`createdby` int(11) DEFAULT NULL,
`updated` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`updatedby` int(11) DEFAULT NULL,
PRIMARY KEY (`rowID`),
UNIQUE KEY `unique_access_path` (`access_path`)
) ENGINE=InnoDB AUTO_INCREMENT=393 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

View File

@@ -0,0 +1,164 @@
<?php
$payment_method = 'payment_method_'.$invoice_data['header']['payment_method'];
$message = '
<!DOCTYPE html>
<html lang="' . strtolower($language) . '">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>' . htmlspecialchars($lbl_invoice) . ' - Total Safety Solutions</title>
</head>
<body style="font-family: Arial, Helvetica, sans-serif; color: #000000; font-size: 14px; line-height: 1.6; margin: 0; padding: 0; background-color: #f4f4f4;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: #f4f4f4;">
<tr>
<td align="center" style="padding: 20px 0;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="600" style="background-color: #ffffff; max-width: 600px;">
<!-- Content -->
<tr>
<td style="padding: 30px 40px;">
<!-- Invoice Title -->
<h1 style="font-size: 24px; font-weight: bold; color: #2c5f5d; margin: 0 0 25px 0;">' . htmlspecialchars($lbl_invoice) . '</h1>
<!-- Company Header -->
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin-bottom: 25px;">
<tr>
<td width="50%" style="vertical-align: top; font-size: 13px; line-height: 1.5;">
<strong style="color: #2c5f5d; font-size: 14px;">Total Safety Solutions B.V.</strong><br>
Laarakkerweg 8<br>
5061 JR OISTERWIJK<br>
Nederland
</td>
<td width="50%" style="vertical-align: top; text-align: left;">
<strong style="color: #2c5f5d; font-size: 14px;">contact-details</strong><br>
Ralf Adams<br>
+31 13 8221480<br>
ralfadams@totalsafetysolutions.nl
</td>
</tr>
</table>
<!-- Customer -->
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin-bottom: 25px;">
<tr>
<td width="50%" style="vertical-align: top; font-size: 13px; line-height: 1.5;">
<strong style="color: #2c5f5d; font-size: 14px;">Customer</strong><br>
'.$invoice_data['customer']['name'].'<br>
'.$invoice_data['customer']['street'].'<br>
'.$invoice_data['customer']['zip'].', '.$invoice_data['customer']['city'].'<br>
'.$invoice_data['customer']['country'].'
</td>
</tr>
</table>
<!-- Invoice Details -->
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin-bottom: 25px; font-size: 13px;">
<tr>
<td width="50%" style="vertical-align: top; padding-right: 10px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 3px 0;"><strong>Invoice Date:</strong></td>
<td style="padding: 3px 0;">' . htmlspecialchars(date('d-m-Y', strtotime($invoice_date))) . '</td>
</tr>
<tr>
<td style="padding: 3px 0;"><strong>Invoice Number:</strong></td>
<td style="padding: 3px 0;">' . htmlspecialchars($order_id) . '</td>
</tr>
<tr>
<td style="padding: 3px 0;"><strong>Your Vat Number:</strong></td>
<td style="padding: 3px 0;">' . htmlspecialchars($invoice_data['customer']['vat_number'] ?? '') . '</td>
</tr>
</table>
</td>
<td width="50%" style="vertical-align: top; padding-left: 10px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 3px 0;"><strong>Reference:</strong></td>
<td style="padding: 3px 0;">Online order</td>
</tr>
<tr>
<td style="padding: 3px 0;"><strong>Order number:</strong></td>
<td style="padding: 3px 0;">' . htmlspecialchars($order_id) . '</td>
</tr>
<tr>
<td style="padding: 3px 0;"><strong>Payment Methodr:</strong></td>
<td style="padding: 3px 0;">' . (${$payment_method} ?? $invoice_data['header']['payment_method'] ). '</td>
</tr>
</table>
</td>
</tr>
</table>
<!-- Items Table -->
<table role="presentation" cellspacing="0" cellpadding="8" border="0" width="100%" style="margin-bottom: 20px; border-bottom: 2px solid #999999;">
<thead>
<tr style="background-color: #f8f8f8;">
<th style="text-align: left; font-weight: bold; font-size: 12px; padding: 10px 8px; border-bottom: 1px solid #999999;">Item code</th>
<th style="text-align: left; font-weight: bold; font-size: 12px; padding: 10px 8px; border-bottom: 1px solid #999999;">Description</th>
<th style="text-align: center; font-weight: bold; font-size: 12px; padding: 10px 8px; border-bottom: 1px solid #999999;">Quantity</th>
<th style="text-align: right; font-weight: bold; font-size: 12px; padding: 10px 8px; border-bottom: 1px solid #999999;">Price</th>
<th style="text-align: right; font-weight: bold; font-size: 12px; padding: 10px 8px; border-bottom: 1px solid #999999;">Total</th>
</tr>
</thead>
<tbody>';
foreach ($items as $item) {
$line_total = $item['price'] * $item['quantity'];
$message .= '<tr>
<td style="padding: 10px 8px; border-bottom: 1px solid #dddddd; font-size: 13px;">SOFTWARE</td>
<td style="padding: 10px 8px; border-bottom: 1px solid #dddddd; font-size: 13px;">' . htmlspecialchars($item['name']);
if ($item['serial_number'] !== 'N/A') {
$message .= '<br><span style="font-size: 12px; color: #666666;">Serial: ' . htmlspecialchars($item['serial_number']) . '</span>';
}
if ($item['license_key'] !== 'Pending') {
$message .= '<br><span style="font-size: 12px; color: #666666;">License: ' . htmlspecialchars($item['license_key']) . '</span>';
}
$message .= '</td>
<td style="text-align: center; padding: 10px 8px; border-bottom: 1px solid #dddddd; font-size: 13px;">' . htmlspecialchars($item['quantity']) . '</td>
<td style="text-align: right; padding: 10px 8px; border-bottom: 1px solid #dddddd; font-size: 13px;">€ ' . number_format($item['price'], 2) . '</td>
<td style="text-align: right; padding: 10px 8px; border-bottom: 1px solid #dddddd; font-size: 13px;">€ ' . number_format($line_total, 2) . '</td>
</tr>';
}
$message .= '</tbody>
</table>
<!-- Totals -->
<table role="presentation" cellspacing="0" cellpadding="5" border="0" width="250" align="right" style="margin-top: 10px; font-size: 13px;">
<tr>
<td style="text-align: left; padding: 5px 0;">' . htmlspecialchars($lbl_subtotal) . '</td>
<td style="text-align: right; padding: 5px 0;">€ ' . number_format($subtotal, 2) . '</td>
</tr>';
if ($tax_amount > 0) {
$message .= '<tr>
<td style="text-align: left; padding: 5px 0;">' . htmlspecialchars($lbl_tax) . '</td>
<td style="text-align: right; padding: 5px 0;">€ ' . number_format($tax_amount, 2) . '</td>
</tr>';
} else {
$message .= '<tr>
<td style="text-align: left; padding: 5px 0;">VAT</td>
<td style="text-align: right; padding: 5px 0;">included</td>
</tr>';
}
$message .= '<tr style="border-top: 2px solid #000000;">
<td style="text-align: left; padding: 8px 0 5px 0;"><strong>' . htmlspecialchars($lbl_total) . '</strong></td>
<td style="text-align: right; padding: 8px 0 5px 0;"><strong>€ ' . number_format($payment_amount, 2) . '</strong></td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>';

View File

@@ -0,0 +1,329 @@
<?php
$payment_method = 'payment_method_'.$invoice_data['header']['payment_method'];
$pdf = '<!DOCTYPE html>
<html lang="' . strtolower($language) . '">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>' . htmlspecialchars($lbl_invoice) . ' - Total Safety Solutions</title>
<style>
@page {margin: 220px 50px; }
body {
font-family: "DejaVu Sans", Arial, sans-serif;
color: #000;
font-size: 11px;
line-height: 1.4;
margin: 10px auto;
padding: 0;
width:90%;
}
#header {
position: fixed;
left: -52px;
top: -220px;
right: -50px;
height: 200px;
text-align: center;
border-radius: 5px;
}
#header img {
width: 100%;
}
#footer {
position: fixed;
left: -50px;
bottom: -250px;
right: -50px;
height: 150px;
border-radius: 5px;
}
#footer img {
width: 100%;
}
.invoice-title {
font-size: 18px;
font-weight: bold;
color: #2c5f5d;
margin-bottom: 20px;
}
.company-header {
display: table;
width: 100%;
margin-bottom: 25px;
}
.company-info, .contact-details {
display: table-cell;
vertical-align: top;
width: 50%;
}
.company-info h3 {
font-weight: bold;
margin: 0 0 5px 0;
color: #2c5f5d;
font-size: 12px;
}
.company-info p, .contact-details p {
margin: 0;
line-height: 1.2;
}
.contact-details {
text-align: right;
}
.contact-details h3 {
font-weight: bold;
margin: 0 0 5px 0;
color: #2c5f5d;
font-size: 12px;
}
.invoice-details {
display: table;
width: 100%;
margin-bottom: 25px;
}
.invoice-left, .invoice-right {
display: table-cell;
vertical-align: top;
width: 50%;
}
.invoice-right {
text-align: left;
}
.detail-row {
margin-bottom: 2px;
display: table;
width: 100%;
}
.detail-label {
display: table-cell;
font-weight: normal;
width: 140px;
padding-right: 10px;
}
.detail-value {
display: table-cell;
}
.detail-value {
display: table-cell;
}
.items-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 30px;
border-bottom: 1px solid #999;
}
.items-table th {
padding: 8px 6px;
font-weight: bold;
text-align: left;
font-size: 10px;
}
.items-table td {
padding: 6px;
border-bottom: 1px solid #999;
font-size: 10px;
}
.items-table .qty-col {
text-align: center;
width: 70px;
}
.items-table .price-col, .items-table .total-col {
text-align: right;
width: 80px;
}
.items-table th.qty-col {
text-align: center;
}
.items-table th.price-col, .items-table th.total-col {
text-align: right;
}
.totals-section {
float: right;
width: 250px;
margin-top: 20px;
clear: both;
}
.total-row {
display: table;
width: 100%;
margin-bottom: 3px;
}
.total-label {
display: table-cell;
text-align: left;
width: 150px;
}
.total-amount {
display: table-cell;
text-align: right;
font-weight: normal;
}
.final-total {
border-top: 1px solid #000;
padding-top: 5px;
margin-top: 8px;
}
.final-total .total-amount {
font-weight: bold;
}
</style>
</head>
<body>
<div id="header">
<img src="https://'.$portalURL.'/assets/images/TSS_invoice_header.png" alt="Invoice header">
</div>
<div id="footer">
<img src="https://'.$portalURL.'/assets/images/TSS_invoice_footer.png" alt="Invoice footer">
</div>
<div class="invoice-title">' . htmlspecialchars($lbl_invoice) . '</div>
<div class="company-header">
<div class="company-info">
<h3>Total Safety Solutions B.V.</h3>
<p>Laarakkerweg 8</p>
<p>5061 JR OISTERWIJK</p>
<p>Nederland</p>
</div>
<div class="contact-details">
<h3>contact-details</h3>
<p>Ralf Adams</p>
<p>+31 13 8221480</p>
<p>ralfadams@totalsafetysolutions.nl</p>
</div>
</div>
<div class="company-header">
<div class="company-info">
<h3>Customer</h3>
<p>'.$invoice_data['customer']['name'].'</p>
<p>'.$invoice_data['customer']['street'].'</p>
<p>'.$invoice_data['customer']['zip'].', '.$invoice_data['customer']['city'].'</p>
<p>'.$invoice_data['customer']['country'].'</p>
</div>
</div>
<div class="invoice-details">
<div class="invoice-left">
<div class="detail-row">
<div class="detail-label">Invoice Date</div>
<div class="detail-value">: ' . htmlspecialchars(date('d-m-Y', strtotime($invoice_date))) . '</div>
</div>
<div class="detail-row">
<div class="detail-label">Invoice Number</div>
<div class="detail-value">: ' . htmlspecialchars($order_id) . '</div>
</div>
<div class="detail-row">
<div class="detail-label">Your Vat Number</div>
<div class="detail-value">: ' . htmlspecialchars($invoice_data['customer']['vat_number'] ?? '') . '</div>
</div>
</div>
<div class="invoice-right">
<div class="detail-row">
<div class="detail-label">Reference</div>
<div class="detail-value">: Online order</div>
</div>
<div class="detail-row">
<div class="detail-label">Order number</div>
<div class="detail-value">: ' . htmlspecialchars($order_id) . '</div>
</div>
<div class="detail-row">
<div class="detail-label">Payment Method</div>
<div class="detail-value">: ' . (${$payment_method} ?? $invoice_data['header']['payment_method'] ). '</div>
</div>
</div>
</div>
<table class="items-table">
<thead>
<tr>
<th>Item code</th>
<th>Description</th>
<th class="qty-col">Quantity</th>
<th class="price-col">Price</th>
<th class="total-col">Total</th>
</tr>
</thead>
<tbody>';
foreach ($items as $item) {
$line_total = $item['price'] * $item['quantity'];
$pdf .= '<tr>
<td>SOFTWARE</td>
<td>' . htmlspecialchars($item['name']);
if ($item['serial_number'] !== 'N/A') {
$pdf .= '<br><small>Serial: ' . htmlspecialchars($item['serial_number']) . '</small>';
}
if ($item['license_key'] !== 'Pending') {
$pdf .= '<br><small>License: ' . htmlspecialchars($item['license_key']) . '</small>';
}
$pdf .= '</td>
<td class="qty-col">' . htmlspecialchars($item['quantity']) . ' </td>
<td class="price-col">€ ' . number_format($item['price'], 2) . '</td>
<td class="total-col">€ ' . number_format($line_total, 2) . '</td>
</tr>';
}
$pdf .= '</tbody>
</table>
<div class="totals-section">
<div class="total-row">
<div class="total-label">' . htmlspecialchars($lbl_subtotal) . '</div>
<div class="total-amount">€ ' . number_format($subtotal, 2) . '</div>
</div>';
if ($tax_amount > 0) {
$pdf .= '<div class="total-row">
<div class="total-label">' . htmlspecialchars($lbl_tax) . '</div>
<div class="total-amount">€ ' . number_format($tax_amount, 2) . '</div>
</div>';
} else {
$pdf .= '<div class="total-row">
<div class="total-label">VAT</div>
<div class="total-amount">included</div>
</div>';
}
$pdf .= '<div class="total-row final-total">
<div class="total-label">' . htmlspecialchars($lbl_total) . '</div>
<div class="total-amount">€ ' . number_format($payment_amount, 2) . '</div>
</div>
</div>
</body>
</html>';

1354
assets/marketing.js Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

View File

@@ -124,7 +124,7 @@ async function connectDevice() {
// Log connection failure details
await logCommunication(`Serial connection failed: ${error.message || 'Unknown error'}`, 'disconnected');
if (openPort === 1){
if (openPort = 1){
closePort();
console.log("Closing port");
alert("System is still trying to close the serial port. If this message continues to come up please refresh this page.");

View File

@@ -10,11 +10,135 @@ let deviceVersion = "";
let deviceHwVersion = "";
let selectedSoftwareUrl = "";
// Helper function to generate country select options
function generateCountryOptions(selectedCountry = '') {
if (typeof COUNTRIES === 'undefined' || !COUNTRIES) {
return `<option value="">${typeof TRANS_COUNTRY !== 'undefined' ? TRANS_COUNTRY : 'Country'}</option>`;
}
// Sort countries alphabetically
const sortedCountries = Object.values(COUNTRIES).sort((a, b) => {
return a.country.localeCompare(b.country);
});
let options = '<option value="">Select country</option>';
sortedCountries.forEach(data => {
const selected = (selectedCountry === data.country) ? 'selected' : '';
options += `<option value="${data.country}" ${selected}>${data.country}</option>`;
});
return options;
}
// Serial port variables (port, writer, textEncoder, writableStreamClosed declared in PHP)
let reader;
let readableStreamClosed;
let keepReading = true;
// Browser compatibility check
let isSerialSupported = false;
// Check browser compatibility on page load
function checkBrowserCompatibility() {
isSerialSupported = 'serial' in navigator;
if (!isSerialSupported) {
// Show warning banner
showBrowserWarningBanner();
// Disable connect button
disableSerialFunctionality();
}
return isSerialSupported;
}
function showBrowserWarningBanner() {
const connectDevice = document.getElementById("connectdevice");
if (!connectDevice) return;
const warningBanner = document.createElement("div");
warningBanner.id = "browserWarningBanner";
warningBanner.style.cssText = `
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
color: white;
padding: 15px 20px;
border-radius: 8px;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 15px;
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.3);
`;
warningBanner.innerHTML = `
<i class="fa-solid fa-exclamation-triangle" style="font-size: 24px;"></i>
<div style="flex: 1;">
<strong style="display: block; margin-bottom: 5px; font-size: 16px;">Browser Not Supported</strong>
<p style="margin: 0; font-size: 14px; opacity: 0.95;">
Please use <strong>Chrome</strong>, <strong>Edge</strong>, or <strong>Opera</strong> to access device connectivity features.
</p>
</div>
`;
connectDevice.parentNode.insertBefore(warningBanner, connectDevice);
}
function disableSerialFunctionality() {
const connectButton = document.getElementById("connectButton");
if (connectButton) {
connectButton.disabled = true;
connectButton.style.opacity = "0.5";
connectButton.style.cursor = "not-allowed";
connectButton.title = "Browser is not supported. Please use Chrome, Edge, or Opera.";
}
}
// Call on page load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', checkBrowserCompatibility);
} else {
checkBrowserCompatibility();
}
// Shared serial port reference for upload.js to use
window.sharedSerialPort = null;
// Override requestPort to minimize user prompts
// This intercepts all requestPort calls (including from upload.js) to reuse authorized ports
if ('serial' in navigator) {
const originalRequestPort = navigator.serial.requestPort.bind(navigator.serial);
navigator.serial.requestPort = async function(options) {
// If we have a shared port, return it instead of prompting
if (window.sharedSerialPort) {
console.log('Using shared serial port (no prompt needed)');
return window.sharedSerialPort;
}
// Try already-authorized ports matching the filters
const ports = await navigator.serial.getPorts();
if (ports.length > 0 && options?.filters) {
const match = ports.find(p => {
const info = p.getInfo();
return options.filters.some(f =>
info.usbVendorId === f.usbVendorId &&
info.usbProductId === f.usbProductId
);
});
if (match) {
console.log('Using previously authorized port (no prompt needed)');
window.sharedSerialPort = match;
return match;
}
}
// Fallback: original prompt behavior
const port = await originalRequestPort(options);
window.sharedSerialPort = port; // Store for future use
return port;
};
}
// Function to log communication to API (reused from scripts.js)
async function logCommunication(data, direction) {
// Only log if debug mode is enabled
@@ -69,14 +193,37 @@ function progressBar(percentage, message, color){
// Connect device for software tool
async function connectDeviceForSoftware() {
// Browser compatibility check
if (!isSerialSupported) {
progressBar("100", "Browser not supported - Please use Chrome, Edge, or Opera", "#dc3545");
await logCommunication('Connection attempt failed: Web Serial API not supported in browser', 'error');
return;
}
//clear input
readBar.innerHTML = '';
serialResultsDiv.innerHTML = '';
// Clear installation status if it exists
const installStatus = document.getElementById("installationStatus");
if (installStatus) {
installStatus.remove();
}
document.getElementById("softwareCheckStatus").style.display = "none";
document.getElementById("softwareOptions").style.display = "none";
document.getElementById("softwareOptionsContainer").style.display = "none";
document.getElementById("noUpdatesMessage").style.display = "none";
document.getElementById("uploadSection").style.display = "none";
// Reset softwareOptions visibility and blur state
const softwareOptions = document.getElementById("softwareOptions");
if (softwareOptions) {
softwareOptions.style.display = "block";
softwareOptions.style.filter = "blur(8px)";
softwareOptions.style.opacity = "0.3";
softwareOptions.style.pointerEvents = "none";
}
// Reset data
receivedDataBuffer = '';
deviceSerialNumber = "";
@@ -87,7 +234,7 @@ async function connectDeviceForSoftware() {
progressBar("1", "", "");
// Check if DEBUG mode is enabled - use mock device data
if (typeof DEBUG !== 'undefined' && DEBUG) {
if (typeof DEBUG !== 'undefined' && DEBUG && typeof DEBUG_ID !== 'undefined' && DEBUG_ID) {
// TEST MODE: Use mock device data
deviceSerialNumber = "22110095";
deviceVersion = "03e615af";
@@ -161,7 +308,20 @@ async function connectDeviceForSoftware() {
} catch (error) {
await logCommunication(`Connection error: ${error.message}`, 'error');
progressBar("0", "Error: " + error.message, "#ff6666");
// Improved error messages with specific cases
if (error.name === 'NotSupportedError' || !navigator.serial) {
progressBar("100", "Browser not supported - Please use Chrome, Edge, or Opera", "#dc3545");
} else if (error.message && error.message.includes('No port selected by the user')) {
progressBar("100", "No device selected - Please try again", "#ff6666");
} else if (error.name === 'NetworkError') {
progressBar("100", "Connection failed - Please check device connection", "#ff6666");
} else if (error.name === 'InvalidStateError') {
progressBar("100", "Port already in use - Refreshing page...", "#ff9800");
setTimeout(() => location.reload(), 2000);
} else {
progressBar("100", "Connection error: " + error.message, "#ff6666");
}
}
}
@@ -295,7 +455,11 @@ async function closePortAfterRead() {
await port.close();
await logCommunication('Port closed successfully', 'info');
// Reset for next connection
// Keep port reference in sharedSerialPort for upload.js to reuse
// This prevents the need for another user prompt during firmware upload
window.sharedSerialPort = port;
// Reset local variables for next connection
reader = null;
writer = null;
readableStreamClosed = null;
@@ -305,7 +469,12 @@ async function closePortAfterRead() {
console.error('Error closing port after read:', error);
await logCommunication(`Error closing port: ${error.message}`, 'error');
// Force reset even on error
// Keep port reference even on error if port exists
if (port) {
window.sharedSerialPort = port;
}
// Force reset local variables even on error
reader = null;
writer = null;
readableStreamClosed = null;
@@ -437,12 +606,28 @@ async function fetchSoftwareOptions() {
return;
}
// Display options in table
// Display options in table (blurred initially)
displaySoftwareOptions(options);
document.getElementById("softwareCheckStatus").style.display = "none";
document.getElementById("softwareOptions").style.display = "block";
document.getElementById("softwareOptionsContainer").style.display = "block";
progressBar("100", "Software options loaded", "#04AA6D");
// Check if customer data already exists in sessionStorage
const savedCustomerData = sessionStorage.getItem('customerData');
// Show user info modal only if no saved data and not in debug mode
if ((typeof DEBUG === 'undefined' || !DEBUG || typeof DEBUG_ID === 'undefined' || !DEBUG_ID) && !savedCustomerData) {
showUserInfoModal();
} else {
// Customer data already exists or debug mode - reveal software options immediately
const softwareOptions = document.getElementById("softwareOptions");
if (softwareOptions) {
softwareOptions.style.filter = "none";
softwareOptions.style.opacity = "1";
softwareOptions.style.pointerEvents = "auto";
}
}
} catch (error) {
await logCommunication(`Software options error: ${error.message}`, 'error');
progressBar("0", "Error loading options: " + error.message, "#ff6666");
@@ -458,6 +643,8 @@ function displaySoftwareOptions(options) {
const price = parseFloat(option.price);
const isFree = price === 0;
const isCurrent = option.is_current === true || option.is_current === 1;
const dealerInfo = option.dealer_info || {};
const isDealer = dealerInfo.is_dealer === 1 || dealerInfo.is_dealer === '1';
// Create card with gradient background
const card = document.createElement("div");
@@ -562,6 +749,68 @@ function displaySoftwareOptions(options) {
margin-top: auto;
`;
// Check if this is a dealer customer - show dealer contact info instead of price/buy button
if (isDealer && !isCurrent && !isFree) {
// Dealer info section - replaces price and buy button
const dealerSection = document.createElement("div");
dealerSection.style.cssText = `
background: linear-gradient(135deg, rgb(255, 107, 53) 0%, rgb(255, 69, 0) 100%);
border-radius: 4px;
padding: 15px;
text-align: center;
`;
// Contact dealer message
const dealerMessage = document.createElement("div");
dealerMessage.style.cssText = `
color: white;
font-size: 14px;
font-weight: 600;
margin-bottom: 12px;
`;
dealerMessage.innerHTML = '<i class="fa-solid fa-handshake" style="margin-right: 8px;"></i>Contact your dealer for pricing and upgrade options';
dealerSection.appendChild(dealerMessage);
// Dealer contact details
const dealerDetails = document.createElement("div");
dealerDetails.style.cssText = `
background: white;
border-radius: 4px;
padding: 12px;
text-align: left;
font-size: 13px;
color: #333;
`;
let dealerHtml = '';
if (dealerInfo.name) {
dealerHtml += `<div style="font-weight: 600; margin-bottom: 8px; color: #1565c0;"><i class="fa-solid fa-building" style="margin-right: 8px; width: 16px;"></i>${dealerInfo.name}</div>`;
}
if (dealerInfo.address || dealerInfo.city || dealerInfo.postalcode || dealerInfo.country) {
dealerHtml += `<div style="margin-bottom: 6px; color: #666; display: flex; align-items: flex-start;"><i class="fa-solid fa-location-dot" style="margin-right: 8px; width: 16px; color: #999; margin-top: 2px;"></i><div>`;
if (dealerInfo.address) {
dealerHtml += `<div>${dealerInfo.address}</div>`;
}
if (dealerInfo.postalcode || dealerInfo.city) {
dealerHtml += `<div>${[dealerInfo.postalcode, dealerInfo.city].filter(Boolean).join(' ')}</div>`;
}
if (dealerInfo.country) {
dealerHtml += `<div>${dealerInfo.country}</div>`;
}
dealerHtml += `</div></div>`;
}
if (dealerInfo.email) {
dealerHtml += `<div style="margin-bottom: 6px;"><i class="fa-solid fa-envelope" style="margin-right: 8px; width: 16px; color: #999;"></i><a href="mailto:${dealerInfo.email}" style="color: #1565c0; text-decoration: none;">${dealerInfo.email}</a></div>`;
}
if (dealerInfo.phone) {
dealerHtml += `<div><i class="fa-solid fa-phone" style="margin-right: 8px; width: 16px; color: #999;"></i><a href="tel:${dealerInfo.phone}" style="color: #1565c0; text-decoration: none;">${dealerInfo.phone}</a></div>`;
}
dealerDetails.innerHTML = dealerHtml;
dealerSection.appendChild(dealerDetails);
priceSection.appendChild(dealerSection);
} else {
// Standard price display for non-dealer customers
const priceText = document.createElement("div");
priceText.style.cssText = `
font-size: ${isCurrent ? '18px' : '28px'};
@@ -577,7 +826,7 @@ function displaySoftwareOptions(options) {
} else {
priceText.innerHTML = isFree
? 'Free'
: `${option.currency || "€"} ${price.toFixed(2)}`;
: `${option.currency || "€"} ${price.toFixed(2)} <small style="font-size: 12px; font-weight: 400; color: #888;">(excl. VAT)</small>`;
}
priceSection.appendChild(priceText);
@@ -630,25 +879,199 @@ function displaySoftwareOptions(options) {
}
priceSection.appendChild(actionBtn);
}
card.appendChild(priceSection);
grid.appendChild(card);
});
}
function showUserInfoModal() {
// Create modal overlay
const modal = document.createElement("div");
modal.id = "userInfoModal";
modal.style.cssText = `
display: flex;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.7);
z-index: 2000;
align-items: center;
justify-content: center;
`;
// Create modal content
const modalContent = document.createElement("div");
modalContent.style.cssText = `
background: white;
border-radius: 12px;
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
margin: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.4);
`;
modalContent.innerHTML = `
<div style="padding: 30px; border-bottom: 2px solid #e0e0e0;">
<h3 style="margin: 0; color: #333; font-size: 24px;">${typeof TRANS_USER_INFO_REQUIRED !== 'undefined' ? TRANS_USER_INFO_REQUIRED : 'User Information Required'}</h3>
<p style="margin: 10px 0 0 0; color: #666; font-size: 14px;">${typeof TRANS_USER_INFO_DESCRIPTION !== 'undefined' ? TRANS_USER_INFO_DESCRIPTION : 'Please provide your information to continue with software updates'}</p>
</div>
<div style="padding: 30px;">
<form id="userInfoForm" style="display: flex; flex-direction: column; gap: 15px;">
<div>
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 600;">${typeof TRANS_NAME !== 'undefined' ? TRANS_NAME : 'Name'} *</label>
<input type="text" name="name" id="userInfoName" required style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'">
</div>
<div>
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 600;">${typeof TRANS_EMAIL !== 'undefined' ? TRANS_EMAIL : 'Email'} *</label>
<input type="email" name="email" id="userInfoEmail" required style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'">
</div>
<div>
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 600;">${typeof TRANS_ADDRESS !== 'undefined' ? TRANS_ADDRESS : 'Address'} *</label>
<input type="text" name="address" id="userInfoAddress" required placeholder="Street and number" style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; margin-bottom: 10px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'">
<input type="text" name="city" id="userInfoCity" required placeholder="${typeof TRANS_CITY !== 'undefined' ? TRANS_CITY : 'City'}" style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; margin-bottom: 10px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<input type="text" name="postal" id="userInfoPostal" required placeholder="${typeof TRANS_POSTAL !== 'undefined' ? TRANS_POSTAL : 'Postal code'}" style="padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'">
<select name="country" id="userInfoCountry" required style="padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'">
${generateCountryOptions()}
</select>
</div>
</div>
<div style="margin-top: 10px; display: flex; gap: 10px;">
<button type="button" onclick="location.reload()" style="padding: 15px 20px; background: #6c757d; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; transition: all 0.3s;" onmouseover="this.style.background='#5a6268'" onmouseout="this.style.background='#6c757d'">
<i class="fa-solid fa-times"></i>
</button>
<button type="submit" style="flex: 1; padding: 15px; background: linear-gradient(135deg, #04AA6D 0%, #038f5a 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; transition: all 0.3s;" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 6px 16px rgba(4,170,109,0.3)'" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='none'">
${typeof TRANS_CONTINUE !== 'undefined' ? TRANS_CONTINUE : 'Continue'}
</button>
</div>
</form>
</div>
`;
modal.appendChild(modalContent);
document.body.appendChild(modal);
// Prefill form with customer data from sessionStorage if available
const savedCustomerData = sessionStorage.getItem('customerData');
if (savedCustomerData) {
try {
const customerData = JSON.parse(savedCustomerData);
if (customerData.name) document.getElementById("userInfoName").value = customerData.name;
if (customerData.email) document.getElementById("userInfoEmail").value = customerData.email;
if (customerData.address) document.getElementById("userInfoAddress").value = customerData.address;
if (customerData.city) document.getElementById("userInfoCity").value = customerData.city;
if (customerData.postal) document.getElementById("userInfoPostal").value = customerData.postal;
if (customerData.country) document.getElementById("userInfoCountry").value = customerData.country;
} catch (e) {
console.warn('Error parsing saved customer data:', e);
}
}
// Handle form submission
document.getElementById("userInfoForm").onsubmit = async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const customerData = {
name: formData.get("name"),
email: formData.get("email"),
address: formData.get("address"),
city: formData.get("city"),
postal: formData.get("postal"),
country: formData.get("country")
};
// Save customer data to sessionStorage
sessionStorage.setItem('customerData', JSON.stringify(customerData));
// Send to API
await sendUserInfoToAPI(customerData);
// Close modal
document.body.removeChild(modal);
// Reveal software options by removing blur
const softwareOptions = document.getElementById("softwareOptions");
if (softwareOptions) {
softwareOptions.style.filter = "none";
softwareOptions.style.opacity = "1";
softwareOptions.style.pointerEvents = "auto";
}
};
}
async function sendUserInfoToAPI(customerData) {
try {
const serviceToken = document.getElementById("servicetoken")?.innerHTML || '';
const url = link + '/v2/history';
const bearer = 'Bearer ' + serviceToken;
const historyData = {
sn: deviceSerialNumber,
type: 'customer',
sn_service: 'Portal',
payload: customerData
};
await logCommunication(`Sending user info to API: ${JSON.stringify(historyData)}`, 'sent');
const response = await fetch(url, {
method: 'POST',
withCredentials: true,
credentials: 'include',
headers: {
'Authorization': bearer,
'Content-Type': 'application/json'
},
body: JSON.stringify(historyData)
});
if (!response.ok) {
console.warn('Failed to send user info:', response.status);
await logCommunication(`Failed to send user info: ${response.status}`, 'error');
} else {
const result = await response.json();
console.log("User info sent successfully:", result);
await logCommunication(`User info sent successfully: ${JSON.stringify(result)}`, 'received');
}
} catch (error) {
console.warn('Error sending user info:', error);
await logCommunication(`Error sending user info: ${error.message}`, 'error');
}
}
async function selectUpgrade(option) {
const price = parseFloat(option.price || 0);
const isFree = price === 0;
// If paid upgrade, show payment modal first
// If paid upgrade, show payment modal with pre-filled data
if (!isFree) {
showPaymentModal(option);
return;
}
// Free upgrade - show confirmation modal first
// Free upgrade - proceed directly with saved customer data
const savedCustomerData = sessionStorage.getItem('customerData');
if (savedCustomerData) {
try {
const customerData = JSON.parse(savedCustomerData);
await downloadAndInstallSoftware(option, customerData);
} catch (e) {
console.warn('Error parsing saved customer data:', e);
showFreeInstallModal(option);
}
} else {
showFreeInstallModal(option);
}
}
function showFreeInstallModal(option) {
// Create modal overlay
@@ -693,22 +1116,24 @@ function showFreeInstallModal(option) {
<form id="freeInstallForm" style="display: flex; flex-direction: column; gap: 15px;">
<div>
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">Name *</label>
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">${typeof TRANS_NAME !== 'undefined' ? TRANS_NAME : 'Name'} *</label>
<input type="text" name="name" id="freeInstallName" required style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
</div>
<div>
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">Email *</label>
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">${typeof TRANS_EMAIL !== 'undefined' ? TRANS_EMAIL : 'Email'} *</label>
<input type="email" name="email" id="freeInstallEmail" required style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
</div>
<div>
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">Address *</label>
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">${typeof TRANS_ADDRESS !== 'undefined' ? TRANS_ADDRESS : 'Address'} *</label>
<input type="text" name="address" id="freeInstallAddress" required placeholder="Street and number" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 10px;">
<input type="text" name="city" id="freeInstallCity" required placeholder="City" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 10px;">
<input type="text" name="city" id="freeInstallCity" required placeholder="${typeof TRANS_CITY !== 'undefined' ? TRANS_CITY : 'City'}" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 10px;">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<input type="text" name="postal" id="freeInstallPostal" required placeholder="Postal code" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<input type="text" name="country" id="freeInstallCountry" required placeholder="Country" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<input type="text" name="postal" id="freeInstallPostal" required placeholder="${typeof TRANS_POSTAL !== 'undefined' ? TRANS_POSTAL : 'Postal code'}" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<select name="country" id="freeInstallCountry" required style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
${generateCountryOptions()}
</select>
</div>
</div>
@@ -786,6 +1211,17 @@ function showPaymentModal(option) {
const price = parseFloat(option.price || 0);
const currency = option.currency || "€";
// Format description as bullet points
const formatDescription = (desc) => {
if (!desc) return '';
// Split by bullet points or newlines and filter out empty lines
const lines = desc.split(/[•·\n]/).map(line => line.trim()).filter(line => line.length > 0);
if (lines.length <= 1) return desc; // Return as-is if no multiple lines
return '<ul style="margin: 0; padding-left: 20px; color: #666; font-size: 13px; line-height: 1.6;">' +
lines.map(line => `<li>${line}</li>`).join('') +
'</ul>';
};
// Create modal overlay
const modal = document.createElement("div");
modal.id = "paymentModal";
@@ -823,40 +1259,58 @@ function showPaymentModal(option) {
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<h4 style="margin: 0 0 10px 0; color: #333;">${option.name || "Software Update"}</h4>
<p style="margin: 0 0 5px 0; color: #666;">Version: <strong>${option.version || "N/A"}</strong></p>
<p style="margin: 0 0 15px 0; color: #666;">${option.description || ""}</p>
<div style="font-size: 24px; font-weight: bold; color: #04AA6D;">
${currency} ${price.toFixed(2)}
<div style="margin: 0 0 15px 0;">${formatDescription(option.description)}</div>
<div id="priceDisplay" style="font-size: 14px; color: #666;">
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
<span>Price (excl. VAT):</span>
<span style="font-weight: 600;">${currency} ${price.toFixed(2)}</span>
</div>
<div id="taxDisplay" style="display: flex; justify-content: space-between; margin-bottom: 5px;">
<span>VAT:</span>
<span style="font-weight: 600;">-</span>
</div>
<div style="display: flex; justify-content: space-between; padding-top: 10px; border-top: 2px solid #ddd; margin-top: 10px;">
<span style="font-weight: bold;">Total:</span>
<span id="totalDisplay" style="font-size: 24px; font-weight: bold; color: #04AA6D;">${currency} ${price.toFixed(2)}</span>
</div>
</div>
</div>
<form id="paymentForm" style="display: flex; flex-direction: column; gap: 15px;">
<div>
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">Name *</label>
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">${typeof TRANS_NAME !== 'undefined' ? TRANS_NAME : 'Name'} *</label>
<input type="text" name="name" id="paymentName" required style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
</div>
<div>
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">Email *</label>
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">${typeof TRANS_EMAIL !== 'undefined' ? TRANS_EMAIL : 'Email'} *</label>
<input type="email" name="email" id="paymentEmail" required style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
</div>
<div>
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">Address *</label>
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">${typeof TRANS_ADDRESS !== 'undefined' ? TRANS_ADDRESS : 'Address'} *</label>
<input type="text" name="address" id="paymentAddress" required placeholder="Street and number" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 10px;">
<input type="text" name="city" id="paymentCity" required placeholder="City" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 10px;">
<input type="text" name="city" id="paymentCity" required placeholder="${typeof TRANS_CITY !== 'undefined' ? TRANS_CITY : 'City'}" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 10px;">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<input type="text" name="postal" id="paymentPostal" required placeholder="Postal code" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<input type="text" name="country" id="paymentCountry" required placeholder="Country" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<input type="text" name="postal" id="paymentPostal" required placeholder="${typeof TRANS_POSTAL !== 'undefined' ? TRANS_POSTAL : 'Postal code'}" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<select name="country" id="paymentCountry" required style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
${generateCountryOptions()}
</select>
</div>
</div>
<div>
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">VAT Number</label>
<input type="text" name="vat_number" id="paymentVatNumber" placeholder="Optional" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
</div>
<div>
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">Payment Method *</label>
<select name="payment_method" required style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<option value="">Select payment method</option>
<option value="credit_card">Credit Card</option>
<option value="paypal">PayPal</option>
<option value="bank_transfer">Bank Transfer</option>
${typeof MOLLIE_ENABLED !== 'undefined' && MOLLIE_ENABLED ? '<option value="1">Credit Card</option>' : ''}
${typeof PAYPAL_ENABLED !== 'undefined' && PAYPAL_ENABLED ? '<option value="3">PayPal</option>' : ''}
${typeof PAY_ON_DELIVERY_ENABLED !== 'undefined' && PAY_ON_DELIVERY_ENABLED ? '<option value="bank_transfer">Bank Transfer</option>' : ''}
</select>
</div>
@@ -875,6 +1329,45 @@ function showPaymentModal(option) {
modal.appendChild(modalContent);
document.body.appendChild(modal);
// Function to calculate and update tax
function updateTaxDisplay() {
const selectedCountry = document.getElementById("paymentCountry").value;
let taxRate = 0;
if (selectedCountry && typeof COUNTRIES !== 'undefined' && COUNTRIES) {
const countryData = Object.values(COUNTRIES).find(c => c.country === selectedCountry);
if (countryData) {
taxRate = parseFloat(countryData.taxes) || 0;
}
}
const taxAmount = price * (taxRate / 100);
const totalAmount = price + taxAmount;
// Update display
const taxDisplay = document.getElementById("taxDisplay");
const totalDisplay = document.getElementById("totalDisplay");
if (taxRate > 0) {
taxDisplay.innerHTML = `
<span>VAT (${taxRate}%):</span>
<span style="font-weight: 600;">${currency} ${taxAmount.toFixed(2)}</span>
`;
} else {
taxDisplay.innerHTML = `
<span>VAT:</span>
<span style="font-weight: 600;">-</span>
`;
}
totalDisplay.textContent = `${currency} ${totalAmount.toFixed(2)}`;
// Store tax info for form submission
modal.taxRate = taxRate;
modal.taxAmount = taxAmount;
modal.totalAmount = totalAmount;
}
// Prefill form with customer data from sessionStorage if available
const savedCustomerData = sessionStorage.getItem('customerData');
if (savedCustomerData) {
@@ -885,12 +1378,19 @@ function showPaymentModal(option) {
if (customerData.address) document.getElementById("paymentAddress").value = customerData.address;
if (customerData.city) document.getElementById("paymentCity").value = customerData.city;
if (customerData.postal) document.getElementById("paymentPostal").value = customerData.postal;
if (customerData.country) document.getElementById("paymentCountry").value = customerData.country;
if (customerData.vat_number) document.getElementById("paymentVatNumber").value = customerData.vat_number;
if (customerData.country) {
document.getElementById("paymentCountry").value = customerData.country;
updateTaxDisplay(); // Calculate tax based on saved country
}
} catch (e) {
console.warn('Error parsing saved customer data:', e);
}
}
// Add event listener to country select to update tax
document.getElementById("paymentCountry").addEventListener('change', updateTaxDisplay);
// Close modal on cancel
document.getElementById("cancelPayment").onclick = () => {
document.body.removeChild(modal);
@@ -900,6 +1400,16 @@ function showPaymentModal(option) {
document.getElementById("paymentForm").onsubmit = async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const paymentMethod = formData.get("payment_method");
// Auto-determine payment provider based on payment method
let paymentProvider = 'mollie'; // default
if (paymentMethod === '3') { // PayPal payment method ID
paymentProvider = 'paypal';
} else if (paymentMethod === '1' || paymentMethod === 'bank_transfer') { // Mollie (Credit Card) or Bank Transfer
paymentProvider = 'mollie';
}
const paymentData = {
name: formData.get("name"),
email: formData.get("email"),
@@ -907,9 +1417,13 @@ function showPaymentModal(option) {
city: formData.get("city"),
postal: formData.get("postal"),
country: formData.get("country"),
payment_method: formData.get("payment_method"),
vat_number: formData.get("vat_number") || '',
payment_method: paymentMethod,
payment_provider: paymentProvider,
version_id: option.version_id,
price: price,
item_price: price, // Price without VAT
tax_amount: modal.taxAmount || 0, // Tax amount
payment_amount: modal.totalAmount || price, // Total price including tax
currency: currency
};
@@ -920,7 +1434,8 @@ function showPaymentModal(option) {
address: paymentData.address,
city: paymentData.city,
postal: paymentData.postal,
country: paymentData.country
country: paymentData.country,
vat_number: paymentData.vat_number
}));
await processPayment(paymentData, option, modal);
@@ -1004,7 +1519,9 @@ async function processPayment(paymentData, option, modal) {
const paymentRequest = {
serial_number: deviceSerialNumber,
version_id: option.version_id,
user_data: paymentData // name, email, address only
payment_method: paymentData.payment_method,
payment_provider: paymentData.payment_provider,
user_data: paymentData // name, email, address, etc.
};
// Debug logging
@@ -1012,13 +1529,15 @@ async function processPayment(paymentData, option, modal) {
console.log("=== DEBUG: Payment Request ===");
console.log("Serial Number:", deviceSerialNumber);
console.log("Version ID:", option.version_id);
console.log("Payment Method:", paymentData.payment_method);
console.log("Payment Provider:", paymentData.payment_provider);
console.log("User Data:", paymentData);
console.log("Request payload:", paymentRequest);
}
await logCommunication(`Payment initiated for version ${option.version_id}`, 'sent');
await logCommunication(`Payment initiated for version ${option.version_id} via ${paymentData.payment_provider}`, 'sent');
// Call payment API to create Mollie payment
// Call payment API (handles both Mollie and PayPal)
const response = await fetch(link + "/v2/payment", {
method: "POST",
headers: {
@@ -1046,16 +1565,16 @@ async function processPayment(paymentData, option, modal) {
}
if (result.checkout_url) {
await logCommunication(`Redirecting to Mollie payment: ${result.payment_id}`, 'sent');
await logCommunication(`Redirecting to ${paymentData.payment_provider} payment: ${result.payment_id}`, 'sent');
if (typeof DEBUG !== 'undefined' && DEBUG) {
console.log("DEBUG: Redirecting to Mollie checkout...");
console.log(`DEBUG: Redirecting to ${paymentData.payment_provider} checkout...`);
}
// Close modal before redirect
document.body.removeChild(modal);
// Redirect to Mollie checkout page
// Redirect to payment checkout page
window.location.href = result.checkout_url;
} else {
throw new Error(result.error || "No checkout URL received");
@@ -1175,7 +1694,7 @@ async function downloadAndInstallSoftware(option, customerData = null) {
window.upgraded_version = option.version || "";
// DEBUG MODE: Don't auto-trigger upload, let user manually test
if (typeof DEBUG !== 'undefined' && DEBUG) {
if (typeof DEBUG !== 'undefined' && DEBUG && typeof DEBUG_ID !== 'undefined' && DEBUG_ID) {
// Show upload section and button for manual testing
document.getElementById("uploadSection").style.display = "block";
const uploadBtn = document.getElementById("uploadSoftware");
@@ -1193,14 +1712,50 @@ async function downloadAndInstallSoftware(option, customerData = null) {
console.log("Click the 'Install Software' button to test if upload.js can handle the file");
alert("DEBUG MODE: Download complete!\n\nBlob size: " + blob.size + " bytes\n\nClick the 'Install Software' button to test upload.js");
} else {
// PRODUCTION MODE: Show upload button and automatically trigger
// PRODUCTION MODE: Hide button and show installation in progress
document.getElementById("uploadSection").style.display = "block";
const uploadBtn = document.getElementById("uploadSoftware");
uploadBtn.style.display = "block";
uploadBtn.style.display = "none";
// Hide device version information during installation
const softwareOptions = document.getElementById("softwareOptions");
if (softwareOptions) {
softwareOptions.style.display = "none";
}
// Create installation status indicator
const installStatus = document.createElement("div");
installStatus.id = "installationStatus";
installStatus.style.cssText = `
text-align: center;
padding: 20px;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 8px;
margin: 10px 0;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
`;
installStatus.innerHTML = `
<i class="fa-solid fa-spinner fa-spin" style="font-size: 32px; color: #04AA6D; margin-bottom: 10px;"></i>
<p style="margin: 0; font-size: 18px; font-weight: 600; color: #333;">Installing Software...</p>
<p style="margin: 5px 0 0 0; color: #666; font-size: 14px;">Please keep your device connected and do not close this page</p>
`;
// Insert status before the hidden upload section
document.getElementById("uploadSection").parentNode.insertBefore(installStatus, document.getElementById("uploadSection"));
progressBar("60", "Starting automatic installation...", "#04AA6D");
// Enable the upload button and automatically click it
setTimeout(() => {
uploadBtn.disabled = false;
progressBar("60", "Ready to install, starting upload...", "#04AA6D");
// Start monitoring for completion
if (typeof startUploadMonitoring === 'function') {
startUploadMonitoring();
}
uploadBtn.click();
}, 1000);
}
} catch (error) {

View File

@@ -1,7 +1,7 @@
<?php
defined(page_security_key) or exit;
if (debug && debug_id == $_SESSION['id']){
if (debug && debug_id == $_SESSION['authorization']['id']){
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
@@ -12,11 +12,11 @@ include_once './settings/settings_redirector.php';
$page = 'buildtool';
//Check if allowed
if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}
$bearertoken = createCommunicationToken($_SESSION['userkey']);
$bearertoken = createCommunicationToken($_SESSION['authorization']['userkey']);
//GET PRODUCTS FROM DB
$pdo = dbConnect($dbname);

View File

@@ -1,7 +1,7 @@
<?php
defined(page_security_key) or exit;
if (debug && debug_id == $_SESSION['id']){
if (debug && debug_id == $_SESSION['authorization']['id']){
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
@@ -13,14 +13,14 @@ include_once './settings/systemcartest.php';
$page = 'cartest';
//Check if allowed
if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}
//PAGE Security
$update_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'C');
$update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
//CALL TO API FOR cartest
$api_url = '/v1/cartests/rowID='.$_GET['rowID'];
@@ -129,6 +129,9 @@ $view .='<div class="content-block order-details">
</div>';
$view .= '</div>';
//Retrieve questions and awnsers
if ($version == 0){
$view .= '<div class="content-block">
<div class="block-header">
<i class="fa-solid fa-bars fa-sm"></i>'.$cartest_questions_text.'
@@ -136,34 +139,32 @@ $view .= '<div class="content-block">
<div class="table order-table">
<table>';
//Retrieve questions and awnsers
if ($version == 0){
foreach ($cartest_questions as $key => $value){
$view .= '<tr>
<td style="width:25%;">'.$key.'</td>
<td>'.((!empty($value)|| $value !='')?$value:$not_specified).'</td>
</tr>';
}
$view .= '
</table>
</div>
</div>
';
} else {
//CREATE OUTPUT BASED ON ARRAY
$view .= '<div class="tabs">';
foreach($arrayQuestions_cartest as $group){
if ($group['Group_sequence'] == 1){
$view .= '<a href="#" class="active">'.$group['Group'].'</a>';
} else {
$view .= '<a href="#">'.$group['Group'].'</a>';
}
}
$view .= '</div>';
foreach($arrayQuestions_cartest as $group){
if ($group['Group_sequence'] == 1){
$view .= '<div class="tabs">
<a href="#" class="active">'.$group['Group'].'</a>
</div>';
$view .= '<div class="content-block tab-content active">
<div class="form responsive-width-100">';
} else {
$view .= '<div class="tabs">
<a href="#">'.$group['Group'].'</a>
</div>';
$view .= '<div class="content-block tab-content">
<div class="form responsive-width-100">';
}
@@ -198,13 +199,7 @@ $view .= '<div class="content-block">
$view .= ' </div>
</div>';
}
}
$view .= '
</table>
</div>
</div>
';
if (isset($cartest_datapoints) && $cartest_datapoints !=''){
$view .= '<div class="content-block">

View File

@@ -1,7 +1,7 @@
<?php
defined(page_security_key) or exit;
if (debug && debug_id == $_SESSION['id']){
if (debug && debug_id == $_SESSION['authorization']['id']){
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
@@ -13,16 +13,16 @@ include_once './settings/systemcartest.php';
$page = 'cartest_manage';
//Check if allowed
if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}
//PAGE Security
$update_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'C');
$update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
$bearertoken = createCommunicationToken($_SESSION['userkey']);
$bearertoken = createCommunicationToken($_SESSION['authorization']['userkey']);
$rowID = $_GET['rowID'] ?? '';
@@ -40,7 +40,7 @@ $cartest = [
'header' => [
'CarVIN' => '',
'year' => '',
'NameTester'=> $_SESSION['username'],
'NameTester'=> $_SESSION['authorization']['clientID'],
'SN' =>'',
'HW' =>'',
'FW' =>''
@@ -181,7 +181,7 @@ if ($delete_allowed === 1){
$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 cartest?\')">';
}
if ($update_allowed === 1){
$view .= '<input type="submit" name="submit" value="💾+" class="btn">';
$view .= '<input type="submit" name="submit" value="💾" class="btn">';
}
$view .= '</div>';
@@ -242,17 +242,6 @@ $view .= '</div>';
//BUILD TO INPUT FORM BASED ON ARRAY
// ------------------------------
$view .= '<div class="tabs">';
foreach($arrayQuestions_cartest as $group){
if ($group['Group_sequence'] == 1){
$view .= '<a href="#" class="active">'.$group['Group'].'</a>';
} else {
$view .= '<a href="#">'.$group['Group'].'</a>';
}
}
$view .= '</div>';
foreach($arrayQuestions_cartest as $group){
if ($group['Group_sequence'] == 1){

View File

@@ -1,7 +1,7 @@
<?php
defined(page_security_key) or exit;
if (debug && debug_id == $_SESSION['id']){
if (debug && debug_id == $_SESSION['authorization']['id']){
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
@@ -11,14 +11,14 @@ include_once './settings/settings_redirector.php';
$page = 'cartests';
//Check if allowed
if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}
//PAGE Security
$update_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'C');
$update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
//GET PARAMETERS
@@ -112,7 +112,7 @@ $view .= '
<a class="btn alt" href="index.php?page=cartests">'.$general_filters_clear.'</a>';
//SHOW DOWNLOAD TO EXCELL OPTION ONLY TO ADMIN USERS
if ($_SESSION['permission'] == 3 || $_SESSION['permission'] == 4){
if (isAllowed('cartests',$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D') === 1){
$view .= '<a class="btn alt" href="index.php?page=cartests&download=">
<i class="fa-solid fa-download"></i> Download
</a>';

View File

@@ -1,7 +1,7 @@
<?php
defined(page_security_key) or exit;
if (debug && debug_id == $_SESSION['id']){
if (debug && debug_id == $_SESSION['authorization']['id']){
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
@@ -15,7 +15,7 @@ $prev_page = $_SESSION['prev_origin'] ?? '';
$page = $_SESSION['origin'] = 'catalog';
//Check if allowed
if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}

View File

@@ -1,7 +1,7 @@
<?php
defined(page_security_key) or exit;
if (debug && debug_id == $_SESSION['id']){
if (debug && debug_id == $_SESSION['authorization']['id']){
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
@@ -15,7 +15,7 @@ $prev_page = $_SESSION['prev_origin'] ?? '';
$page = $_SESSION['origin'] = 'categories';
//Check if allowed
if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}

View File

@@ -3,14 +3,14 @@ defined(page_security_key) or exit;
$page = 'category';
//Check if allowed
if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}
//PAGE Security
$update_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'C');
$update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
$category = [
'rowID' => '',
@@ -104,7 +104,7 @@ if ($delete_allowed === 1){
$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 category?\')">';
}
if ($update_allowed === 1){
$view .= '<input type="submit" name="submit" value="💾+" class="btn">';
$view .= '<input type="submit" name="submit" value="💾" class="btn">';
}
$view .= '</div>';

View File

@@ -3,14 +3,14 @@ defined(page_security_key) or exit;
$page = 'communication';
//Check if allowed
if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}
//PAGE Security
$update_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'C');
$update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
// Default input communication values
$communication = [
@@ -106,7 +106,7 @@ if ($delete_allowed === 1){
$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 communication?\')">';
}
if ($update_allowed === 1){
$view .= '<input type="submit" name="submit" value="💾+" class="btn">';
$view .= '<input type="submit" name="submit" value="💾" class="btn">';
}
$view .= '</div>';

View File

@@ -3,14 +3,14 @@ defined(page_security_key) or exit;
$page = 'communication_send';
//Check if allowed
if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}
//PAGE Security
$update_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'C');
$update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
$url = 'index.php?page=communications';

View File

@@ -1,7 +1,7 @@
<?php
defined(page_security_key) or exit;
if (debug && debug_id == $_SESSION['id']){
if (debug && debug_id == $_SESSION['authorization']['id']){
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
@@ -19,15 +19,15 @@ $back_btn_orgin = ($prev_page != '')? '<a href="'.$prev_page.'" class="btn alt
//Check if allowed
if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}
//PAGE Security
$page_manage = 'communication';
$update_allowed = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'U');
$delete_allowed = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'D');
$create_allowed = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'C');
$update_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
//GET PARAMETERS
$pagination_page = isset($_GET['p']) ? $_GET['p'] : 1;
@@ -83,7 +83,7 @@ if ($create_allowed === 1){
$view .= '<a href="index.php?page=communication" class="btn">'.$button_create_communication.'</a>';
}
if (isAllowed('communication_send',$_SESSION['profile'],$_SESSION['permission'],'U') === 1){
if (isAllowed('communication_send',$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U') === 1){
$view .= '<a href="index.php?page=communication_send" class="btn red mar-right-2">'.$button_create_communication_send.'</a>';
}

View File

@@ -1,7 +1,7 @@
<?php
defined(page_security_key) or exit;
if (debug && debug_id == $_SESSION['id']){
if (debug && debug_id == $_SESSION['authorization']['id']){
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
@@ -17,7 +17,7 @@ $page = 'contract';
$back_btn_orgin = ($prev_page != '')? '<a href="'.$prev_page.'" class="btn alt mar-right-2">←</a>':'';
//Check if allowed
if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}
@@ -27,11 +27,11 @@ $pagination_page = $_SESSION['p'] = isset($_GET['p']) ? $_GET['p'] : 1;
//PAGE Security
$page_manage = 'contract_manage';
$update_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'U');
$update_allowed_edit = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'U');
$delete_allowed = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'D');
$create_allowed = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'C');
$view_equipment = isAllowed('equipment' ,$_SESSION['profile'],$_SESSION['permission'],'R');
$update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$update_allowed_edit = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
$view_equipment = isAllowed('equipment' ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R');
//GET Details from URL
$GET_VALUES = urlGETdetails($_GET) ?? '';
@@ -309,7 +309,7 @@ $view .= '<div class="content-block">
<tbody>';
//Check for assigned tools
foreach ($servicetools as $tools){
$view .= '<tr><td>'.(($view_equipment == 1)? '<a href="index.php?page=equipment&serialnumber='.$tools.'" class="btn2">'.$tools.'</a>':$tools).'</td><tr>';
$view .= '<tr><td>'.(($view_equipment == 1)? '<a href="index.php?page=equipment&serialnumber='.$tools.'" class="link-with-icon">'.$tools.' <i class="fa-solid fa-up-right-from-square"></i></a>':$tools).'</td><tr>';
}
$view .= '
</tbody>

View File

@@ -3,14 +3,14 @@ defined(page_security_key) or exit;
$page = 'contract_manage';
//Check if allowed
if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}
//PAGE Security
$update_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'C');
$update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
// Default input product values
$contract = [
@@ -31,7 +31,7 @@ $contract = [
'reference' => '',
'servicetool' => [],
'assigned_users' => [],
'accounthierarchy' => $_SESSION['partnerhierarchy'],
'accounthierarchy' => $_SESSION['authorization']['partnerhierarchy'],
'ignore_list' => []
];
@@ -121,7 +121,7 @@ if ($delete_allowed === 1){
$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 contract?\')">';
}
if ($update_allowed === 1){
$view .= '<input type="submit" name="submit" value="💾+" class="btn">';
$view .= '<input type="submit" name="submit" value="💾" class="btn">';
}
$view .= '</div>';
@@ -248,10 +248,10 @@ $view .=' </div>
$partner_data = json_decode($contract['accounthierarchy']);
//BUID UP DROPDOWNS
$salesid_dropdown = listPartner('salesid',$_SESSION['permission'],$partner_data->salesid,'');
$soldto_dropdown = listPartner('soldto',$_SESSION['permission'],$partner_data->soldto,'');
$shipto_dropdown = listPartner('shipto',$_SESSION['permission'],$partner_data->shipto,'');
$location_dropdown = listPartner('location',$_SESSION['permission'],$partner_data->location,'');
$salesid_dropdown = listPartner('salesid',$_SESSION['authorization']['permission'],$partner_data->salesid,'');
$soldto_dropdown = listPartner('soldto',$_SESSION['authorization']['permission'],$partner_data->soldto,'');
$shipto_dropdown = listPartner('shipto',$_SESSION['authorization']['permission'],$partner_data->shipto,'');
$location_dropdown = listPartner('location',$_SESSION['authorization']['permission'],$partner_data->location,'');
//DISPLAY
$view .= '<div class="tabs">

View File

@@ -1,7 +1,7 @@
<?php
defined(page_security_key) or exit;
if (debug && debug_id == $_SESSION['id']){
if (debug && debug_id == $_SESSION['authorization']['id']){
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
@@ -18,15 +18,15 @@ $prev_page = ($_SESSION['origin'] == 'equipments') ? $_SESSION['prev_origin_equi
$back_btn_orgin = ($prev_page != '')? '<a href="'.$prev_page.'" class="btn alt mar-right-2">←</a>':'';
//Check if allowed
if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}
//PAGE Security
$page_manage = 'contract_manage';
$update_allowed = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'U');
$delete_allowed = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'D');
$create_allowed = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'C');
$update_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
//Close Contracts when end_date expired
closeContract();

View File

@@ -5,7 +5,7 @@ include_once './assets/functions.php';
include_once './settings/settings_redirector.php';
include_once './settings/config_redirector.php';
if (debug && debug_id == $_SESSION['id']){
if (debug && debug_id == $_SESSION['authorization']['id']){
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
@@ -62,12 +62,10 @@ foreach ($communications as $communication){
$token ='';
$data = json_encode(array("username" => interface_user, "password" => interface_pw), JSON_UNESCAPED_UNICODE);
//Secure data
$payload = generate_payload($data);
//API call
$responses = ioServer('/v1/authorization', $payload);
$responses = ioServer('/v2/authorization', $data);
//Decode Payload
if (!empty($responses)){$responses = decode_payload($responses);}else{$responses = '400';}
if (!empty($responses)){$responses = json_decode($responses);}else{$responses = '400';}
if ($responses === 'NOK' || $responses === '400'){
//Not allowed

View File

@@ -19,7 +19,7 @@ $color_accent = '#2FAC66'; //'#ececec';
// Database settings
//------------------------------------------
require '/var/www/vhosts/morvalwatches.com/settings/soveliti_cloud_settings.php';
require '/var/www/vhosts/morvalwatches.com/settings/bewellwell_cloud_settings.php';
//------------------------------------------
// Menusetup & settings

View File

@@ -327,7 +327,7 @@ $page_rows_software_versions = 50; //software versions
//------------------------------------------
// Languages supported
//------------------------------------------
$supportedLanguages = ['US', 'NL', 'DE', 'ES','PT'];
$supportedLanguages = ['US', 'NL', 'DE', 'ES','PL','PT'];
//------------------------------------------
// Pricing

View File

@@ -0,0 +1,102 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// Content Reset Email
//------------------------------------------
$newuser_subject = 'SoVeLiTi account created';
$newuser_header = 'Dear user';
$newuser_text = 'Your administrator has provided access to SoVeLiTi.';
$newuser_credential_text_1 = 'Your account has been created with username ';
$newuser_credential_text_2 = 'Please click the button below to complete your registration.';
$newuser_closure = 'For security reasons this link is only active for 10 minutes.';
//------------------------------------------
// Content Reset Email
//------------------------------------------
$subject = $newuser_subject;
$message = '
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>' . $subject . '</title>
<style>
@media screen and (max-width: 600px) {
.content {
width: 100% !important;
display: block !important;
padding: 10px !important;
}
.header, .body, .footer {
padding: 20px !important;
}
}
</style>
</head>
<body style="font-family: Arial, sans-serif">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="padding: 20px;">
<table class="content" width="600" border="0" cellspacing="0" cellpadding="0" style="border-collapse: collapse; border: 1px solid #cccccc;">
<!-- Header -->
<tr>
<td class="header" style="background-color:'.color.'; padding: 40px; text-align: center; color: white; font-size: 24px;">
'.site_name.'
</td>
</tr>
<!-- Body -->
<tr>
<td class="body" style="padding: 40px; text-align: left; font-size: 16px; line-height: 1.6;">
' . $newuser_header . ',
<br>
<br>
'.$newuser_text.' '.$newuser_credential_text_1.'<b>'.$post_content['username'].'</b>
<br>
<br>
'.$newuser_credential_text_2.'
</td>
</tr>
<!-- Call to action Button -->
<tr>
<td style="padding: 0px 40px 0px 40px; text-align: center;">
<!-- CTA Button -->
<table cellspacing="0" cellpadding="0" style="margin: auto;">
<tr>
<td align="center" style="background-color: '.color_accent.'; padding: 10px 20px; border-radius: 5px;">
<a href="https://' . $portalURL . '/reset.php?resetkey='.$resetkey.'" target="_blank" style="color: #ffffff; text-decoration: none; font-weight: bold;">Reset Password</a>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td class="body" style="padding: 40px; text-align: left; font-size: 16px; line-height: 1.6;">
' . $newuser_closure . '
<br>
<br>
Kind regards,
<br>
<br>
Service team
<br>
<br>
</td>
</tr>
<!-- Footer -->
<tr>
<td class="footer" style="background: url(\'https://'.$portalURL.emaillogo.'\');background-position: center center;background-repeat:no-repeat;background-size:contain;background-color: '.color.'; padding: 40px;">
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
';

View File

@@ -0,0 +1,99 @@
<?php
defined($security_key) or exit;
//------------------------------------------
// Content Reset Email
//------------------------------------------
$changeuser_subject = 'SoVeLiTi - password reset requested';
$changeuser_header = 'Dear user';
$changeuser_text = 'A password reset has been requested for your account.';
$changeuser_credential_text_1 = 'Please click the button below to reset the password of your SoVeLiTi account.';
$changeuser_closure = 'For security reasons this link is only active for 10 minutes.';
//------------------------------------------
// Content Reset Email
//------------------------------------------
$subject = $changeuser_subject;
$message = '
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>' . $subject . '</title>
<style>
@media screen and (max-width: 600px) {
.content {
width: 100% !important;
display: block !important;
padding: 10px !important;
}
.header, .body, .footer {
padding: 20px !important;
}
}
</style>
</head>
<body style="font-family: Arial, sans-serif">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="padding: 20px;">
<table class="content" width="600" border="0" cellspacing="0" cellpadding="0" style="border-collapse: collapse; border: 1px solid #cccccc;">
<!-- Header -->
<tr>
<td class="header" style="background-color:'.color.'; padding: 40px; text-align: center; color: white; font-size: 24px;">
'.site_title.'
</td>
</tr>
<!-- Body -->
<tr>
<td class="body" style="padding: 40px; text-align: left; font-size: 16px; line-height: 1.6;">
' . $changeuser_header . ',
<br>
<br>
'.$changeuser_text.'
<br>
<br>
'.$changeuser_credential_text_1 .'
</td>
</tr>
<!-- Call to action Button -->
<tr>
<td style="padding: 0px 40px 0px 40px; text-align: center;">
<!-- CTA Button -->
<table cellspacing="0" cellpadding="0" style="margin: auto;">
<tr>
<td align="center" style="background-color: '.color_accent.'; padding: 10px 20px; border-radius: 5px;">
<a href="https://' . $portalURL . '/reset.php?resetkey='.$resetkey.'" target="_blank" style="color: #ffffff; text-decoration: none; font-weight: bold;">Reset Password</a>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td class="body" style="padding: 40px; text-align: left; font-size: 16px; line-height: 1.6;">
' . $changeuser_closure . '
<br>
<br>
Kind regards,
<br>
<br>
Service team
<br>
<br>
</td>
</tr>
<!-- Footer -->
<tr>
<td class="footer" style="background: url(\'https://'.$portalURL.emaillogo.'\');background-position: center center;background-repeat:no-repeat;background-size:contain;background-color: '.color.'; padding: 40px;">
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
';

View File

@@ -0,0 +1,55 @@
<?php
require 'settingsprofiles.php';
// This will change the title on the website
define('site_name','');
define('site_name_footer','SoVeLiTi Cloud');
// This will change the title on browser TAB
define('site_title','SoVeLiTi');
//Scriptversion
define('script_version','v1');
//Enable VeLiTi-issue mgt
define('veliti_cim',true);
//Enable VeLiTi-analytics
define('veliti_analytics',false);
//Rewrite rule
define('rewrite_url',true);
define('news','');
//maintenance_mode
define('maintenance_mode_communication',false);
define('maintenance_mode_notification','Notice: Portal not accessible due to maintenance on May 15th between 12.00 - 13.00 CET.');
define('maintenance_mode',false);
define('maintenance_mode_text','System in maintenance');
define('debug',true);
define('debug_id','1');
/*Security*/
// Page security
define('page_security_key','secure_admin_342642');
define('cronjob_number','25');
define('header_security',false);
/* Email */
// The from email that will appear on the customer's order details email
define('mail_from','SoVeLiTi');
// Your email
define('email','CustomerPortal@veliti.nl');
//Additional phpmailer-settings
define('email_host_name','veliti.nl');
define('email_reply_to','info@gewoonlekkerspaans.nl');
define('email_outgoing_pw','306yc%X5f');
define('email_outgoing_port','587');
define('email_outgoing_security','ssl');
/*Appearance*/
//Icon
define('icon_image','/custom/soveliti/style/VeLiTi-Logo2.png');
define('color','#527ee5');
define('color_accent','#527ee5');
define('emaillogo','/custom/soveliti/style/SoVeLiTi.png');
/*Default Users*/
define('software_update_user','EMP-updater');
define('software_update_pw','EMP-updater');
define('interface_user','interface@test.nl');
define('interface_pw','test1234');

View File

@@ -0,0 +1,112 @@
<?php
//------------------------------------------
//EXCEPTION LIST
//------------------------------------------
$serialnumber_exceptions = array("22050695","22110095");
//------------------------------------------
// Security
//------------------------------------------
$security_key = 'secure_34563$52';
//------------------------------------------
// Base color
//------------------------------------------
$color = '#005655';//'#0b1054';
$color_accent = '#2FAC66'; //'#ececec';
//------------------------------------------
// Database settings
//------------------------------------------
require '/var/www/vhosts/veliti.nl/settings/morvalwatches_cloud_settings.php';
//------------------------------------------
// Menusetup & settings
//------------------------------------------
require 'settingsmenu.php';
//------------------------------------------
// API BaseUrl
//------------------------------------------
$baseurl = 'https://'.$_SERVER['SERVER_NAME'].'/api.php'; //URL of API
$portalURL = $_SERVER['SERVER_NAME'];
//------------------------------------------
// Equipmentdetails
//------------------------------------------
$servicedate = date("Y-m-d", strtotime("-365 days"));
$warrantydate = date("Y-m-d", strtotime("-365 days"));
$warranty_extended = date("Y-m-d", strtotime("+365 days"));
$date = date('Y-m-d H:i:s');
$curYear = date("Y", time());
$curMonth = date("m", time());
$curQuarter = (int)ceil($curMonth / 3);
$curdateObj = DateTime::createFromFormat('!m', $curMonth);
$curMonth_name = $curdateObj->format('F');
//------------------------------------------
//History Type
//------------------------------------------
$type1 = 'General';
$type2 = 'Customer';
$type3 = 'Service';
$type4 = 'Testing';
$type5 = 'Data';
$type6 = 'Other';
$type7 = 'Internal';
$type8 = 'Ignore';
$type9 = 'Warranty';
$type10 = 'Contract';
$type11 = 'Warranty-Expired';
$type12 = 'Contract-Expired';
$type13 = "Order";
$type14 = "ServiceReport";
$type15 = "SRIncluded";
$type16 = "Notes";
$type17 = "Visual";
$HistoryType_1 = 'Bootloader';
$HistoryType_2 = 'Firmware';
$HistoryType_3 = 'SerialNumber';
$HistoryType_4 = 'Visual_Test';
$HistoryType_5 = 'Maintenance_Test';
$HistoryType_6 = 'Assembly_Test';
$HistoryType_7 = 'ProductNumber';
$HistoryType_8 = 'Visual';
$HistoryType_9 = 'ServiceReport';
//------------------------------------------
//Permissions CRUD
//------------------------------------------
$permission_4 = 'CRUD'; //Admin+
$permission_3 = 'CRUD'; //Admin
$permission_2 = 'CRU'; //SuperUser
$permission_1 = 'CRU'; //CreateUpdate
$permission_0 = 'R'; //Readonly
$permissionlabel1 = 'Permission';
$permission1 = 'Superuser'; #1
$permission2 = 'Create & Update'; #2
$permission3 = 'read-only'; // #3
$permission4 = 'Admin'; //#4
$permission5 = 'Admin+'; // #5
$settingslabel1 = 'profile';
$setting1 = 'firmware'; //Fix
$setting2 = 'service';
$setting3 = 'build'; //Fix
$setting4 = 'distribution';
$setting5 = '';
$setting6 = '';
$setting7 = ''; //Fix
$setting8 = 'interface';
//------------------------------------------
//Partners
//------------------------------------------
$partnertype1 = 'SalesID';
$partnertype2 = 'SoldTo';
$partnertype3 = 'ShipTo';
$partnertype4 = 'Location';
$partnertype5 = 'Section';

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