diff --git a/.gitignore b/.gitignore index 3d559fe..d82c57b 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/PAYMENT_IMPLEMENTATION_SUMMARY.md b/PAYMENT_IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 579343f..0000000 --- a/PAYMENT_IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -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 diff --git a/PAYMENT_INTEGRATION_PLAN.md b/PAYMENT_INTEGRATION_PLAN.md deleted file mode 100644 index 8b672e9..0000000 --- a/PAYMENT_INTEGRATION_PLAN.md +++ /dev/null @@ -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 - 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 - 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) - -``` - -**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 - diff --git a/access_element.php b/access_element.php new file mode 100644 index 0000000..e02ee99 --- /dev/null +++ b/access_element.php @@ -0,0 +1,188 @@ +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 = ' +
+

'.($view_access_h2 ?? 'Access Element').' - '.$responses->access_name.'

+ +'; + +if ($update_allowed_edit === 1){ + $view .= '✏️'; +} + +$view .= '
'; + +if (isset($success_msg)){ + $view .= '
+ +

'.$success_msg.'

+ +
'; +} + +$view .= '
'; + +// Access Element Information Block +$view .= '
+
+ '.($view_access_information ?? 'Access Element Information').' +
+
+

'.($general_status ?? 'Status').'

+

'.$status_text.'

+
+
+

'.($access_element_name ?? 'Name').'

+

'.$responses->access_name.'

+
+
+

'.($access_element_path ?? 'Path').'

+

'.$responses->access_path.'

+
+
+

'.($access_element_group ?? 'Group').'

+

'.($responses->access_group ?? '-').'

+
+
+

'.($role_description ?? 'Description').'

+

'.($responses->description ?? '-').'

+
+
+'; + +$view .= '
'; // Close content-block-wrapper + +// Roles Using This Access Element +$view .= '
+
+ '.($view_access_roles ?? 'Roles Using This Element').' +
+
+ + + + + + + + + + + '; + +if (!empty($role_permissions)){ + foreach ($role_permissions as $role_perm){ + $can_create = ($role_perm->can_create == 1) ? '' : ''; + $can_read = ($role_perm->can_read == 1) ? '' : ''; + $can_update = ($role_perm->can_update == 1) ? '' : ''; + $can_delete = ($role_perm->can_delete == 1) ? '' : ''; + + $view .= ' + + + + + + '; + } +} else { + $view .= ' + + '; +} + +$view .= ' +
'.($role_name ?? 'Role Name').''.($permission_create ?? 'C').''.($permission_read ?? 'R').''.($permission_update ?? 'U').''.($permission_delete ?? 'D').'
'.$role_perm->role_name.''.$can_create.''.$can_read.''.$can_update.''.$can_delete.'
'.($no_roles_using ?? 'No roles are using this access element').'
+
+
+ '; + +// Metadata Block +$view .= '
+
+ '.($tab3 ?? 'Details').' +
+
+ + + + + + + + + +
'.($general_created ?? 'Created').''.getRelativeTime($responses->created).'
'.($general_updated ?? 'Updated').''.getRelativeTime($responses->updated).'
+
+
+'; + +//OUTPUT +echo $view; + +template_footer() + +?> diff --git a/access_element_manage.php b/access_element_manage.php new file mode 100644 index 0000000..1394bae --- /dev/null +++ b/access_element_manage.php @@ -0,0 +1,168 @@ + '', + '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 =' +
+
+

'.$label_h2.'

+ +'; + +if ($delete_allowed === 1 && $element_ID != ''){ + $view .= ''; +} +if ($update_allowed === 1 || ($create_allowed === 1 && $element_ID == '')){ + $view .= ''; +} + +$view .= '
'; + +$view .= '
+ '.($tab1 ?? 'General').' +
+
+
+ + + + + + + + + + + +
+
'; + +//DISPLAY TAB 2 - Metadata +if ($element_ID != ''){ +$view .= '
+ '.($tab3 ?? 'Details').' +
+
+
+ + + + + + + + +
+
'; +} + +$view .= '
'; + +//Output +echo $view; +template_footer()?> diff --git a/access_elements.php b/access_elements.php new file mode 100644 index 0000000..6dac667 --- /dev/null +++ b/access_elements.php @@ -0,0 +1,317 @@ +←':''; + +//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 = ' +
+
+ +
+

'.($access_elements_h2 ?? 'Access Elements').' ('.$query_total.')

+

'.($access_elements_p ?? 'Manage system access elements and paths').'

+
+
+
+ '.$back_btn_orgin; + +// Scan button - only show if user has create permission +if ($create_allowed === 1){ + $view .= ' +
+ +
'; + $view .= '+'; +} + +$view .= ' +
+
'; + +if (isset($success_msg)){ +$view .= '
+ +

'.$success_msg.'

+ +
'; +} + +$view .= ' + +'; +$view .= ' +
+
+ + + + + + + + + + + + + '; + + if (empty($responses)){ + + $view .= ' + + + '; + } + +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 .= ' + + + + + + + + '; + } + $view .= ' + +
'.($access_element_name ?? 'Name').''.($access_element_path ?? 'Path').''.($access_element_group ?? 'Group').''.($role_description ?? 'Description').''.($general_status ?? 'Status').''.($general_created ?? 'Created').'
'.($message_no_access_elements ?? 'No access elements found').'
'.$response->access_name.''.$response->access_path.''.($response->access_group ?? '-').''.($response->description ?? '-').''.$status_text.''.getRelativeTime($response->created).'
+
+
+'; + +$page_rows = $page_rows_equipment ?? 20; +$view.=''; +//OUTPUT +echo $view; + +template_footer(); +?> diff --git a/account.php b/account.php index d270157..831a34f 100644 --- a/account.php +++ b/account.php @@ -1,7 +1,7 @@ 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 = ' '.$button_partner_assigned_communication.''; } //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 = ' '.$button_partner_assigned_users.''; } diff --git a/account_manage.php b/account_manage.php index 9af1788..eb225fa 100644 --- a/account_manage.php +++ b/account_manage.php @@ -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 .= ''; } if ($update_allowed === 1){ - $view .= ''; + $view .= ''; } $view .= ''; @@ -271,8 +271,8 @@ $view .= '
//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 .= '
'.$tab3.' diff --git a/accounts.php b/accounts.php index 72e748c..00c6705 100644 --- a/accounts.php +++ b/accounts.php @@ -1,7 +1,7 @@ getMessage() . " in " . $exception->getFile() . " on line " . $exception->getLine()); + }); +} + //------------------------------------------ // Header security - enabled via config //------------------------------------------ @@ -122,7 +134,7 @@ if($is_jwt_valid && str_contains($version, 'v')) { //------------------------------------------ // Check for maintenance mode, exclude debug user //------------------------------------------ - if(maintenance_mode == false|| debug_id == $user_data['id']){ + if(maintenance_mode == false || debug_id == $user_data['id']){ //------------------------------------------ // Build up version and check if file is available @@ -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); diff --git a/api/.DS_Store b/api/.DS_Store index 25bea50..362cceb 100644 Binary files a/api/.DS_Store and b/api/.DS_Store differ diff --git a/api/v0/get/user_credentials.php b/api/v0/get/user_credentials.php index 09062c9..6168639 100644 --- a/api/v0/get/user_credentials.php +++ b/api/v0/get/user_credentials.php @@ -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']; diff --git a/api/v0/post/application.php b/api/v0/post/application.php index 6a8a550..323d616 100644 --- a/api/v0/post/application.php +++ b/api/v0/post/application.php @@ -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]); } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/api/v1/.DS_Store b/api/v1/.DS_Store index cb2f10e..ad7d9dd 100644 Binary files a/api/v1/.DS_Store and b/api/v1/.DS_Store differ diff --git a/api/v1/get/contracts.php b/api/v1/get/contracts.php index 4591407..9e04cd6 100644 --- a/api/v1/get/contracts.php +++ b/api/v1/get/contracts.php @@ -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; diff --git a/api/v1/get/history.php b/api/v1/get/history.php index c1667e0..b844c71 100644 --- a/api/v1/get/history.php +++ b/api/v1/get/history.php @@ -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 = ''; diff --git a/api/v1/get/user_credentials.php b/api/v1/get/user_credentials.php index 3f98774..f7fa705 100644 --- a/api/v1/get/user_credentials.php +++ b/api/v1/get/user_credentials.php @@ -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']; diff --git a/api/v1/post/accounts.php b/api/v1/post/accounts.php index 072c4bf..26049a7 100644 --- a/api/v1/post/accounts.php +++ b/api/v1/post/accounts.php @@ -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, diff --git a/api/v1/post/application.php b/api/v1/post/application.php index dd567bf..afd1e3d 100644 --- a/api/v1/post/application.php +++ b/api/v1/post/application.php @@ -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; @@ -85,238 +90,148 @@ switch ($action) { $communication_check = 0; //Check communication record $message_box = []; $timestamp = date("Y-m-d H:i:s"); - - // Create history description - $history_description = [ - "start_date"=>$timestamp, - "end_date"=>date("Y-m-d", strtotime("+730 days")), - "organization"=>strip_tags(trim($post_content['organization'])), - "phone"=>strip_tags(trim($post_content['phone'])), - "city"=>strip_tags(trim($post_content['city'])), - "country"=>strip_tags(trim($post_content['country'])), - "email_consent"=>strip_tags(trim($post_content['email_consent'])), - "terms_consent"=>strip_tags(trim($post_content['terms_consent'])) - ]; - - $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 - $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']]); + // Normalize input to always be an array + $serial_numbers = is_array($post_content['sn']) ? $post_content['sn'] : [$post_content['sn']]; - //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; - } - - $shipto = $partnerhierarchy['shipto'] ?? ''; - $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[] = $sn.' - '.$register_message_3; - $communication_check = 1; - } - } else { - //Serialnumber not recognized - $message_box[] = $sn.' - '.$register_message_1; - } + 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; } - } - 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){ - // -------------------------------------------- + + // 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 - // -------------------------------------------- - //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; - } + $message_box[] = $sn . ' - ' . $register_message_2; + $communication_check = 1; + continue; } - else { - //Serialnumber not recognized - $message_box[] = $post_content['sn'].' - '.$register_message_1; + + //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"=> $warranty_end_date, + "organization"=>strip_tags(trim($post_content['organization'])), + "phone"=>strip_tags(trim($post_content['phone'])), + "city"=>strip_tags(trim($post_content['city'])), + "country"=>strip_tags(trim($post_content['country'])), + "email_consent"=>strip_tags(trim($post_content['email_consent'])), + "terms_consent"=>strip_tags(trim($post_content['terms_consent'])) + ]; + + $description = json_encode($history_description, JSON_UNESCAPED_UNICODE); + + // 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'] + ]); + + // Get partner details of equipment + $partner_equipment = getrowID($dbname, 'accounthierarchy', 'equipment', 'rowID="' . $rowID['rowID'] . '"'); + $partner_equipment = json_decode($partner_equipment['accounthierarchy']); + + // Setup partner hierarchy + $partnerhierarchy = [ + "salesid" => $partner_equipment->salesid, + "soldto" => $partner_equipment->soldto + ]; + + // Setup variables for partner + $partnername = $post_content['organization']; + $salesID = json_encode($partnerhierarchy, JSON_UNESCAPED_UNICODE); + $createdby = 'system'; + + // 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 = $pdo->prepare($sql); + $stmt->execute([$partnertype, $partnername, $salesID, $createdby, '1']); + + $partner_rowid = $pdo->lastInsertId(); + return $partner_rowid . '-' . $partnername; + }; + + // Handle shipto + $partnerhierarchy['shipto'] = empty($partner_equipment->shipto) + ? $getOrCreatePartner($partnertype3) + : $partner_equipment->shipto; + + // Handle location + $partnerhierarchy['location'] = empty($partner_equipment->location) + ? $getOrCreatePartner($partnertype4) + : $partner_equipment->location; + + $partnerhierarchy_json = json_encode($partnerhierarchy, JSON_UNESCAPED_UNICODE); + + // Update equipment record + $sql = 'UPDATE equipment SET status = ?, warranty_date = ?, accounthierarchy = ?, updatedby = ? WHERE rowID = ?'; + $stmt = $pdo->prepare($sql); + $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_end_date, $warranty_user); + + // Serial number recognized + $message_box[] = $sn . ' - ' . $register_message_3; + $communication_check = 1; + } // -------------------------------------------- // Send generic account to user for software updates diff --git a/api/v1/post/contracts.php b/api/v1/post/contracts.php index 498f164..12f90e7 100644 --- a/api/v1/post/contracts.php +++ b/api/v1/post/contracts.php @@ -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']); } } diff --git a/api/v1/post/equipments.php b/api/v1/post/equipments.php index 7200512..198012c 100644 --- a/api/v1/post/equipments.php +++ b/api/v1/post/equipments.php @@ -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'], diff --git a/api/v1/post/partners.php b/api/v1/post/partners.php index 6f1be77..ea07e22 100644 --- a/api/v1/post/partners.php +++ b/api/v1/post/partners.php @@ -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, diff --git a/api/v1/post/users.php b/api/v1/post/users.php index 1d35037..96ca3b1 100644 --- a/api/v1/post/users.php +++ b/api/v1/post/users.php @@ -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, diff --git a/api/v2/.DS_Store b/api/v2/.DS_Store index c28ab40..16d8e39 100644 Binary files a/api/v2/.DS_Store and b/api/v2/.DS_Store differ diff --git a/api/v2/get/.DS_Store b/api/v2/get/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/api/v2/get/.DS_Store and /dev/null differ diff --git a/api/v2/get/access_elements.php b/api/v2/get/access_elements.php new file mode 100644 index 0000000..b505979 --- /dev/null +++ b/api/v2/get/access_elements.php @@ -0,0 +1,158 @@ +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; + +?> diff --git a/api/v2/get/contracts.php b/api/v2/get/contracts.php index 2362582..f5da5bc 100644 --- a/api/v2/get/contracts.php +++ b/api/v2/get/contracts.php @@ -17,19 +17,25 @@ 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; case '2': $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; + 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; - default: + 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; diff --git a/api/v2/get/equipment_history.php b/api/v2/get/equipment_history.php new file mode 100644 index 0000000..1fc241e --- /dev/null +++ b/api/v2/get/equipment_history.php @@ -0,0 +1,116 @@ + 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' + ]); +} + +?> \ No newline at end of file diff --git a/api/v2/get/equipments.php b/api/v2/get/equipments.php index d02c76d..ab70944 100644 --- a/api/v2/get/equipments.php +++ b/api/v2/get/equipments.php @@ -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]; diff --git a/api/v2/get/history.php b/api/v2/get/history.php index be5c826..aa81a11 100644 --- a/api/v2/get/history.php +++ b/api/v2/get/history.php @@ -13,27 +13,34 @@ 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'; + $whereclause = 'WHERE e.accounthierarchy like :condition '; break; case '2': $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') { - //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; + 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; diff --git a/api/v2/get/invoice.php b/api/v2/get/invoice.php index 8091646..07055c6 100644 --- a/api/v2/get/invoice.php +++ b/api/v2/get/invoice.php @@ -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; diff --git a/api/v2/get/marketing_files.php b/api/v2/get/marketing_files.php new file mode 100644 index 0000000..6efa96c --- /dev/null +++ b/api/v2/get/marketing_files.php @@ -0,0 +1,152 @@ +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; \ No newline at end of file diff --git a/api/v2/get/marketing_folders.php b/api/v2/get/marketing_folders.php new file mode 100644 index 0000000..fed5660 --- /dev/null +++ b/api/v2/get/marketing_folders.php @@ -0,0 +1,165 @@ +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; \ No newline at end of file diff --git a/api/v2/get/marketing_tags.php b/api/v2/get/marketing_tags.php new file mode 100644 index 0000000..fe1d003 --- /dev/null +++ b/api/v2/get/marketing_tags.php @@ -0,0 +1,112 @@ +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); \ No newline at end of file diff --git a/api/v2/get/payment.php b/api/v2/get/payment.php index 132e34d..abed788 100644 --- a/api/v2/get/payment.php +++ b/api/v2/get/payment.php @@ -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) { diff --git a/api/v2/get/products_software_upgrade_paths.php b/api/v2/get/products_software_upgrade_paths.php index 4035243..a114f74 100644 --- a/api/v2/get/products_software_upgrade_paths.php +++ b/api/v2/get/products_software_upgrade_paths.php @@ -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 = []; diff --git a/api/v2/get/products_software_versions.php b/api/v2/get/products_software_versions.php index 31553f2..fcaa325 100644 --- a/api/v2/get/products_software_versions.php +++ b/api/v2/get/products_software_versions.php @@ -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 = []; diff --git a/api/v2/get/report_builder.php b/api/v2/get/report_builder.php new file mode 100644 index 0000000..03b4fde --- /dev/null +++ b/api/v2/get/report_builder.php @@ -0,0 +1,152 @@ +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; +?> diff --git a/api/v2/get/role_access_permissions.php b/api/v2/get/role_access_permissions.php new file mode 100644 index 0000000..096fd5a --- /dev/null +++ b/api/v2/get/role_access_permissions.php @@ -0,0 +1,123 @@ +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; + +?> diff --git a/api/v2/get/service.php b/api/v2/get/service.php new file mode 100644 index 0000000..3f71c20 --- /dev/null +++ b/api/v2/get/service.php @@ -0,0 +1,82 @@ +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); +} + +?> \ No newline at end of file diff --git a/api/v2/get/software_available.php b/api/v2/get/software_available.php index 3513575..ca139d1 100644 --- a/api/v2/get/software_available.php +++ b/api/v2/get/software_available.php @@ -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,8 +294,9 @@ 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 pup.is_active = 1'; + 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); @@ -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, diff --git a/api/v2/get/software_update.php b/api/v2/get/software_update.php index 2f4696b..a7191c3 100644 --- a/api/v2/get/software_update.php +++ b/api/v2/get/software_update.php @@ -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 pup.is_active = 1'; + 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)) { diff --git a/api/v2/get/user_credentials.php b/api/v2/get/user_credentials.php index 8ee04a2..d6ccdba 100644 --- a/api/v2/get/user_credentials.php +++ b/api/v2/get/user_credentials.php @@ -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']; diff --git a/api/v2/get/user_permissions.php b/api/v2/get/user_permissions.php new file mode 100644 index 0000000..9d6f139 --- /dev/null +++ b/api/v2/get/user_permissions.php @@ -0,0 +1,70 @@ + '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'])); +} + +?> \ No newline at end of file diff --git a/api/v2/get/user_role_assignments.php b/api/v2/get/user_role_assignments.php new file mode 100644 index 0000000..f90991e --- /dev/null +++ b/api/v2/get/user_role_assignments.php @@ -0,0 +1,128 @@ +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; + +?> diff --git a/api/v2/get/user_roles.php b/api/v2/get/user_roles.php new file mode 100644 index 0000000..cb07b3f --- /dev/null +++ b/api/v2/get/user_roles.php @@ -0,0 +1,167 @@ +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; + +?> diff --git a/api/v2/post/.DS_Store b/api/v2/post/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/api/v2/post/.DS_Store and /dev/null differ diff --git a/api/v2/post/access_elements.php b/api/v2/post/access_elements.php new file mode 100644 index 0000000..e2d768d --- /dev/null +++ b/api/v2/post/access_elements.php @@ -0,0 +1,79 @@ + $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]); +} + +?> diff --git a/api/v2/post/accounts.php b/api/v2/post/accounts.php index 6e7af66..eddcce5 100644 --- a/api/v2/post/accounts.php +++ b/api/v2/post/accounts.php @@ -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, diff --git a/api/v2/post/contracts.php b/api/v2/post/contracts.php index 9ecb7bb..74621e4 100644 --- a/api/v2/post/contracts.php +++ b/api/v2/post/contracts.php @@ -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']); } } diff --git a/api/v2/post/equipments.php b/api/v2/post/equipments.php index f4d14cd..9057ddb 100644 --- a/api/v2/post/equipments.php +++ b/api/v2/post/equipments.php @@ -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 diff --git a/api/v2/post/history.php b/api/v2/post/history.php index 75eead9..51ed350 100644 --- a/api/v2/post/history.php +++ b/api/v2/post/history.php @@ -1,5 +1,6 @@ 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,12 +150,21 @@ 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; $servicetoolHistoryUpdate = 1; $sn_service = $post_content['sn_service']; break; + + case 'customer': //update from Portal + $historytype = 'Customer'; + break; default: $historytype = 'Other'; @@ -155,14 +173,14 @@ if (isset($post_content['sn']) && isset($post_content['payload'])){ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++ //Connect to DB // +++++++++++++++++++++++++++++++++++++++++++++++++++++++ - + //Get whereclause based on serialnumber $whereclause = checkSerial($serial); // +++++++++++++++++++++++++++++++++++++++++++++++++++++++ //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 { diff --git a/api/v2/post/marketing_delete.php b/api/v2/post/marketing_delete.php new file mode 100644 index 0000000..ed9036e --- /dev/null +++ b/api/v2/post/marketing_delete.php @@ -0,0 +1,93 @@ +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']); +} + +?> \ No newline at end of file diff --git a/api/v2/post/marketing_folders.php b/api/v2/post/marketing_folders.php new file mode 100644 index 0000000..ab29f94 --- /dev/null +++ b/api/v2/post/marketing_folders.php @@ -0,0 +1,105 @@ +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']); +} + +?> \ No newline at end of file diff --git a/api/v2/post/marketing_update.php b/api/v2/post/marketing_update.php new file mode 100644 index 0000000..8ede166 --- /dev/null +++ b/api/v2/post/marketing_update.php @@ -0,0 +1,94 @@ +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']); +} +?> \ No newline at end of file diff --git a/api/v2/post/marketing_upload.php b/api/v2/post/marketing_upload.php new file mode 100644 index 0000000..5055f52 --- /dev/null +++ b/api/v2/post/marketing_upload.php @@ -0,0 +1,336 @@ +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); +} + +?> \ No newline at end of file diff --git a/api/v2/post/payment.php b/api/v2/post/payment.php index 84b4c54..3f7858b 100644 --- a/api/v2/post/payment.php +++ b/api/v2/post/payment.php @@ -1,12 +1,9 @@ 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,67 +160,159 @@ 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 with Mollie - $payment = $mollie->payments->create([ - 'amount' => [ - 'currency' => $final_currency ?: 'EUR', - 'value' => "{$formatted_price}" - ], - 'description' => "Software upgrade Order #{$txn_id}", - 'redirectUrl' => "{$redirect_url}", - 'webhookUrl' => "{$webhook_url}", - 'metadata' => [ - 'order_id' => $txn_id, - 'serial_number' => $serial_number, - 'version_id' => $version_id, - 'equipment_id' => $equipment_id - ] - ]); + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + // 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(); - $mollie_payment_id = $payment->id; - $checkout_url = $payment->getCheckoutUrl(); + // 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' => [ + 'currency' => $final_currency ?: 'EUR', + 'value' => "{$formatted_price}" + ], + 'description' => "Software upgrade Order #{$txn_id}", + 'redirectUrl' => "{$redirect_url}", + 'webhookUrl' => "{$webhook_url}", + 'metadata' => [ + 'order_id' => $txn_id, + 'serial_number' => $serial_number, + 'version_id' => $version_id, + 'equipment_id' => $equipment_id + ] + ]); + + $payment_id = $payment->id; + $checkout_url = $payment->getCheckoutUrl(); + + if (debug) { + debuglog("DEBUG: Mollie payment created successfully"); + debuglog("DEBUG: Payment ID: $payment_id"); + debuglog("DEBUG: Redirect URL sent: $redirect_url"); + debuglog("DEBUG: Checkout URL: $checkout_url"); + } + + $payment_method_id = 1; // Mollie + $payment_metadata = 'mollie_payment_id'; + } if (debug) { - debuglog("DEBUG: Mollie payment created successfully"); - debuglog("DEBUG: Payment ID: $mollie_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: 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'] ?? ''; +} + ?> diff --git a/api/v2/post/placeorder.php b/api/v2/post/placeorder.php index b6e7b37..306af85 100644 --- a/api/v2/post/placeorder.php +++ b/api/v2/post/placeorder.php @@ -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(); diff --git a/api/v2/post/products_software_licenses.php b/api/v2/post/products_software_licenses.php index 1b3a6fe..1282504 100644 --- a/api/v2/post/products_software_licenses.php +++ b/api/v2/post/products_software_licenses.php @@ -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); diff --git a/api/v2/post/products_software_upgrade_paths.php b/api/v2/post/products_software_upgrade_paths.php index 6b6f9a4..6ce616f 100644 --- a/api/v2/post/products_software_upgrade_paths.php +++ b/api/v2/post/products_software_upgrade_paths.php @@ -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 diff --git a/api/v2/post/report_builder.php b/api/v2/post/report_builder.php new file mode 100644 index 0000000..9d6516e --- /dev/null +++ b/api/v2/post/report_builder.php @@ -0,0 +1,124 @@ + 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; +?> diff --git a/api/v2/post/role_access_permissions.php b/api/v2/post/role_access_permissions.php new file mode 100644 index 0000000..c064dc2 --- /dev/null +++ b/api/v2/post/role_access_permissions.php @@ -0,0 +1,75 @@ + $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]); +} + +?> diff --git a/api/v2/post/user_role_assignments.php b/api/v2/post/user_role_assignments.php new file mode 100644 index 0000000..cd663d9 --- /dev/null +++ b/api/v2/post/user_role_assignments.php @@ -0,0 +1,141 @@ +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]); + } +} + +?> diff --git a/api/v2/post/user_roles.php b/api/v2/post/user_roles.php new file mode 100644 index 0000000..da38722 --- /dev/null +++ b/api/v2/post/user_roles.php @@ -0,0 +1,123 @@ + $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]); +} + +?> diff --git a/api/v2/post/users.php b/api/v2/post/users.php index fba231a..175e8f3 100644 --- a/api/v2/post/users.php +++ b/api/v2/post/users.php @@ -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; diff --git a/assets/admin.js b/assets/admin.js index b3869a2..31e7cde 100644 --- a/assets/admin.js +++ b/assets/admin.js @@ -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(); @@ -1158,14 +1218,22 @@ function sortTextVal(a, b) { // Print DIV //------------------------------------------ function printDiv(div) { -var divContents = document.getElementById(div).innerHTML; -var a = window.open('', '', ''); -a.document.write(''); -a.document.write(' '); -a.document.write(divContents); -a.document.write(''); -a.document.close(); -a.print(); + var divContents = document.getElementById(div).innerHTML; + var printWindow = window.open('', '', 'height=600,width=800'); + printWindow.document.write('Print'); + printWindow.document.write(''); + printWindow.document.write(''); + printWindow.document.write(divContents); + printWindow.document.write(''); + printWindow.document.close(); + printWindow.focus(); + printWindow.print(); + printWindow.close(); } //------------------------------------------ diff --git a/assets/database/marketing_install.sql b/assets/database/marketing_install.sql new file mode 100644 index 0000000..7fb34e3 --- /dev/null +++ b/assets/database/marketing_install.sql @@ -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 \ No newline at end of file diff --git a/assets/database/migration_profiles_to_rbac.sql b/assets/database/migration_profiles_to_rbac.sql new file mode 100644 index 0000000..9c70b64 --- /dev/null +++ b/assets/database/migration_profiles_to_rbac.sql @@ -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; diff --git a/assets/database/migration_users_to_rbac.sql b/assets/database/migration_users_to_rbac.sql new file mode 100644 index 0000000..3fc2400 --- /dev/null +++ b/assets/database/migration_users_to_rbac.sql @@ -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; diff --git a/assets/database/user_rbac b/assets/database/user_rbac new file mode 100644 index 0000000..12f9eb5 --- /dev/null +++ b/assets/database/user_rbac @@ -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; \ No newline at end of file diff --git a/assets/functions.php b/assets/functions.php index 6e50226..27d009e 100644 --- a/assets/functions.php +++ b/assets/functions.php @@ -83,7 +83,9 @@ function send_mail($to, $subject, $message, $attachment, $attachment_name){ if( !$mail->send() ){ // render error if it is $tab = array('error' => 'Mailer Error: '.$mail->ErrorInfo ); - debuglog(json_encode($tab)); + if(debug){ + debuglog(json_encode($tab)); + } exit; } else{ @@ -157,7 +159,9 @@ function sendIcsCalendar($appointment, $to, $subject = 'Appointment Confirmation if (!$mail->send()) { $tab = array('error' => 'Mailer Error: ' . $mail->ErrorInfo); - debuglog(json_encode($tab)); + if(debug){ + debuglog(json_encode($tab)); + } return false; } else { return true; @@ -229,20 +233,19 @@ function routes($urls) { //------------------------------------------ // Menu Builder //------------------------------------------ +/** + * @deprecated Use filterMenuByPermissions() instead + * Filter menu items based on profile string (legacy) + */ function filterMenuByProfile($menu, $profileString) { - // Convert profile string to array $profileArray = explode(',', $profileString); - - // Initialize result array $filteredMenu = []; - - // Loop through main menu sections + foreach ($menu as $sectionKey => $section) { $sectionIncluded = in_array($sectionKey, $profileArray); $submenuFound = false; $firstSubmenuItem = null; - - // First check if any submenu items are in profile + foreach ($section as $itemKey => $item) { if ($itemKey !== 'main_menu' && in_array($itemKey, $profileArray)) { $submenuFound = true; @@ -251,24 +254,19 @@ function filterMenuByProfile($menu, $profileString) { } } } - - // Include this section if either section key or any submenu is in profile + if ($sectionIncluded || $submenuFound) { $filteredMenu[$sectionKey] = []; - - // Add main_menu - if section not in profile but submenu found, use first submenu as main_menu + if (!$sectionIncluded && $submenuFound && $firstSubmenuItem !== null) { - // Create hybrid main_menu - keep name and icon from original, but use URL and selected from submenu $hybridMainMenu = $section['main_menu']; $hybridMainMenu['url'] = $firstSubmenuItem['url']; $hybridMainMenu['selected'] = $firstSubmenuItem['selected']; - $filteredMenu[$sectionKey]['main_menu'] = $hybridMainMenu; } else { $filteredMenu[$sectionKey]['main_menu'] = $section['main_menu']; } - - // Add allowed submenu items + foreach ($section as $itemKey => $item) { if ($itemKey !== 'main_menu' && in_array($itemKey, $profileArray)) { $filteredMenu[$sectionKey][$itemKey] = $item; @@ -276,17 +274,83 @@ function filterMenuByProfile($menu, $profileString) { } } } - + return $filteredMenu; } -function menu($selected,$selected_child){ + +/** + * Filter menu items based on user permissions array + * + * @param array $menu The full menu structure from settingsmenu.php + * @param array $permissions The permissions array from $_SESSION['authorization']['permissions'] + * @return array Filtered menu with only items user has can_read permission for + */ +function filterMenuByPermissions($menu, $permissions) { + $filteredMenu = []; + + foreach ($menu as $sectionKey => $section) { + // Get the main_menu's 'selected' path to check permission + $mainMenuPath = $section['main_menu']['selected'] ?? $sectionKey; + + // Check if user has read permission for main menu + $mainMenuAllowed = isset($permissions[$mainMenuPath]) && + $permissions[$mainMenuPath]['can_read'] == 1; + + $allowedSubmenus = []; + $firstAllowedSubmenu = null; + + // Check each submenu item for permission + foreach ($section as $itemKey => $item) { + if ($itemKey === 'main_menu') { + continue; + } + + // Get the submenu item's 'selected' path + $submenuPath = $item['selected'] ?? $itemKey; + + // Check if user has read permission for this submenu item + if (isset($permissions[$submenuPath]) && + $permissions[$submenuPath]['can_read'] == 1) { + $allowedSubmenus[$itemKey] = $item; + if ($firstAllowedSubmenu === null) { + $firstAllowedSubmenu = $item; + } + } + } + + // Include section if main menu is allowed OR any submenu is allowed + if ($mainMenuAllowed || count($allowedSubmenus) > 0) { + $filteredMenu[$sectionKey] = []; + + // Handle main_menu entry + if (!$mainMenuAllowed && $firstAllowedSubmenu !== null) { + // User doesn't have main access but has submenu access + // Create hybrid: keep name/icon from main, use URL/selected from first submenu + $hybridMainMenu = $section['main_menu']; + $hybridMainMenu['url'] = $firstAllowedSubmenu['url']; + $hybridMainMenu['selected'] = $firstAllowedSubmenu['selected']; + $filteredMenu[$sectionKey]['main_menu'] = $hybridMainMenu; + } else { + $filteredMenu[$sectionKey]['main_menu'] = $section['main_menu']; + } + + // Add allowed submenu items + foreach ($allowedSubmenus as $itemKey => $item) { + $filteredMenu[$sectionKey][$itemKey] = $item; + } + } + } + + return $filteredMenu; +} +function menu($selected, $selected_child){ include dirname(__FILE__,2).'/settings/settings_redirector.php'; if(isset($_SESSION['country_code'])){ $api_file_language = dirname(__FILE__,2).'/settings/translations/translations_'.strtoupper($_SESSION['country_code']).'.php'; - if (file_exists($api_file_language)){ - include $api_file_language; //Include the code + if (file_exists($api_file_language)){ + include $api_file_language; } else { include dirname(__FILE__,2).'/settings/translations/translations_US.php'; @@ -294,31 +358,70 @@ function menu($selected,$selected_child){ } else { include dirname(__FILE__,2).'/settings/translations/translations_US.php'; - } - - //Define Menu + } + $menu = ''; - //filter the main_menu array based on profile - $filteredMenu = filterMenuByProfile($main_menu, $_SESSION['profile']); + // Use permissions array if available, fallback to legacy profile string + if (isset($_SESSION['authorization']['permissions']) && !empty($_SESSION['authorization']['permissions'])) { + $filteredMenu = filterMenuByPermissions($main_menu, $_SESSION['authorization']['permissions']); + } else { + $filteredMenu = filterMenuByProfile($main_menu, $_SESSION['authorization']['profile']); + } - foreach ($filteredMenu as $menu_item){ - //Main Item - $menu .= ''.ucfirst((${$menu_item['main_menu']['name']} ?? 'not specified')).''; - - if (count($menu_item) > 1){ - //SUBMENU - $menu .= '
'; + foreach ($filteredMenu as $menu_item) { + $submenuCount = count($menu_item) - 1; // Exclude main_menu + $mainMenu = $menu_item['main_menu']; + $menuName = ucfirst((${$mainMenu['name']} ?? ucfirst(str_replace('menu_', '', $mainMenu['name'])))); + $isMainSelected = ($selected == $mainMenu['selected']); - foreach ($menu_item as $key => $item){ - //filter out main_menu - if($key !='main_menu'){ - $menu .= ''.ucfirst((${$item['name']}?? 'not specified')).''; + // Check if any child is selected (for expanded state) + $hasSelectedChild = false; + foreach ($menu_item as $key => $item) { + if ($key !== 'main_menu' && $selected == $item['selected']) { + $hasSelectedChild = true; + break; } - } - $menu .= '
'; + } + + if ($submenuCount > 0) { + // HAS SUBMENUS: Render as collapsible header (not a link) + $expandedClass = ($isMainSelected || $hasSelectedChild) ? ' expanded' : ''; + $selectedClass = $isMainSelected ? ' selected' : ''; + + $menu .= ''; + + // SUBMENU container + $subExpandedClass = ($isMainSelected || $hasSelectedChild) ? ' expanded' : ''; + $subDisplayStyle = ($isMainSelected || $hasSelectedChild) ? 'display:flex;' : 'display:none;'; + $menu .= '
'; + + foreach ($menu_item as $key => $item) { + if ($key !== 'main_menu') { + $itemName = ucfirst((${$item['name']} ?? ucfirst(str_replace('menu_', '', $item['name'])))); + $itemSelectedClass = ($selected == $item['selected']) ? ' class="selected"' : ''; + $menu .= ''; + $menu .= ''; + $menu .= '' . $itemName . ''; + $menu .= ''; + } + } + $menu .= '
'; + } else { + // NO SUBMENUS: Render as direct link + $selectedClass = $isMainSelected ? ' class="selected"' : ''; + $menu .= ''; + $menu .= ''; + $menu .= '' . $menuName . ''; + $menu .= ''; + $menu .= ''; } } + return $menu; } @@ -336,12 +439,12 @@ function template_header($title, $selected = 'assets', $selected_child = 'view') $domain = getDomainName($_SERVER['SERVER_NAME']); $custom_css = (file_exists(dirname(__FILE__,2).'/custom/'.$domain.'/style/'.$domain.'.css') ? './custom/'.$domain.'/style/'.$domain.'.css' : './style/admin.css'); - $user = ucfirst($_SESSION['username']); + $user = ucfirst($_SESSION['authorization']['clientID']); if (filter_var($user, FILTER_VALIDATE_EMAIL)){ $user = substr($user, 0, strpos($user, "@")); } - if (isset($_SESSION['id'])){$id = $_SESSION['id'];} else{$id='';} + if (isset($_SESSION['authorization']['id'])){$id = $_SESSION['authorization']['id'];} else{$id='';} if(isset($_SESSION['country_code'])){ $api_file_language = dirname(__FILE__,2).'/settings/translations/translations_'.strtoupper($_SESSION['country_code']).'.php'; @@ -412,23 +515,19 @@ echo << { - form.addEventListener('submit', function(e) { - // Show loading screen before form submission + document.querySelectorAll('form').forEach(function(form) { + form.addEventListener('submit', function() { showLoading(); }); }); } - + // Intercept all network requests (fetch and XMLHttpRequest) function interceptNetworkRequests() { // Track active requests @@ -516,8 +615,8 @@ EOT; //------------------------------------------ function template_footer($js_script = '') { $js_script = $js_script ? '' : ''; - $lancode = $_SESSION['language'] ?? 'US'; - $user_mail = $_SESSION['email'] ?? ''; + $lancode = $_SESSION['authorization']['language'] ?? 'US'; + $user_mail = $_SESSION['authorization']['email'] ?? ''; $veliti_cim = ''; if (veliti_cim){ $veliti_cim = ' @@ -1067,7 +1166,9 @@ function validate_secure_download_token($token, $secret_key = null) { // Check JSON parsing with detailed error info if ($header === null) { $json_error = json_last_error_msg(); - debuglog("JSON decode failed for header. Raw JSON: " . $header_json . " Error: " . $json_error); + if(debug){ + debuglog("JSON decode failed for header. Raw JSON: " . $header_json . " Error: " . $json_error); + } return ['error' => 'INVALID_TOKEN', 'message' => 'Failed to decode token header JSON: ' . $json_error]; } if ($payload === null) { @@ -1227,8 +1328,13 @@ function log_download($params) { function ioServer($api_call, $data){ include dirname(__FILE__,2).'/settings/settings_redirector.php'; + + if(debug){ + $data_log = is_array($data) ? json_encode($data) : $data; + debuglog($date." - ioServer incoming call: api_call=$api_call, data=" . $data_log); + } - $token = $_SESSION['userkey'] ?? 'authorization_request'; + $token = $_SESSION['authorization']['userkey'] ?? 'authorization_request'; $bearertoken = createCommunicationToken($token); $url = $baseurl.$api_call; @@ -1252,7 +1358,13 @@ function ioServer($api_call, $data){ $resp = curl_exec($curl); $http_status = curl_getinfo($curl) ?? '200'; curl_close($curl); + + if (debug) { + $resp_log = $date . " - ioServer: URL=$url, HTTP Code= ". ($http_status['http_code'] ?? 'unknown') . ", Response=" . substr($resp, 0, 500) . (strlen($resp) > 500 ? '...' : ''); + debuglog(json_encode($resp_log)); + } + //Check If errorcode is returned if($http_status['http_code'] == '403' || $http_status['http_code'] == '400') {$resp = generate_payload('NOK');} @@ -1354,101 +1466,220 @@ function ioAPIv2($api_call, $data, $token){ } //------------------------------------------ -// DEFINE WHERECLAUSE BASED ON ACCOUNTHIERARCHY ALL +// API TO API version 2 File Upload //------------------------------------------ +function ioAPIv2_FileUpload($api_call, $fileData, $additionalData = [], $token = '') { + include dirname(__FILE__,2).'/settings/settings_redirector.php'; + + $url = $baseurl . $api_call; + + $curl = curl_init($url); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_POST, true); + + // Prepare headers (no Content-Type for multipart uploads) + if ($token != '') { + $headers = array("Authorization: Bearer $token"); + } else { + $headers = array(); + } + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + + // Merge file data with additional data + $postData = array_merge($fileData, $additionalData); + curl_setopt($curl, CURLOPT_POSTFIELDS, $postData); -function getWhereclause($table_name,$permission,$partner,$method){ + $resp = curl_exec($curl); + $http_status = curl_getinfo($curl) ?? '200'; + curl_close($curl); - //api_name converter to table - $table =[ - "equipment" => "e.accounthierarchy", - "products" => "p.accounthierarchy", - "profile" => "partnerhierarchy", - "text_variables" => "tv.accounthierarchy", - "products_attributes_items" => "pat.accounthierarchy", - "products_attributes_groups" => "pag.accounthierarchy", - "pricelists" => "pls.accounthierarchy", - "pricelists_items" => "pli.accounthierarchy" - ]; + if ($http_status['http_code'] == '403' || $http_status['http_code'] == '400') { + $resp = json_encode('NOK'); + } + + if (debug){ + $message = $date.';'.$api_call; + debuglog($message); + } - $table = ($table_name != '') ? $table[$table_name] : 'accounthierarchy'; - $type = ($method == 'get') ? 'WHERE ' : ' AND '; - //SoldTo is empty - if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';} - - //default whereclause - $whereclause = ''; - - switch ($permission) { - case '4': - $whereclause = ''; - $condition = ''; - break; - case '3': - $condition = '__salesid___'.$partner->salesid.'___soldto___%'; - $whereclause = $type.$table.' like "'.$condition.'"'; - break; - case '2': - $condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search; - $whereclause = $type.$table.' like "'.$condition.'"'; - break; - default: - $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 = $type.$table.' like "'.$condition.'"'; - break; - } - - return array($whereclause,$condition); + return $resp; } //------------------------------------------ -// DEFINE WHERECLAUSE BASED ON ACCOUNTHIERARCHY SALES AND SOLD +// DEFINE WHERECLAUSE BASED ON ACCOUNTHIERARCHY //------------------------------------------ -function getWhereclauselvl2($table_name,$permission,$partner,$method){ +function getWhereclause($table_name, $permission, $partner, $method) { + // API name converter to table + $table = [ + "equipment" => "e.accounthierarchy", + "products" => "p.accounthierarchy", + "profile" => "partnerhierarchy", + "text_variables" => "tv.accounthierarchy", + "products_attributes_items" => "pat.accounthierarchy", + "products_attributes_groups" => "pag.accounthierarchy", + "pricelists" => "pls.accounthierarchy", + "pricelists_items" => "pli.accounthierarchy" + ]; - //api_name converter to table - $table =[ - "pricelist" => "pls.accounthierarchy", - "communications" => "salesID", - "partners" => "salesID", - "discounts" => "d.accounthierarchy", - "invoice" => "inv.accounthierarchy", - "attributes" => "pat.accounthierarchy", - "config" => "pc.accounthierarchy", - "software" => "p.accounthierarchy", - "transactions" => "tx.accounthierarchy", - "dealers" => "d.accounthierarchy", - "categories" => "c.accounthierarchy", - "products_software_licenses" => "l.accounthierarchy" - ]; + $table = ($table_name != '') ? $table[$table_name] : 'accounthierarchy'; + $type = ($method == 'get') ? 'WHERE ' : ' AND '; - $table = ($table_name != '') ? $table[$table_name] : 'accounthierarchy'; - $type = ($method == 'get') ? 'WHERE ' : ' AND '; - - //SoldTo is empty - if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';} + // If permission is 4, grant full access (admin+) + if ($permission == '4' || $permission === 4) { + return array('', ''); + } - //default whereclause - $whereclause = ''; + // Determine hierarchy level based on which fields are filled + $hierarchy_level = getHierarchyLevel($partner); + + // Build condition based on hierarchy level + $condition = buildHierarchyCondition($partner, $hierarchy_level); + + // Build whereclause + $whereclause = ($condition != '') ? $type . $table . ' LIKE "' . $condition . '"' : ''; - switch ($permission) { - case '4': - $whereclause = ''; - $condition = ''; - break; - case '3': - $condition = '__salesid___'.$partner->salesid.'___soldto___%'; - $whereclause = $type.$table.' like "'.$condition.'" '; - break; - default: - $condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search; - $whereclause = $type.$table.' like "'.$condition.'"'; - break; -} - - return array($whereclause,$condition); + return array($whereclause, $condition); } +function getWhereclauselvl2($table_name, $permission, $partner, $method) { + // API name converter to table + $table = [ + "pricelist" => "pls.accounthierarchy", + "communications" => "salesID", + "partners" => "salesID", + "discounts" => "d.accounthierarchy", + "invoice" => "inv.accounthierarchy", + "attributes" => "pat.accounthierarchy", + "config" => "pc.accounthierarchy", + "software" => "p.accounthierarchy", + "transactions" => "tx.accounthierarchy", + "dealers" => "d.accounthierarchy", + "categories" => "c.accounthierarchy", + "products_software_licenses" => "l.accounthierarchy" + ]; + + $table = ($table_name != '') ? $table[$table_name] : 'accounthierarchy'; + $type = ($method == 'get') ? 'WHERE ' : ' AND '; + + // If permission is 4, grant full access (admin+) + if ($permission == '4' || $permission === 4) { + return array('', ''); + } + + // Determine hierarchy level (lvl2 only uses salesid and soldto) + $hierarchy_level = getHierarchyLevelLvl2($partner); + + // Build condition based on hierarchy level + $condition = buildHierarchyConditionLvl2($partner, $hierarchy_level); + + // Build whereclause + $whereclause = ($condition != '') ? $type . $table . ' LIKE "' . $condition . '"' : ''; + + return array($whereclause, $condition); +} + +// Helper function to determine hierarchy level for full hierarchy (4 levels) +function getHierarchyLevel($partner) { + // Level 4: All fields filled (salesid, soldto, shipto, location) + if (!empty($partner->salesid) && !empty($partner->soldto) && + !empty($partner->shipto) && !empty($partner->location)) { + return 4; + } + // Level 3: salesid, soldto, shipto filled (location empty) + if (!empty($partner->salesid) && !empty($partner->soldto) && + !empty($partner->shipto) && empty($partner->location)) { + return 3; + } + // Level 2: salesid, soldto filled (shipto and location empty) + if (!empty($partner->salesid) && !empty($partner->soldto) && + empty($partner->shipto) && empty($partner->location)) { + return 2; + } + // Level 1: Only salesid filled + if (!empty($partner->salesid) && empty($partner->soldto)) { + return 1; + } + // Level 0: No restrictions (all access) + return 0; +} + +// Helper function to determine hierarchy level for lvl2 (2 levels only) +function getHierarchyLevelLvl2($partner) { + // Level 2: salesid and soldto filled + if (!empty($partner->salesid) && !empty($partner->soldto)) { + return 2; + } + // Level 1: Only salesid filled + if (!empty($partner->salesid) && empty($partner->soldto)) { + return 1; + } + // Level 0: No restrictions (all access) + return 0; +} + +// Helper function to build condition string for full hierarchy +function buildHierarchyCondition($partner, $level) { + $condition = ''; + + switch ($level) { + case 4: // Exact match on all 4 levels + $condition = '__salesid___' . $partner->salesid . + '___soldto___' . substr($partner->soldto, 0, strpos($partner->soldto, "-")) . '-' . + substr($partner->soldto, strpos($partner->soldto, "-") + 1) . + '___shipto___' . substr($partner->shipto, 0, strpos($partner->shipto, "-")) . '-' . + substr($partner->shipto, strpos($partner->shipto, "-") + 1) . + '___location___' . substr($partner->location, 0, strpos($partner->location, "-")) . '-' . + substr($partner->location, strpos($partner->location, "-") + 1) . '%'; + break; + + case 3: // Match salesid, soldto, shipto - all locations under this shipto + $condition = '__salesid___' . $partner->salesid . + '___soldto___' . substr($partner->soldto, 0, strpos($partner->soldto, "-")) . '-' . + substr($partner->soldto, strpos($partner->soldto, "-") + 1) . + '___shipto___' . substr($partner->shipto, 0, strpos($partner->shipto, "-")) . '-%'; + break; + + case 2: // Match salesid, soldto - all shiptos and locations under this soldto + $condition = '__salesid___' . $partner->salesid . + '___soldto___' . substr($partner->soldto, 0, strpos($partner->soldto, "-")) . '-%'; + break; + + case 1: // Match salesid only - all soldtos, shiptos, and locations under this salesid + $condition = '__salesid___' . $partner->salesid . '___soldto___%'; + break; + + case 0: // No restrictions + $condition = ''; + break; + } + + return $condition; +} + +// Helper function to build condition string for lvl2 +function buildHierarchyConditionLvl2($partner, $level) { + $condition = ''; + + switch ($level) { + case 2: // Match salesid and soldto + $condition = '__salesid___' . $partner->salesid . + '___soldto___' . substr($partner->soldto, 0, strpos($partner->soldto, "-")) . '-%'; + break; + + case 1: // Match salesid only + $condition = '__salesid___' . $partner->salesid . '___soldto___%'; + break; + + case 0: // No restrictions + $condition = ''; + break; + } + + return $condition; +} + + //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //get user profile||$profile=settings, $permision = userright() //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -1481,70 +1712,104 @@ function getProfile($profile, $permission){ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //Is allowed (yes=1)++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - function isAllowed($page,$profile,$permission,$action){ - - //Include settingsa - include dirname(__FILE__,2).'/settings/settings_redirector.php'; + // RBAC-based permission check + // $access_element = the page/element to check access for (e.g., 'user', 'equipment') + // $permissions = array of user permissions from $_SESSION['authorization']['permissions'] (from getUserPermissions()) + // $basic_permission_level = optional legacy permission level (5 = system, always allowed) + // $action = C, R, U, or D + function isAllowed($access_element, $permissions, $basic_permission_level = null, $action = 'R'){ + + $date = date('Y-m-d H:i:s'); + $filelocation = dirname(__FILE__,2).'/log/permission_log_'.date('d').'.txt'; // Always allowed collections: [collection => allowed_actions_string] $always_allowed = [ - 'com_log' => 'U', + 'com_log' => 'CRU', + 'application' => 'CRU', + 'user_role_assignments' => 'R', + 'user_permissions' => 'R', + 'products_software' => 'R', 'software_update' => 'R', 'software_download' => 'R', - 'software_available' => 'R' + 'software_available' => 'R', + 'history' => 'RU', + 'payment' => 'RU' ]; - // Group permissions: [granting_page => [collection => allowed_actions_string]] - $group_permissions = [ - 'products_software' => [ - 'products_software_version_access_rules' => 'CRU', - 'products_software_licenses' => 'CRU', - 'products_software_upgrade_paths' => 'CRU', - 'products_software_versions' => 'CRU', - 'products_software_assignment' => 'CRU', - 'products_software_assignments' => 'CRU' - ] - ]; + // 1. Check if basic_permission_level is 4 (System-admin+) - always allow + if ($basic_permission_level !== null && $basic_permission_level == 4) { - // Debug log - debuglog("isAllowed called: page=$page, permission=$permission, action=$action"); - - // 1. Check always allowed - if (isset($always_allowed[$page]) && str_contains($always_allowed[$page], $action)) { - debuglog("Allowed by always_allowed"); return 1; } - //GET ALLOWED ACTIONS - $user_permission = ${'permission_'.$permission}; - - //CHECK ALLOWED - $page_action = str_contains($user_permission,$action) > 0 ? 1 : 0; //CHECK IF USER IS ALLOWED TO DO THE ACTION - $page_access = str_contains($profile,$page) > 0 ? 1 : 0; //CHECK USER IS ALLOWED TO ACCESS PAGE - - debuglog("user_permission=$user_permission, page_action=$page_action, page_access=$page_access"); - - // 2. Check user permissions (standard) - if ($page_access == 1 && $page_action == 1){ - debuglog("Allowed by user permissions"); - return 1; - } - - // 3. If not allowed by user, check group permissions - if ($page_access == 0) { - foreach ($group_permissions as $granting_page => $grants) { - if (str_contains($profile, $granting_page)) { - debuglog("Found granting_page: $granting_page"); - if (isset($grants[$page]) && str_contains($grants[$page], $action)) { - debuglog("Allowed by group permissions"); - return 1; - } + // 2. Check always_allowed list (supports multi-action like 'RU') + if (isset($always_allowed[$access_element])) { + $actions = str_split($action); + $all_in_allowed = true; + foreach ($actions as $single_action) { + if (!str_contains($always_allowed[$access_element], $single_action)) { + $all_in_allowed = false; + break; } } + if ($all_in_allowed) { + return 1; + } + } + + // 3. Check RBAC permissions array (from getUserPermissions()) + if (is_array($permissions) && isset($permissions[$access_element])) { + $element_permissions = $permissions[$access_element]; + + // Map action letter to permission key + $action_map = [ + 'C' => 'can_create', + 'R' => 'can_read', + 'U' => 'can_update', + 'D' => 'can_delete' + ]; + + // Check each action in the string (supports 'R', 'RU', 'CRUD', etc.) + $actions = str_split($action); + $all_allowed = true; + + foreach ($actions as $single_action) { + $permission_key = $action_map[$single_action] ?? null; + + if (!$permission_key || !isset($element_permissions[$permission_key]) || $element_permissions[$permission_key] != 1) { + $all_allowed = false; + break; + } + } + + if ($all_allowed) { + return 1; + } + + if(debug){ + $test = "$date - isAllowed called: access_element=$access_element, basic_permission_level=$basic_permission_level, action=$action".PHP_EOL; + error_log($test, 3, $filelocation); + $perm_value = $element_permissions[$permission_key] ?? 'not_set'; + $test = "$date - RBAC check failed: $access_element -> $permission_key = $perm_value".PHP_EOL; + error_log($test, 3, $filelocation); + } + } else { + if(debug){ + $test = "$date - isAllowed called: access_element=$access_element, basic_permission_level=$basic_permission_level, action=$action".PHP_EOL; + error_log($test, 3, $filelocation); + $test = "$date - Access element '$access_element' not found in permissions array".PHP_EOL; + error_log($test, 3, $filelocation); + } } - debuglog("Not allowed"); // Not allowed + if(debug){ + $test = "$date - isAllowed called: access_element=$access_element, basic_permission_level=$basic_permission_level, action=$action".PHP_EOL; + error_log($test, 3, $filelocation); + $test = "$date - Not allowed: access_element=$access_element, action=$action".PHP_EOL; + error_log($test, 3, $filelocation); + } + return 0; } @@ -1650,33 +1915,38 @@ function getPartnerID($str){ // overview Indicators //------------------------------------------ function overviewIndicators($warranty, $service, $sw_version, $sw_version_latest){ - include dirname(__FILE__,2).'/settings/settings_redirector.php'; - include dirname(__FILE__,2).'/settings/systemfirmware.php'; - $indicator =''; - //In warranty - if (!empty($warranty ) && $warranty > $warrantydate){ - $indicator .= 'W'; - } else { - $indicator .= 'W'; - } - //Out of Service - if (!empty($service) && $service < $servicedate){ - $indicator .= 'S'; - } else { - $indicator .= 'S'; - } + + include dirname(__FILE__,2).'/settings/settings_redirector.php'; + include dirname(__FILE__,2).'/settings/systemfirmware.php'; + + $indicator =''; + $current_date = date('Y-m-d'); + + //In warranty + if (!empty($warranty ) && $warranty >= $current_date){ + $indicator .= 'W'; + } else { + $indicator .= 'W'; + } + //Out of Service + if (!empty($service) && $service >= $current_date){ + $indicator .= 'S'; + } else { + $indicator .= 'S'; + } //Firmware if (isset($sw_version_latest)){ - if($sw_version_latest == 1){ - $indicator .= 'F'; + if($sw_version_latest == 1){ + $indicator .= 'F'; + } + else { + if ($sw_version == ''){ + $indicator .= 'F'; } else { - if ($sw_version == ''){ - $indicator .= 'F'; - } else { - $indicator .= 'F'; - } + $indicator .= 'F'; } + } } return $indicator; @@ -1693,23 +1963,24 @@ function warrantyStatus($input){ //INCLUDE TRANSLATION FILE if(isset($_SESSION['country_code'])){ $api_file_language = dirname(__FILE__,2).'/settings/translations/translations_'.strtoupper($_SESSION['country_code']).'.php'; - if (file_exists($api_file_language)){ - include $api_file_language; //Include the code - } - else { - include dirname(__FILE__,2).'/settings/translations/translations_US.php'; - } + if (file_exists($api_file_language)){ + include $api_file_language; //Include the code + } + else { + include dirname(__FILE__,2).'/settings/translations/translations_US.php'; + } } else { include dirname(__FILE__,2).'/settings/translations/translations_US.php'; } $warranty_date_due ='Unknown'; - - if (!empty($input) && $input < $warrantydate){ - $warranty_date_due = ''.$warranty_outdated_text.''; + $current_date = date('Y-m-d'); + + if (!empty($input) && $input >= $current_date){ + $warranty_date_due = ''.$warranty_recent.' ('.$input.')'; } else { - $warranty_date_due =''.$warranty_recent.' ('.date('Y-m-d', strtotime($input. ' + 365 days')).')'; + $warranty_date_due = ''.$warranty_outdated_text.''; } return $warranty_date_due; @@ -1736,13 +2007,15 @@ function serviceStatus($input){ else { include dirname(__FILE__,2).'/settings/translations/translations_US.php'; } - + + $current_date = date('Y-m-d'); $service_date_due ='Unknown'; - if (!empty($input) && $input < $servicedate){ - $service_date_due = ''.$service_renewal_text.''; + if (!empty($input) && $input >= $current_date){ + $service_date_due =''.$service_recent.' ('.$input.')'; } else { - $service_date_due =''.$service_recent.' ('.date('Y-m-d', strtotime($input. ' + 365 days')).')'; + $service_date_due = ''.$service_renewal_text.''; + } return $service_date_due; @@ -1769,7 +2042,7 @@ function availableFirmware($sw_version,$sw_version_latest){ switch ($sw_version_latest) { case 1: - $message = ''.$firmware_recent_text.''; + $message = ''.$firmware_recent_text.''; break; case 0: @@ -1777,7 +2050,7 @@ function availableFirmware($sw_version,$sw_version_latest){ break; default: - $message ='Unknown'; + $message =''; break; } @@ -2581,7 +2854,7 @@ function listPartner($partnertype, $user_right, $input, $required) //BASED ON USERRIGHT DEFINE SQL AND DATA RETURNED if ($user_right != 3 || $user_right !=4) { //NOT ADMIN USER - $partner = json_decode($_SESSION['partnerhierarchy']); + $partner = json_decode($_SESSION['authorization']['partnerhierarchy']); //SoldTo is empty if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';} //BUILD CONDITION @@ -2629,7 +2902,7 @@ function listAccounts($type, $user_right, $input) //BASED ON USERRIGHT DEFINE SQL AND DATA RETURNED if ($user_right != 3 || $user_right !=4) { //NOT ADMIN USER - $partner = json_decode($_SESSION['partnerhierarchy']); + $partner = json_decode($_SESSION['authorization']['partnerhierarchy']); //SoldTo is empty if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';} //BUILD CONDITION @@ -2778,7 +3051,7 @@ function uploadrequest($key){ //------------------------------------------ function debuglog($error){ include_once dirname(__FILE__,2).'/settings/config_redirector.php'; - $test = $error.PHP_EOL; + $test = $error.PHP_EOL; $filelocation = dirname(__FILE__,2).'/log/log_'.date('d').'.txt'; error_log($test, 3, $filelocation); } @@ -2898,20 +3171,29 @@ function showlog($object,$objectID){ $stmt->execute([$object,$objectID]); $changes = $stmt->fetchAll(PDO::FETCH_ASSOC); - $view = ''; - foreach($changes as $change){ - - $object_value = $change['object_value']; - - //UPDATE TO HUMANREADABLE STATUS - if ($object == 'equipment' && $change['object_field'] == 'status'){ - $object_text = 'status'.$change['object_value'].'_text'; - $object_value = $$object_text; + $view = '
'; + if ($changes) { + foreach ($changes as $change) { + $object_value = $change['object_value']; + // Human-readable status + if ($object == 'equipment' && $change['object_field'] == 'status') { + $object_text = 'status' . $change['object_value'] . '_text'; + if (isset($$object_text)) { + $object_value = $$object_text; + } + } + $entry = htmlspecialchars( $object_value . ' - ' . $change['created'] . ' - ' . $change['createdby']); + $view .= '
+ +

'.$entry.'

+
'; + + } + } else { + $view .= '
No changelog entries found.
'; } - $view .= ''; - } - - return $view; + $view .= '
'; + return $view; } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -3726,27 +4008,29 @@ function dateInRange($start_date, $end_date, $date_check) function getLatestVersion($productcode,$token){ - //CALL TO API TO GET ALL ACTIVE CONTRACTS - $api_url = '/v2/products_software/productcode='.$productcode; - $responses = ioAPIv2($api_url,'',$token); + //$pdo = dbConnect($dbname); - //Decode Payload - if (!empty($responses)){$responses = json_decode($responses,true); - } - else{ - $responses = $output = array( - "productcode" => "", - "version"=> "", - "mandatory"=> "", - "latest"=> "", - "software"=> "", - "source" => "", - "source_type" => "" - ); - ;} + //CALL TO API TO GET ALL ACTIVE CONTRACTS + $api_url = '/v2/products_software/productcode='.$productcode; + $responses = ioAPIv2($api_url,'',$token); + + //Decode Payload + if (!empty($responses)){$responses = json_decode($responses,true); + } + else{ + $responses = $output = array( + "productcode" => "", + "version"=> "", + "mandatory"=> "", + "latest"=> "", + "software"=> "", + "source" => "", + "source_type" => "" + ); + ;} - //DEFAULT OUTPUT - return $responses; + //DEFAULT OUTPUT + return $responses; } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -4374,6 +4658,7 @@ function transformOrderData(array $orderData): array { 'country' => $firstRow['address_country'], 'phone' => $firstRow['address_phone'], 'language' => $firstRow['user_language'], + 'vat_number' => $firstRow['vat_number'] ], 'products' => [], 'invoice' => [ @@ -5163,7 +5448,7 @@ function updateSoftwareVersionStatus($pdo, $serialnumber = null) { SET e.sw_version_latest = 1 WHERE psv.latest = 1 AND psv.status = 1 - AND lower(e.sw_version) = lower(psv.version) + AND LOWER(TRIM(LEADING "0" FROM e.sw_version)) = lower(psv.version) AND (lower(psv.hw_version) = lower(e.hw_version) OR lower(psv.hw_version) IS NULL OR lower(psv.hw_version) = "") AND e.sw_version_latest = 0' . $sn_clause; @@ -5339,10 +5624,12 @@ function generateSoftwareInvoice($invoice_data, $order_id, $language = 'US') { $customer_country = $customer['address_country'] ?? ''; // Extract transaction data - $payment_amount = $invoice_data['payment_amount'] ?? 0; - $tax_amount = $invoice_data['tax_amount'] ?? 0; - $shipping_amount = $invoice_data['shipping_amount'] ?? 0; - $discount_amount = $invoice_data['discount_amount'] ?? 0; + $pricing = $invoice_data['pricing'] ?? []; + $payment_amount = $pricing['payment_amount'] ?? $invoice_data['payment_amount'] ?? 0; + $tax_amount = $pricing['tax_total'] ?? $invoice_data['tax_amount'] ?? 0; + $shipping_amount = $pricing['shipping_total'] ?? $invoice_data['shipping_amount'] ?? 0; + $discount_amount = $pricing['discount_total'] ?? $invoice_data['discount_amount'] ?? 0; + $subtotal_amount = $pricing['subtotal'] ?? 0; $currency = 'EUR'; // Default currency $invoice_date = $invoice_data['invoice_created'] ?? date('Y-m-d H:i:s'); @@ -5373,6 +5660,39 @@ function generateSoftwareInvoice($invoice_data, $order_id, $language = 'US') { 'serial_number' => $serial_number, 'license_key' => $license_key ]; + } elseif (isset($invoice_data['products']) && is_array($invoice_data['products'])) { + // New format with products array + $pdo = dbConnect($dbname); + + foreach ($invoice_data['products'] as $product) { + $product_code = $product['productcode'] ?? null; + $product_name = $product['product_name'] ?? null; + $product_options = $product['options'] ?? []; + $product_serial = $product_options['serial_number'] ?? null; + + // Handle case where productcode and product_name are empty but serial_number exists + if ((empty($product_code) || $product_code === null) && + (empty($product_name) || $product_name === null) && + !empty($product_serial)) { + $product_code = 'License'; + $product_name = 'software license for ' . $product_serial; + } + + // Get license key from database + $sql = 'SELECT license_key FROM products_software_licenses WHERE transaction_id = ? LIMIT 1'; + $stmt = $pdo->prepare($sql); + $stmt->execute([$order_id]); + $license_result = $stmt->fetch(PDO::FETCH_ASSOC); + $license_key = $license_result['license_key'] ?? 'Pending'; + + $items[] = [ + 'name' => $product_name ?? 'Software Upgrade', + 'quantity' => $product['quantity'] ?? 1, + 'price' => $product['price'] ?? 0, + 'serial_number' => $product_serial ?? 'N/A', + 'license_key' => $license_key + ]; + } } // Load language translations @@ -5402,131 +5722,23 @@ function generateSoftwareInvoice($invoice_data, $order_id, $language = 'US') { $lbl_license_key = $translations['license_key'] ?? 'License Key'; $lbl_license_expiry = $translations['license_expiry'] ?? 'License Expiry'; - // Build HTML invoice - $html = ' - - - - - - -
-
' . htmlspecialchars($lbl_invoice) . '
-
- ' . htmlspecialchars($lbl_invoice_number) . ': ' . htmlspecialchars($order_id) . '
- ' . htmlspecialchars($lbl_invoice_date) . ': ' . htmlspecialchars(date('Y-m-d', strtotime($invoice_date))) . ' -
-
- -
- ' . htmlspecialchars($lbl_customer) . ':
- ' . htmlspecialchars($customer_name) . '
'; - - if ($customer_address) { - $html .= htmlspecialchars($customer_address) . '
'; - } - if ($customer_city || $customer_zip) { - $html .= htmlspecialchars($customer_zip . ' ' . $customer_city) . '
'; - } - if ($customer_state) { - $html .= htmlspecialchars($customer_state) . '
'; - } - if ($customer_country) { - $html .= htmlspecialchars($customer_country) . '
'; + // Subtotal calculation - use from pricing data or calculate from items + if ($subtotal_amount > 0) { + $subtotal = $subtotal_amount; + } else { + // Calculate from items if not provided + $subtotal = 0; + foreach ($items as $item) { + $subtotal += $item['price'] * $item['quantity']; + } } - $html .= htmlspecialchars($customer_email) . ' -
+ // Build HTML for PDF and EMAIL + include dirname(__FILE__,2).'/assets/mail/email_template_invoice.php'; + include dirname(__FILE__,2).'/assets/mail/pdf_template_invoice.php'; + - - - - - - - - - '; - - foreach ($items as $item) { - $html .= ' - - - - '; - } - - // Subtotal - $subtotal = $payment_amount - $tax_amount - $shipping_amount + $discount_amount; - $html .= ' - - - '; - - // Tax - if ($tax_amount > 0) { - $html .= ' - - - '; - } - - // Shipping - if ($shipping_amount > 0) { - $html .= ' - - - '; - } - - // Discount - if ($discount_amount > 0) { - $html .= ' - - - '; - } - - // Total - $html .= ' - - - '; - - $html .= ' -
' . htmlspecialchars($lbl_product) . '' . htmlspecialchars($lbl_quantity) . '' . htmlspecialchars($lbl_price) . '
' . htmlspecialchars($item['name']) . '' . htmlspecialchars($item['quantity']) . '' . number_format($item['price'], 2) . ' ' . htmlspecialchars($currency) . '
' . htmlspecialchars($lbl_subtotal) . ':' . number_format($subtotal, 2) . ' ' . htmlspecialchars($currency) . '
' . htmlspecialchars($lbl_tax) . ':' . number_format($tax_amount, 2) . ' ' . htmlspecialchars($currency) . '
' . htmlspecialchars($lbl_shipping) . ':' . number_format($shipping_amount, 2) . ' ' . htmlspecialchars($currency) . '
' . htmlspecialchars($lbl_discount) . ':-' . number_format($discount_amount, 2) . ' ' . htmlspecialchars($currency) . '
' . htmlspecialchars($lbl_total) . ':' . number_format($payment_amount, 2) . ' ' . htmlspecialchars($currency) . '
'; - - // License information - if ($license_key && $serial_number) { - $html .= '
- Software License Information:
- ' . htmlspecialchars($lbl_device_serial) . ': ' . htmlspecialchars($serial_number) . '
- ' . htmlspecialchars($lbl_license_key) . ': ' . htmlspecialchars($license_key) . '
- ' . htmlspecialchars($lbl_license_expiry) . ': 2099-12-31 -
'; - } - - $html .= ' - -'; - - return [$html, $customer_email, $order_id]; + return [$message,$pdf,$customer_email, $order_id]; } /** @@ -5572,4 +5784,121 @@ function updateSoftwareLatestFlags($pdo, $version_id, $hw_version) { $stmt->execute([$version['rowID']]); } } +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// Generate Countries File from Taxes API +++++++++++++++++ +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ +function generateCountriesFile($token){ + + //API call to get all taxes + $api_url = '/v2/taxes'; + $response = ioAPIv2($api_url, '', $token); + + if(!empty($response)){ + //decode the API response + $taxes = json_decode($response, true); + + if(!empty($taxes) && is_array($taxes)){ + //Build the countries array - id as key, with country name and tax rate + $countries = []; + foreach($taxes as $tax){ + $countries[$tax['id']] = [ + 'country' => $tax['country'] ?? '', + 'taxes' => $tax['rate'] ?? 0 + ]; + } + + //Generate PHP file content + $fileContent = " $data){ + $fileContent .= " " . $id . " => ['country' => '" . addslashes($data['country']) . "', 'taxes' => " . $data['taxes'] . "],\n"; + } + $fileContent .= "];\n"; + + //Write to settings/countries.php + $filePath = dirname(__FILE__, 2) . '/settings/countries.php'; + $result = file_put_contents($filePath, $fileContent); + + return ($result !== false); + } + } + + return false; +} + +/** + * Get combined user permissions based on all assigned roles + * + * This function retrieves all role assignments for a user and combines permissions + * from multiple roles. If the same access_element appears in multiple roles, + * permissions are merged (OR operation) so the user gets the union of all permissions. + * + * For example: + * - Role A: access_element 'assets' with C=1, U=1, D=0 + * - Role B: access_element 'assets' with C=0, U=0, D=1 + * - Result: access_element 'assets' with C=1, U=1, D=1 + * + * @param PDO $pdo Database connection + * @param int $user_id The user ID to get permissions for + * @return array Associative array of permissions indexed by access_element path + * Each element contains: [path, name, group, can_create, can_read, can_update, can_delete] + */ +function getUserPermissions($pdo, $user_id) { + // Get all active role assignments for the user with their permissions + $sql = "SELECT + ae.access_path, + ae.access_name, + ae.access_group, + rap.can_create, + rap.can_read, + rap.can_update, + rap.can_delete + FROM user_role_assignments ura + INNER JOIN user_roles ur ON ura.role_id = ur.rowID + INNER JOIN role_access_permissions rap ON ur.rowID = rap.role_id + INNER JOIN access_elements ae ON rap.access_id = ae.rowID + WHERE ura.user_id = :user_id + AND ura.is_active = 1 + AND ur.is_active = 1 + AND ae.is_active = 1 + AND (ura.expires_at IS NULL OR ura.expires_at > NOW()) + ORDER BY ae.access_path"; + + $stmt = $pdo->prepare($sql); + $stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT); + $stmt->execute(); + $results = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // Combine permissions for duplicate access elements + $combined_permissions = []; + + foreach ($results as $row) { + $path = $row['access_path']; + + if (!isset($combined_permissions[$path])) { + // First time seeing this access element + $combined_permissions[$path] = [ + 'path' => $row['access_path'], + 'name' => $row['access_name'], + 'group' => $row['access_group'], + 'can_create' => (int)$row['can_create'], + 'can_read' => (int)$row['can_read'], + 'can_update' => (int)$row['can_update'], + 'can_delete' => (int)$row['can_delete'] + ]; + } else { + // Access element already exists, combine permissions (OR operation) + // If any role grants a permission, the user has that permission + $combined_permissions[$path]['can_create'] = max($combined_permissions[$path]['can_create'], (int)$row['can_create']); + $combined_permissions[$path]['can_read'] = max($combined_permissions[$path]['can_read'], (int)$row['can_read']); + $combined_permissions[$path]['can_update'] = max($combined_permissions[$path]['can_update'], (int)$row['can_update']); + $combined_permissions[$path]['can_delete'] = max($combined_permissions[$path]['can_delete'], (int)$row['can_delete']); + } + } + + return $combined_permissions; } \ No newline at end of file diff --git a/assets/images/TSS_invoice_footer.png b/assets/images/TSS_invoice_footer.png new file mode 100644 index 0000000..c6129f5 Binary files /dev/null and b/assets/images/TSS_invoice_footer.png differ diff --git a/assets/images/TSS_invoice_header.png b/assets/images/TSS_invoice_header.png new file mode 100644 index 0000000..aa6ef31 Binary files /dev/null and b/assets/images/TSS_invoice_header.png differ diff --git a/assets/mail/email_template_invoice.php b/assets/mail/email_template_invoice.php new file mode 100644 index 0000000..429edea --- /dev/null +++ b/assets/mail/email_template_invoice.php @@ -0,0 +1,164 @@ + + + + + + ' . htmlspecialchars($lbl_invoice) . ' - Total Safety Solutions + + + + + + +
+ + + + + + + +
+ + +

' . htmlspecialchars($lbl_invoice) . '

+ + + + + + + +
+ Total Safety Solutions B.V.
+ Laarakkerweg 8
+ 5061 JR OISTERWIJK
+ Nederland +
+ contact-details
+ Ralf Adams
+ +31 13 8221480
+ ralfadams@totalsafetysolutions.nl +
+ + + + + + + +
+ Customer
+ '.$invoice_data['customer']['name'].'
+ '.$invoice_data['customer']['street'].'
+ '.$invoice_data['customer']['zip'].', '.$invoice_data['customer']['city'].'
+ '.$invoice_data['customer']['country'].' +
+ + + + + + + +
+ + + + + + + + + + + + + +
Invoice Date:' . htmlspecialchars(date('d-m-Y', strtotime($invoice_date))) . '
Invoice Number:' . htmlspecialchars($order_id) . '
Your Vat Number:' . htmlspecialchars($invoice_data['customer']['vat_number'] ?? '') . '
+
+ + + + + + + + + + + + + +
Reference:Online order
Order number:' . htmlspecialchars($order_id) . '
Payment Methodr:' . (${$payment_method} ?? $invoice_data['header']['payment_method'] ). '
+
+ + + + + + + + + + + + + '; + +foreach ($items as $item) { + $line_total = $item['price'] * $item['quantity']; + $message .= ' + + + + + + '; +} + +$message .= ' +
Item codeDescriptionQuantityPriceTotal
SOFTWARE' . htmlspecialchars($item['name']); + + if ($item['serial_number'] !== 'N/A') { + $message .= '
Serial: ' . htmlspecialchars($item['serial_number']) . ''; + } + if ($item['license_key'] !== 'Pending') { + $message .= '
License: ' . htmlspecialchars($item['license_key']) . ''; + } + + $message .= '
' . htmlspecialchars($item['quantity']) . '€ ' . number_format($item['price'], 2) . '€ ' . number_format($line_total, 2) . '
+ + + + + + + '; + +if ($tax_amount > 0) { + $message .= ' + + + '; +} else { + $message .= ' + + + '; +} + +$message .= ' + + + +
' . htmlspecialchars($lbl_subtotal) . '€ ' . number_format($subtotal, 2) . '
' . htmlspecialchars($lbl_tax) . '€ ' . number_format($tax_amount, 2) . '
VATincluded
' . htmlspecialchars($lbl_total) . '€ ' . number_format($payment_amount, 2) . '
+ +
+
+ +'; \ No newline at end of file diff --git a/assets/mail/pdf_template_invoice.php b/assets/mail/pdf_template_invoice.php new file mode 100644 index 0000000..29aea68 --- /dev/null +++ b/assets/mail/pdf_template_invoice.php @@ -0,0 +1,329 @@ + + + + + + ' . htmlspecialchars($lbl_invoice) . ' - Total Safety Solutions + + + + + + +
' . htmlspecialchars($lbl_invoice) . '
+ +
+
+

Total Safety Solutions B.V.

+

Laarakkerweg 8

+

5061 JR OISTERWIJK

+

Nederland

+
+
+

contact-details

+

Ralf Adams

+

+31 13 8221480

+

ralfadams@totalsafetysolutions.nl

+
+
+ +
+
+

Customer

+

'.$invoice_data['customer']['name'].'

+

'.$invoice_data['customer']['street'].'

+

'.$invoice_data['customer']['zip'].', '.$invoice_data['customer']['city'].'

+

'.$invoice_data['customer']['country'].'

+
+
+ +
+
+
+
Invoice Date
+
: ' . htmlspecialchars(date('d-m-Y', strtotime($invoice_date))) . '
+
+
+
Invoice Number
+
: ' . htmlspecialchars($order_id) . '
+
+
+
Your Vat Number
+
: ' . htmlspecialchars($invoice_data['customer']['vat_number'] ?? '') . '
+
+
+
+
+
Reference
+
: Online order
+
+
+
Order number
+
: ' . htmlspecialchars($order_id) . '
+
+
+
Payment Method
+
: ' . (${$payment_method} ?? $invoice_data['header']['payment_method'] ). '
+
+
+
+ + + + + + + + + + + + '; + + foreach ($items as $item) { + $line_total = $item['price'] * $item['quantity']; + $pdf .= ' + + + + + + '; + } + +$pdf .= ' +
Item codeDescriptionQuantityPriceTotal
SOFTWARE' . htmlspecialchars($item['name']); + + if ($item['serial_number'] !== 'N/A') { + $pdf .= '
Serial: ' . htmlspecialchars($item['serial_number']) . ''; + } + if ($item['license_key'] !== 'Pending') { + $pdf .= '
License: ' . htmlspecialchars($item['license_key']) . ''; + } + + $pdf .= '
' . htmlspecialchars($item['quantity']) . ' € ' . number_format($item['price'], 2) . '€ ' . number_format($line_total, 2) . '
+ +
+
+
' . htmlspecialchars($lbl_subtotal) . '
+
€ ' . number_format($subtotal, 2) . '
+
'; + + if ($tax_amount > 0) { + $pdf .= '
+
' . htmlspecialchars($lbl_tax) . '
+
€ ' . number_format($tax_amount, 2) . '
+
'; + } else { + $pdf .= '
+
VAT
+
included
+
'; + } + + $pdf .= '
+
' . htmlspecialchars($lbl_total) . '
+
€ ' . number_format($payment_amount, 2) . '
+
+
+ + +'; \ No newline at end of file diff --git a/assets/marketing.js b/assets/marketing.js new file mode 100644 index 0000000..240287f --- /dev/null +++ b/assets/marketing.js @@ -0,0 +1,1354 @@ +/** + * Marketing File Management System + * Professional drag-and-drop upload with folder management and tagging + */ + +class MarketingFileManager { + constructor() { + this.currentFolder = ''; + this.selectedFiles = []; + this.uploadQueue = []; + this.viewMode = 'grid'; + this.filters = { + search: '', + tag: '', + fileTypes: [] + }; + this.folders = []; // Store folders data + this.loadRequestId = 0; // Track the latest load request + + // Get permissions from PHP + this.permissions = window.marketingPermissions || { + canCreate: 0, + canUpdate: 0, + canDelete: 0 + }; + + this.init(); + } + + init() { + this.bindEvents(); + this.loadFolders(); + this.loadTags(); + this.loadFiles(); + this.setupDragAndDrop(); + } + + bindEvents() { + // Upload modal + document.getElementById('uploadBtn')?.addEventListener('click', () => { + this.showUploadModal(); + }); + + // Create folder modal + document.getElementById('createFolderBtn')?.addEventListener('click', () => { + this.showFolderModal(); + }); + + // View mode toggle + document.getElementById('gridViewBtn')?.addEventListener('click', () => { + this.setViewMode('grid'); + }); + + document.getElementById('listViewBtn')?.addEventListener('click', () => { + this.setViewMode('list'); + }); + + // Search + document.getElementById('searchInput')?.addEventListener('input', (e) => { + this.filters.search = e.target.value; + this.debounce(this.loadFiles.bind(this), 300)(); + }); + + // Tag filter + document.getElementById('tagFilter')?.addEventListener('change', (e) => { + this.filters.tag = e.target.value; + this.loadFiles(); + }); + + // File type filters + document.querySelectorAll('.file-type-filters input[type="checkbox"]').forEach(checkbox => { + checkbox.addEventListener('change', () => { + this.updateFileTypeFilters(); + }); + }); + + // Modal events + this.bindModalEvents(); + + // Upload events + this.bindUploadEvents(); + } + + bindModalEvents() { + // Close modals + document.querySelectorAll('.modal-close, .modal-cancel').forEach(btn => { + btn.addEventListener('click', (e) => { + this.closeModal(e.target.closest('.modal')); + }); + }); + + // Create folder + document.getElementById('createFolder')?.addEventListener('click', () => { + this.createFolder(); + }); + + // Download file + document.getElementById('downloadFile')?.addEventListener('click', () => { + if (this.selectedFile) { + this.downloadFile(this.selectedFile); + } + }); + + // Delete file + document.getElementById('deleteFile')?.addEventListener('click', () => { + if (this.selectedFile) { + this.deleteFile(this.selectedFile); + } + }); + + // Save edit + document.getElementById('saveEdit')?.addEventListener('click', () => { + this.saveEdit(); + }); + + // Edit folder + document.getElementById('saveEditFolder')?.addEventListener('click', () => { + this.saveEditFolder(); + }); + + // Delete folder + document.getElementById('deleteFolder')?.addEventListener('click', () => { + if (this.selectedFolder) { + this.deleteFolder(this.selectedFolder); + } + }); + } + + bindUploadEvents() { + const fileInput = document.getElementById('fileInput'); + const browseBtn = document.getElementById('browseBtn'); + const startUpload = document.getElementById('startUpload'); + + browseBtn?.addEventListener('click', () => { + fileInput.click(); + }); + + fileInput?.addEventListener('change', (e) => { + this.handleFileSelect(e.target.files); + }); + + startUpload?.addEventListener('click', () => { + this.startUpload(); + }); + } + + setupDragAndDrop() { + const uploadArea = document.getElementById('uploadArea'); + const filesContainer = document.getElementById('filesContainer'); + + if (uploadArea) { + uploadArea.addEventListener('dragover', this.handleDragOver); + uploadArea.addEventListener('drop', (e) => this.handleDrop(e)); + } + + if (filesContainer) { + filesContainer.addEventListener('dragover', this.handleDragOver); + filesContainer.addEventListener('drop', (e) => this.handleDrop(e)); + } + } + + handleDragOver(e) { + e.preventDefault(); + e.stopPropagation(); + e.currentTarget.classList.add('drag-over'); + } + + handleDrop(e) { + e.preventDefault(); + e.stopPropagation(); + e.currentTarget.classList.remove('drag-over'); + + const files = e.dataTransfer.files; + if (files.length > 0) { + this.showUploadModal(); + this.handleFileSelect(files); + } + } + + async loadFolders() { + try { + const response = await fetch('index.php?page=marketing&action=marketing_folders&tree=true', { cache: 'no-store' }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const text = await response.text(); + if (!text || text.trim() === '') { + console.warn('Empty response from folders API'); + this.folders = []; + this.renderFolderTree([]); + this.populateFolderSelects([]); + return; + } + + const data = JSON.parse(text); + + this.folders = data || []; // Store the folders data + // Always render the folder tree (at minimum shows Root) + this.renderFolderTree(this.folders); + this.populateFolderSelects(this.folders); + } catch (error) { + console.error('Error loading folders:', error); + this.folders = []; + // Show at least root folder on error + this.renderFolderTree([]); + this.populateFolderSelects([]); + } + } + + async loadTags() { + try { + const response = await fetch('index.php?page=marketing&action=marketing_tags&used_only=true', { cache: 'no-store' }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const text = await response.text(); + if (!text || text.trim() === '') { + console.warn('Empty response from tags API'); + this.populateTagFilter([]); + return; + } + + const data = JSON.parse(text); + + // Always populate tag filter (at minimum shows "All Tags") + this.populateTagFilter(data || []); + } catch (error) { + console.error('Error loading tags:', error); + // Show empty tag filter on error + this.populateTagFilter([]); + } + } + + async loadFiles() { + const container = document.getElementById('filesContainer'); + const loading = document.getElementById('loadingIndicator'); + const emptyState = document.getElementById('emptyState'); + + // Increment request ID to invalidate previous requests + const requestId = ++this.loadRequestId; + + // Clear container FIRST to prevent showing old files + container.innerHTML = ''; + loading.style.display = 'block'; + emptyState.style.display = 'none'; + + try { + // Add cache busting to prevent browser caching + let url = `index.php?page=marketing&action=marketing_files&limit=50&_t=${Date.now()}`; + + // Only filter by folder if no tag filter is active (tag search is across all folders) + if (!this.filters.tag) { + const folderId = this.currentFolder ? this.currentFolder : 'null'; + url += `&folder_id=${folderId}`; + } + + if (this.filters.search) { + url += `&search=${encodeURIComponent(this.filters.search)}`; + } + + if (this.filters.tag) { + url += `&tag=${encodeURIComponent(this.filters.tag)}`; + } + + if (this.filters.fileTypes.length > 0) { + // API expects individual file_type parameter, so we'll filter client-side for now + } + + const response = await fetch(url, { cache: 'no-store' }); + + // Ignore response if a newer request was made + if (requestId !== this.loadRequestId) { + return; + } + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const text = await response.text(); + + if (!text || text.trim() === '') { + console.warn('Empty response from files API'); + emptyState.style.display = 'block'; + return; + } + + const data = JSON.parse(text); + + if (data && data.length > 0) { + let files = data; + + // Client-side file type filtering + if (this.filters.fileTypes.length > 0) { + files = files.filter(file => + this.filters.fileTypes.includes(file.file_type.toLowerCase()) + ); + } + + if (files.length === 0) { + // No files after filtering, check for subfolders + const subfolders = this.getSubfolders(this.currentFolder); + if (subfolders.length > 0) { + this.renderFolderTiles(subfolders); + } else { + emptyState.style.display = 'block'; + } + } else { + this.renderFiles(files); + } + } else { + // No files, check for subfolders + const subfolders = this.getSubfolders(this.currentFolder); + if (subfolders.length > 0) { + this.renderFolderTiles(subfolders); + } else { + emptyState.style.display = 'block'; + } + } + } catch (error) { + console.error('Error loading files:', error); + this.showToast('Error loading files', 'error'); + } finally { + loading.style.display = 'none'; + } + } + + renderFolderTree(folders, container = null, level = 0) { + if (!container) { + container = document.getElementById('folderTree'); + container.innerHTML = '
Root
'; + + // Add click listener to root folder + const rootFolder = container.querySelector('.folder-item.root'); + if (rootFolder) { + rootFolder.addEventListener('click', () => { + this.selectFolder(''); + }); + } + } + + folders.forEach(folder => { + const folderItem = document.createElement('div'); + folderItem.className = 'folder-item'; + folderItem.setAttribute('data-folder', folder.id); + folderItem.style.marginLeft = `${level * 20}px`; + + const hasChildren = folder.children && folder.children.length > 0; + const expandIcon = hasChildren ? '' : ''; + + // Only show edit button if user has update permission + const editButton = this.permissions.canUpdate === 1 + ? `` + : ''; + + folderItem.innerHTML = ` + ${expandIcon} + + ${this.escapeHtml(folder.folder_name)} + (${folder.file_count}) + ${editButton} + `; + + folderItem.addEventListener('click', (e) => { + // Don't select folder if edit button was clicked + if (e.target.closest('.folder-edit-btn')) { + e.stopPropagation(); + this.editFolder(folder); + } else { + this.selectFolder(folder.id); + } + }); + + container.appendChild(folderItem); + + if (hasChildren) { + this.renderFolderTree(folder.children, container, level + 1); + } + }); + } + + renderFiles(files) { + const container = document.getElementById('filesContainer'); + container.innerHTML = ''; + + files.forEach(file => { + const fileElement = this.createFileElement(file); + container.appendChild(fileElement); + }); + } + + getSubfolders(folderId) { + // Find immediate children of the specified folder + if (!folderId || folderId === '') { + // Root folder - return top-level folders + return this.folders; + } + + // Recursively search for the folder and return its children + const findFolder = (folders, targetId) => { + for (const folder of folders) { + if (folder.id === targetId) { + return folder.children || []; + } + if (folder.children && folder.children.length > 0) { + const found = findFolder(folder.children, targetId); + if (found) return found; + } + } + return []; + }; + + return findFolder(this.folders, folderId); + } + + renderFolderTiles(subfolders) { + const container = document.getElementById('filesContainer'); + container.innerHTML = ''; + + subfolders.forEach(folder => { + const folderElement = this.createFolderTileElement(folder); + container.appendChild(folderElement); + }); + } + + createFolderTileElement(folder) { + const folderElement = document.createElement('div'); + folderElement.className = `folder-tile ${this.viewMode}-item`; + folderElement.setAttribute('data-folder-id', folder.id); + + folderElement.innerHTML = ` +
+ +
+
+
+ ${this.escapeHtml(folder.folder_name)} +
+
+ ${folder.file_count || 0} files +
+
+ `; + + // Click to navigate to folder + folderElement.addEventListener('click', () => { + this.selectFolder(folder.id); + }); + + return folderElement; + } + + createFileElement(file) { + const fileElement = document.createElement('div'); + fileElement.className = `file-item ${this.viewMode}-item`; + fileElement.setAttribute('data-file-id', file.id); + + const thumbnail = this.getThumbnail(file); + const tags = file.tags.map(tag => `${this.escapeHtml(tag)}`).join(''); + + fileElement.innerHTML = ` +
+ ${thumbnail} +
+ + + +
+
+
+
+ ${this.escapeHtml(file.title || file.original_filename)} +
+
+ ${file.file_size_formatted} + .${file.file_type.toUpperCase()} + ${this.formatDate(file.created)} +
+
+ ${tags} +
+
+ `; + + // Bind events + fileElement.querySelector('.preview-btn').addEventListener('click', () => { + this.previewFile(file); + }); + + fileElement.querySelector('.download-btn').addEventListener('click', () => { + this.downloadFile(file); + }); + + fileElement.querySelector('.edit-btn').addEventListener('click', () => { + this.editFile(file); + }); + + // Make tags clickable to filter by tag + fileElement.querySelectorAll('.tag.clickable').forEach(tagElement => { + tagElement.addEventListener('click', (e) => { + e.stopPropagation(); + const tagName = tagElement.getAttribute('data-tag'); + this.filterByTag(tagName); + }); + }); + + fileElement.addEventListener('dblclick', () => { + this.previewFile(file); + }); + + return fileElement; + } + + getThumbnail(file) { + const isImage = ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(file.file_type.toLowerCase()); + + if (isImage && file.thumbnail_path) { + return `${this.escapeHtml(file.title)}`; + } + + // File type icons + const iconMap = { + pdf: 'fa-file-pdf', + doc: 'fa-file-word', + docx: 'fa-file-word', + xls: 'fa-file-excel', + xlsx: 'fa-file-excel', + mp4: 'fa-file-video', + mov: 'fa-file-video', + avi: 'fa-file-video' + }; + + const iconClass = iconMap[file.file_type.toLowerCase()] || 'fa-file'; + + return `
`; + } + + showUploadModal() { + const modal = document.getElementById('uploadModal'); + this.showModal(modal); + this.populateUploadFolders(this.folders); // Use stored folders data + } + + showFolderModal() { + const modal = document.getElementById('folderModal'); + this.showModal(modal); + this.populateParentFolders(this.folders); // Use stored folders data + } + + showModal(modal) { + modal.style.display = 'flex'; + modal.classList.add('show'); + document.body.classList.add('modal-open'); + } + + closeModal(modal) { + modal.classList.remove('show'); + setTimeout(() => { + modal.style.display = 'none'; + document.body.classList.remove('modal-open'); + }, 300); + } + + handleFileSelect(files) { + this.uploadQueue = []; + + Array.from(files).forEach(file => { + this.uploadQueue.push({ + file: file, + progress: 0, + status: 'pending' + }); + }); + + this.renderUploadQueue(); + document.getElementById('startUpload').disabled = this.uploadQueue.length === 0; + } + + renderUploadQueue() { + const container = document.getElementById('uploadQueue'); + container.innerHTML = ''; + + this.uploadQueue.forEach((item, index) => { + const queueItem = document.createElement('div'); + queueItem.className = 'upload-item'; + queueItem.innerHTML = ` +
+
${this.escapeHtml(item.file.name)}
+
${this.formatFileSize(item.file.size)}
+
+
+
+
+
+
${item.status}
+
+ + `; + + queueItem.querySelector('.remove-btn').addEventListener('click', () => { + this.removeFromQueue(index); + }); + + container.appendChild(queueItem); + }); + } + + async startUpload() { + const folder = document.getElementById('uploadFolder').value; + const tags = document.getElementById('uploadTags').value + .split(',') + .map(tag => tag.trim()) + .filter(tag => tag.length > 0); + + for (let i = 0; i < this.uploadQueue.length; i++) { + const item = this.uploadQueue[i]; + await this.uploadFile(item, folder, tags, i); + } + + // Switch to the uploaded folder if different from current + if (folder && folder !== this.currentFolder) { + this.currentFolder = folder; + } + + this.loadFiles(); + this.closeModal(document.getElementById('uploadModal')); + this.showToast('Files uploaded successfully!', 'success'); + } + + async uploadFile(item, folderId, tags, index) { + const formData = new FormData(); + formData.append('file', item.file); + formData.append('folder_id', folderId); + formData.append('tags', JSON.stringify(tags)); + formData.append('title', item.file.name.replace(/\.[^/.]+$/, "")); + + item.status = 'uploading'; + this.updateQueueItem(index, item); + + try { + const response = await fetch('index.php?page=marketing&action=marketing_upload', { + method: 'POST', + body: formData, + onUploadProgress: (progressEvent) => { + item.progress = Math.round((progressEvent.loaded * 100) / progressEvent.total); + this.updateQueueItem(index, item); + } + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const text = await response.text(); + if (!text || text.trim() === '') { + throw new Error('Empty response from upload server'); + } + + const result = JSON.parse(text); + + if (result.success) { + item.status = 'completed'; + item.progress = 100; + } else { + throw new Error(result.error || 'Upload failed'); + } + } catch (error) { + item.status = 'error'; + item.error = error.message; + this.showToast(error.message, 'error'); + } + + this.updateQueueItem(index, item); + } + + async createFolder() { + const folderName = document.getElementById('folderName').value.trim(); + const parentId = document.getElementById('parentFolder').value; + const description = document.getElementById('folderDescription').value.trim(); + + if (!folderName) { + this.showToast('Folder name is required', 'error'); + return; + } + + try { + const formData = new FormData(); + formData.append('folder_name', folderName); + formData.append('parent_id', parentId || ''); + formData.append('description', description); + + const response = await fetch('index.php?page=marketing&action=marketing_folders', { + method: 'POST', + body: formData + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const text = await response.text(); + if (!text || text.trim() === '') { + throw new Error('Empty response from server'); + } + + const data = JSON.parse(text); + + if (data && (data.success || data.rowID)) { + this.closeModal(document.getElementById('folderModal')); + this.loadFolders(); + this.showToast('Folder created successfully!', 'success'); + } else if (data.error) { + throw new Error(data.error); + } else { + throw new Error('Unexpected response format'); + } + } catch (error) { + console.error('Create folder error:', error); + this.showToast(error.message || 'Error creating folder', 'error'); + } + } + + async deleteFile(file) { + if (!confirm(`Are you sure you want to delete "${file.title || file.original_filename}"?`)) { + return; + } + + try { + const formData = new FormData(); + formData.append('file_id', file.id); + + const response = await fetch('index.php?page=marketing&action=marketing_delete', { + method: 'POST', + body: formData + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const text = await response.text(); + if (!text || text.trim() === '') { + throw new Error('Empty response from delete server'); + } + + const data = JSON.parse(text); + + if (data && (data.success || !data.error)) { + this.closeModal(document.getElementById('previewModal')); + this.loadFiles(); + this.showToast('File deleted successfully!', 'success'); + } else if (data.error) { + throw new Error(data.error); + } else { + throw new Error('Unexpected response format'); + } + } catch (error) { + this.showToast(error.message || 'Error deleting file', 'error'); + } + } + + previewFile(file) { + this.selectedFile = file; + const modal = document.getElementById('previewModal'); + const title = document.getElementById('previewTitle'); + const content = document.getElementById('previewContent'); + + title.textContent = file.title || file.original_filename; + + // Generate preview content based on file type + if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(file.file_type.toLowerCase())) { + content.innerHTML = `${this.escapeHtml(file.title)}`; + } else if (file.file_type.toLowerCase() === 'mp4') { + content.innerHTML = ``; + } else { + content.innerHTML = ` +
+ +

${this.escapeHtml(file.title || file.original_filename)}

+

File Type: ${file.file_type.toUpperCase()}

+

Size: ${file.file_size_formatted}

+

Created: ${this.formatDate(file.created)}

+
+ `; + } + + this.showModal(modal); + } + + downloadFile(file) { + const link = document.createElement('a'); + link.href = file.file_path; + link.download = file.original_filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + + // Utility methods + async apiCall(endpoint, params = {}, method = 'GET') { + const url = new URL(`/api.php${endpoint}`, window.location.origin); + + let options = { + method: method, + headers: { + 'Content-Type': 'application/json', + } + }; + + if (method === 'GET') { + Object.keys(params).forEach(key => url.searchParams.append(key, params[key])); + } else { + options.body = JSON.stringify(params); + } + + const response = await fetch(url, options); + return await response.json(); + } + + escapeHtml(text) { + const map = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + return text ? text.replace(/[&<>"']/g, m => map[m]) : ''; + } + + formatDate(dateString) { + return new Date(dateString).toLocaleDateString(); + } + + formatFileSize(bytes) { + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i]; + } + + debounce(func, wait) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + } + + showToast(message, type = 'info') { + // Simple toast implementation + const toast = document.createElement('div'); + toast.className = `toast toast-${type}`; + toast.textContent = message; + + document.body.appendChild(toast); + + setTimeout(() => { + toast.classList.add('show'); + }, 100); + + setTimeout(() => { + toast.classList.remove('show'); + setTimeout(() => document.body.removeChild(toast), 300); + }, 3000); + } + + setViewMode(mode) { + this.viewMode = mode; + const container = document.getElementById('filesContainer'); + + // Update view mode classes + container.className = `files-container ${mode}-view`; + + // Update button states + document.querySelectorAll('.view-btn').forEach(btn => btn.classList.remove('active')); + document.getElementById(`${mode}ViewBtn`).classList.add('active'); + + // Re-render files with new view mode + this.loadFiles(); + } + + selectFolder(folderId) { + // Clear current folder selection and files BEFORE setting new folder + const container = document.getElementById('filesContainer'); + container.innerHTML = ''; + + // Set new current folder + this.currentFolder = folderId; + + // Update UI + this.updateBreadcrumb(); + + // Update active folder in tree + document.querySelectorAll('.folder-item').forEach(item => { + item.classList.remove('active'); + }); + const selectedFolder = document.querySelector(`[data-folder="${folderId}"]`); + if (selectedFolder) { + selectedFolder.classList.add('active'); + } + + // Load files for the new folder + this.loadFiles(); + } + + updateBreadcrumb() { + // Implement breadcrumb navigation + const nav = document.getElementById('breadcrumbNav'); + // This would build breadcrumb based on current folder path + } + + updateFileTypeFilters() { + this.filters.fileTypes = []; + + document.querySelectorAll('.file-type-filters input[type="checkbox"]:checked').forEach(checkbox => { + const types = checkbox.value.split(','); + this.filters.fileTypes.push(...types); + }); + + this.loadFiles(); + } + + filterByTag(tagName) { + // Set the tag filter + this.filters.tag = tagName; + + // Update the dropdown to show the selected tag + const tagSelect = document.getElementById('tagFilter'); + if (tagSelect) { + tagSelect.value = tagName; + } + + // Clear folder selection to search across all folders + this.currentFolder = ''; + + // Update folder tree UI to show root as active + document.querySelectorAll('.folder-item').forEach(item => { + item.classList.remove('active'); + }); + const rootFolder = document.querySelector('.folder-item.root'); + if (rootFolder) { + rootFolder.classList.add('active'); + } + + // Reload files with the tag filter + this.loadFiles(); + } + + populateTagFilter(tags) { + const select = document.getElementById('tagFilter'); + select.innerHTML = ''; + + tags.forEach(tag => { + const option = document.createElement('option'); + option.value = tag.tag_name; + option.textContent = `${tag.tag_name} (${tag.usage_count})`; + select.appendChild(option); + }); + } + + populateFolderSelects(folders) { + this.populateUploadFolders(folders); + this.populateParentFolders(folders); + } + + addFolderOptions(select, folders, level = 0) { + folders.forEach(folder => { + const option = document.createElement('option'); + option.value = folder.id; + option.textContent = ' '.repeat(level) + folder.folder_name; + select.appendChild(option); + + if (folder.children && folder.children.length > 0) { + this.addFolderOptions(select, folder.children, level + 1); + } + }); + } + + populateUploadFolders(folders = []) { + // Populate upload folder select + const select = document.getElementById('uploadFolder'); + if (select) { + select.innerHTML = ''; + this.addFolderOptions(select, folders); + } + } + + populateParentFolders(folders = []) { + // Populate parent folder select + const select = document.getElementById('parentFolder'); + if (select) { + select.innerHTML = ''; + this.addFolderOptions(select, folders); + } + } + + getFileIcon(fileType) { + const iconMap = { + pdf: 'fa-file-pdf', + doc: 'fa-file-word', + docx: 'fa-file-word', + xls: 'fa-file-excel', + xlsx: 'fa-file-excel', + mp4: 'fa-file-video', + mov: 'fa-file-video', + avi: 'fa-file-video' + }; + + return iconMap[fileType.toLowerCase()] || 'fa-file'; + } + + // Edit file functionality + editFile(file) { + this.selectedFile = file; + this.showEditModal(); + this.populateEditModal(file); + } + + showEditModal() { + const modal = document.getElementById('editModal'); + if (modal) { + this.showModal(modal); + } + } + + populateEditModal(file) { + // Populate title + const titleInput = document.getElementById('editTitle'); + if (titleInput) { + titleInput.value = file.title || ''; + } + + // Populate folder select + const folderSelect = document.getElementById('editFolder'); + if (folderSelect) { + folderSelect.innerHTML = ''; + this.addFolderOptions(folderSelect, this.folders); + + // Select current folder + if (file.folder_id) { + folderSelect.value = file.folder_id; + } + } + + // Populate tags + const tagsInput = document.getElementById('editTags'); + if (tagsInput) { + tagsInput.value = file.tags ? file.tags.join(', ') : ''; + } + } + + saveEdit() { + if (!this.selectedFile) return; + + const title = document.getElementById('editTitle').value.trim(); + const folderId = document.getElementById('editFolder').value; + const tags = document.getElementById('editTags').value.trim(); + + // Compare with original values to detect changes + const originalTitle = this.selectedFile.title || ''; + const originalFolderId = this.selectedFile.folder_id || ''; + const originalTags = (this.selectedFile.tags || []).join(', '); + + const hasChanges = title !== originalTitle || + folderId !== originalFolderId || + tags !== originalTags; + + if (!hasChanges) { + this.showToast('No changes detected', 'info'); + this.closeModal(document.getElementById('editModal')); + return; + } + + // Show loading state + const saveBtn = document.getElementById('saveEdit'); + const originalText = saveBtn.innerHTML; + saveBtn.innerHTML = ' Saving...'; + saveBtn.disabled = true; + + // Prepare FormData with only changed fields + const formData = new FormData(); + formData.append('file_id', this.selectedFile.id); + + if (title !== originalTitle) { + formData.append('title', title); + } + if (folderId !== originalFolderId) { + formData.append('folder_id', folderId); + } + if (tags !== originalTags) { + formData.append('tags', tags); + } + + // Send update request + fetch('index.php?page=marketing&action=marketing_update', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + this.showToast('File updated successfully!', 'success'); + // Reload window to reflect changes + setTimeout(() => { + window.location.reload(); + }, 500); + } else { + throw new Error(data.error || data.message || 'Failed to update file'); + } + }) + .catch(error => { + console.error('Update error:', error); + this.showToast('Error updating file: ' + error.message, 'error'); + }) + .finally(() => { + // Restore button state + saveBtn.innerHTML = originalText; + saveBtn.disabled = false; + }); + } + + updateQueueItem(index, item) { + const queueItems = document.querySelectorAll('.upload-item'); + if (queueItems[index]) { + const progressFill = queueItems[index].querySelector('.progress-fill'); + const status = queueItems[index].querySelector('.upload-status'); + + progressFill.style.width = `${item.progress}%`; + status.textContent = item.status; + + if (item.status === 'error') { + queueItems[index].classList.add('error'); + } else if (item.status === 'completed') { + queueItems[index].classList.add('completed'); + } + } + } + + removeFromQueue(index) { + this.uploadQueue.splice(index, 1); + this.renderUploadQueue(); + document.getElementById('startUpload').disabled = this.uploadQueue.length === 0; + } + + // Edit folder functionality + editFolder(folder) { + this.selectedFolder = folder; + this.showEditFolderModal(); + this.populateEditFolderModal(folder); + } + + showEditFolderModal() { + const modal = document.getElementById('editFolderModal'); + if (modal) { + this.showModal(modal); + } + } + + populateEditFolderModal(folder) { + // Populate folder name + const nameInput = document.getElementById('editFolderName'); + if (nameInput) { + nameInput.value = folder.folder_name || ''; + } + + // Populate parent folder select + const parentSelect = document.getElementById('editParentFolder'); + if (parentSelect) { + parentSelect.innerHTML = ''; + this.addFolderOptionsExcluding(parentSelect, this.folders, folder.id); + + // Select current parent + if (folder.parent_id) { + parentSelect.value = folder.parent_id; + } + } + + // Populate description + const descInput = document.getElementById('editFolderDescription'); + if (descInput) { + descInput.value = folder.description || ''; + } + } + + addFolderOptionsExcluding(select, folders, excludeId, level = 0) { + // Add folders but exclude the current folder and its children + folders.forEach(folder => { + if (folder.id !== excludeId && !this.isFolderDescendant(folder, excludeId)) { + const option = document.createElement('option'); + option.value = folder.id; + option.textContent = ' '.repeat(level) + folder.folder_name; + select.appendChild(option); + + if (folder.children && folder.children.length > 0) { + this.addFolderOptionsExcluding(select, folder.children, excludeId, level + 1); + } + } + }); + } + + isFolderDescendant(folder, ancestorId) { + // Check if folder is a descendant of ancestorId + if (folder.id === ancestorId) return true; + if (folder.children) { + for (let child of folder.children) { + if (this.isFolderDescendant(child, ancestorId)) { + return true; + } + } + } + return false; + } + + saveEditFolder() { + if (!this.selectedFolder) return; + + const folderName = document.getElementById('editFolderName').value.trim(); + const parentId = document.getElementById('editParentFolder').value; + const description = document.getElementById('editFolderDescription').value.trim(); + + // Compare with original values + const originalName = this.selectedFolder.folder_name || ''; + const originalParentId = this.selectedFolder.parent_id || ''; + const originalDescription = this.selectedFolder.description || ''; + + const hasChanges = folderName !== originalName || + parentId !== originalParentId || + description !== originalDescription; + + if (!hasChanges) { + this.showToast('No changes detected', 'info'); + this.closeModal(document.getElementById('editFolderModal')); + return; + } + + if (!folderName) { + this.showToast('Folder name is required', 'error'); + return; + } + + // Show loading state + const saveBtn = document.getElementById('saveEditFolder'); + const originalText = saveBtn.innerHTML; + saveBtn.innerHTML = ' Saving...'; + saveBtn.disabled = true; + + // Prepare FormData + const formData = new FormData(); + formData.append('id', this.selectedFolder.id); + + if (folderName !== originalName) { + formData.append('folder_name', folderName); + } + if (parentId !== originalParentId) { + formData.append('parent_id', parentId); + } + if (description !== originalDescription) { + formData.append('description', description); + } + + // Send update request + fetch('index.php?page=marketing&action=marketing_folders', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + this.showToast('Folder updated successfully!', 'success'); + // Reload window to reflect changes + setTimeout(() => { + window.location.reload(); + }, 500); + } else { + throw new Error(data.error || data.message || 'Failed to update folder'); + } + }) + .catch(error => { + console.error('Update folder error:', error); + this.showToast('Error updating folder: ' + error.message, 'error'); + }) + .finally(() => { + // Restore button state + saveBtn.innerHTML = originalText; + saveBtn.disabled = false; + }); + } + + async deleteFolder(folder) { + if (!confirm(`Are you sure you want to delete the folder "${folder.folder_name}"? This action cannot be undone.`)) { + return; + } + + try { + const formData = new FormData(); + formData.append('id', folder.id); + formData.append('delete', 'true'); + + const response = await fetch('index.php?page=marketing&action=marketing_folders', { + method: 'POST', + body: formData + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const text = await response.text(); + if (!text || text.trim() === '') { + throw new Error('Empty response from server'); + } + + const data = JSON.parse(text); + + if (data && (data.success || !data.error)) { + this.closeModal(document.getElementById('editFolderModal')); + this.showToast('Folder deleted successfully!', 'success'); + // Reload window to reflect changes + setTimeout(() => { + window.location.reload(); + }, 500); + } else if (data.error) { + throw new Error(data.error); + } else { + throw new Error('Unexpected response format'); + } + } catch (error) { + this.showToast(error.message || 'Error deleting folder', 'error'); + } + } +} + +// Initialize when DOM is ready +document.addEventListener('DOMContentLoaded', () => { + window.marketingManager = new MarketingFileManager(); +}); \ No newline at end of file diff --git a/assets/mollie/.DS_Store b/assets/mollie/.DS_Store deleted file mode 100644 index 9c0e967..0000000 Binary files a/assets/mollie/.DS_Store and /dev/null differ diff --git a/assets/mollie/src/.DS_Store b/assets/mollie/src/.DS_Store deleted file mode 100644 index c6ec583..0000000 Binary files a/assets/mollie/src/.DS_Store and /dev/null differ diff --git a/assets/scripts.js b/assets/scripts.js index be9dad7..6a2744d 100644 --- a/assets/scripts.js +++ b/assets/scripts.js @@ -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."); diff --git a/assets/softwaretool.js b/assets/softwaretool.js index 0194de5..a8855c8 100644 --- a/assets/softwaretool.js +++ b/assets/softwaretool.js @@ -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 ``; + } + + // Sort countries alphabetically + const sortedCountries = Object.values(COUNTRIES).sort((a, b) => { + return a.country.localeCompare(b.country); + }); + + let options = ''; + sortedCountries.forEach(data => { + const selected = (selectedCountry === data.country) ? 'selected' : ''; + options += ``; + }); + + 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 = ` + +
+ Browser Not Supported +

+ Please use Chrome, Edge, or Opera to access device connectivity features. +

+
+ `; + + 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,92 +749,328 @@ function displaySoftwareOptions(options) { margin-top: auto; `; - const priceText = document.createElement("div"); - priceText.style.cssText = ` - font-size: ${isCurrent ? '18px' : '28px'}; - font-weight: ${isCurrent ? '600' : '800'}; - color: ${isCurrent ? '#6c757d' : (isFree ? '#04AA6D' : '#FF6B35')}; - margin-bottom: 15px; - text-align: center; - letter-spacing: 0.5px; - `; + // 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; + `; - if (isCurrent) { - priceText.innerHTML = ' INSTALLED'; + // Contact dealer message + const dealerMessage = document.createElement("div"); + dealerMessage.style.cssText = ` + color: white; + font-size: 14px; + font-weight: 600; + margin-bottom: 12px; + `; + dealerMessage.innerHTML = '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 += `
${dealerInfo.name}
`; + } + if (dealerInfo.address || dealerInfo.city || dealerInfo.postalcode || dealerInfo.country) { + dealerHtml += `
`; + if (dealerInfo.address) { + dealerHtml += `
${dealerInfo.address}
`; + } + if (dealerInfo.postalcode || dealerInfo.city) { + dealerHtml += `
${[dealerInfo.postalcode, dealerInfo.city].filter(Boolean).join(' ')}
`; + } + if (dealerInfo.country) { + dealerHtml += `
${dealerInfo.country}
`; + } + dealerHtml += `
`; + } + if (dealerInfo.email) { + dealerHtml += `
${dealerInfo.email}
`; + } + if (dealerInfo.phone) { + dealerHtml += `
${dealerInfo.phone}
`; + } + + dealerDetails.innerHTML = dealerHtml; + dealerSection.appendChild(dealerDetails); + priceSection.appendChild(dealerSection); } else { - priceText.innerHTML = isFree - ? 'Free' - : `${option.currency || "€"} ${price.toFixed(2)}`; + // Standard price display for non-dealer customers + const priceText = document.createElement("div"); + priceText.style.cssText = ` + font-size: ${isCurrent ? '18px' : '28px'}; + font-weight: ${isCurrent ? '600' : '800'}; + color: ${isCurrent ? '#6c757d' : (isFree ? '#04AA6D' : '#FF6B35')}; + margin-bottom: 15px; + text-align: center; + letter-spacing: 0.5px; + `; + + if (isCurrent) { + priceText.innerHTML = ' INSTALLED'; + } else { + priceText.innerHTML = isFree + ? 'Free' + : `${option.currency || "€"} ${price.toFixed(2)} (excl. VAT)`; + } + + priceSection.appendChild(priceText); + + // Action button with gradient for paid + const actionBtn = document.createElement("button"); + actionBtn.className = "btn"; + actionBtn.style.cssText = ` + width: 100%; + background: ${isCurrent ? '#6c757d' : (isFree ? 'linear-gradient(135deg, #04AA6D 0%, #038f5a 100%)' : 'linear-gradient(135deg, #FF6B35 0%, #FF4500 100%)')}; + color: white; + border: none; + cursor: ${isCurrent ? 'not-allowed' : 'pointer'}; + transition: all 0.3s ease; + opacity: ${isCurrent ? '0.5' : '1'}; + box-shadow: ${isCurrent ? 'none' : '0 4px 12px rgba(0,0,0,0.15)'}; + letter-spacing: 0.5px; + text-transform: uppercase; + `; + + if (isCurrent) { + actionBtn.innerHTML = ' Currently Installed'; + actionBtn.disabled = true; + } else if (isFree) { + actionBtn.innerHTML = ''; + actionBtn.onclick = () => selectUpgrade(option); + actionBtn.onmouseenter = () => { + actionBtn.style.background = 'linear-gradient(135deg, #038f5a 0%, #026b43 100%)'; + actionBtn.style.transform = 'translateY(-2px)'; + actionBtn.style.boxShadow = '0 6px 16px rgba(0,0,0,0.2)'; + }; + actionBtn.onmouseleave = () => { + actionBtn.style.background = 'linear-gradient(135deg, #04AA6D 0%, #038f5a 100%)'; + actionBtn.style.transform = 'translateY(0)'; + actionBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'; + }; + } else { + actionBtn.innerHTML = ''; + actionBtn.onclick = () => selectUpgrade(option); + actionBtn.onmouseenter = () => { + actionBtn.style.background = 'linear-gradient(135deg, #FF4500 0%, #CC3700 100%)'; + actionBtn.style.transform = 'translateY(-2px)'; + actionBtn.style.boxShadow = '0 6px 16px rgba(255,107,53,0.4)'; + }; + actionBtn.onmouseleave = () => { + actionBtn.style.background = 'linear-gradient(135deg, #FF6B35 0%, #FF4500 100%)'; + actionBtn.style.transform = 'translateY(0)'; + actionBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'; + }; + } + + priceSection.appendChild(actionBtn); } - priceSection.appendChild(priceText); - - // Action button with gradient for paid - const actionBtn = document.createElement("button"); - actionBtn.className = "btn"; - actionBtn.style.cssText = ` - width: 100%; - background: ${isCurrent ? '#6c757d' : (isFree ? 'linear-gradient(135deg, #04AA6D 0%, #038f5a 100%)' : 'linear-gradient(135deg, #FF6B35 0%, #FF4500 100%)')}; - color: white; - border: none; - cursor: ${isCurrent ? 'not-allowed' : 'pointer'}; - transition: all 0.3s ease; - opacity: ${isCurrent ? '0.5' : '1'}; - box-shadow: ${isCurrent ? 'none' : '0 4px 12px rgba(0,0,0,0.15)'}; - letter-spacing: 0.5px; - text-transform: uppercase; - `; - - if (isCurrent) { - actionBtn.innerHTML = ' Currently Installed'; - actionBtn.disabled = true; - } else if (isFree) { - actionBtn.innerHTML = ''; - actionBtn.onclick = () => selectUpgrade(option); - actionBtn.onmouseenter = () => { - actionBtn.style.background = 'linear-gradient(135deg, #038f5a 0%, #026b43 100%)'; - actionBtn.style.transform = 'translateY(-2px)'; - actionBtn.style.boxShadow = '0 6px 16px rgba(0,0,0,0.2)'; - }; - actionBtn.onmouseleave = () => { - actionBtn.style.background = 'linear-gradient(135deg, #04AA6D 0%, #038f5a 100%)'; - actionBtn.style.transform = 'translateY(0)'; - actionBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'; - }; - } else { - actionBtn.innerHTML = ''; - actionBtn.onclick = () => selectUpgrade(option); - actionBtn.onmouseenter = () => { - actionBtn.style.background = 'linear-gradient(135deg, #FF4500 0%, #CC3700 100%)'; - actionBtn.style.transform = 'translateY(-2px)'; - actionBtn.style.boxShadow = '0 6px 16px rgba(255,107,53,0.4)'; - }; - actionBtn.onmouseleave = () => { - actionBtn.style.background = 'linear-gradient(135deg, #FF6B35 0%, #FF4500 100%)'; - actionBtn.style.transform = 'translateY(0)'; - actionBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'; - }; - } - - 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 = ` +
+

${typeof TRANS_USER_INFO_REQUIRED !== 'undefined' ? TRANS_USER_INFO_REQUIRED : 'User Information Required'}

+

${typeof TRANS_USER_INFO_DESCRIPTION !== 'undefined' ? TRANS_USER_INFO_DESCRIPTION : 'Please provide your information to continue with software updates'}

+
+
+
+
+ + +
+ +
+ + +
+ +
+ + + +
+ + +
+
+ +
+ + +
+
+
+ `; + + 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 - showFreeInstallModal(option); + // 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) { @@ -693,22 +1116,24 @@ function showFreeInstallModal(option) {
- +
- +
- + - +
- - + +
@@ -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 '
    ' + + lines.map(line => `
  • ${line}
  • `).join('') + + '
'; + }; + // Create modal overlay const modal = document.createElement("div"); modal.id = "paymentModal"; @@ -823,40 +1259,58 @@ function showPaymentModal(option) {

${option.name || "Software Update"}

Version: ${option.version || "N/A"}

-

${option.description || ""}

-
- ${currency} ${price.toFixed(2)} +
${formatDescription(option.description)}
+
+
+ Price (excl. VAT): + ${currency} ${price.toFixed(2)} +
+
+ VAT: + - +
+
+ Total: + ${currency} ${price.toFixed(2)} +
- +
- +
- + - +
- - + +
+
+ + +
+
@@ -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 = ` + VAT (${taxRate}%): + ${currency} ${taxAmount.toFixed(2)} + `; + } else { + taxDisplay.innerHTML = ` + VAT: + - + `; + } + + 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.disabled = false; + 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 = ` + +

Installing Software...

+

Please keep your device connected and do not close this page

+ `; + + // Insert status before the hidden upload section + document.getElementById("uploadSection").parentNode.insertBefore(installStatus, document.getElementById("uploadSection")); - progressBar("60", "Ready to install, starting upload...", "#04AA6D"); - uploadBtn.click(); + progressBar("60", "Starting automatic installation...", "#04AA6D"); + + // Enable the upload button and automatically click it + setTimeout(() => { + uploadBtn.disabled = false; + + // Start monitoring for completion + if (typeof startUploadMonitoring === 'function') { + startUploadMonitoring(); + } + + uploadBtn.click(); + }, 1000); } } catch (error) { diff --git a/buildtool.php b/buildtool.php index 553a35c..a34bd1d 100644 --- a/buildtool.php +++ b/buildtool.php @@ -1,7 +1,7 @@
'; $view .= '
'; -$view .= '
+ +//Retrieve questions and awnsers +if ($version == 0){ + $view .= '
'.$cartest_questions_text.'
'; - - //Retrieve questions and awnsers - - if ($version == 0){ - foreach ($cartest_questions as $key => $value){ - $view .= ' - - - '; - } - } else { - //CREATE OUTPUT BASED ON ARRAY - $view .= '
'; - foreach($arrayQuestions_cartest as $group){ - if ($group['Group_sequence'] == 1){ - $view .= ''.$group['Group'].''; - } else { - $view .= ''.$group['Group'].''; - } - } - $view .= '
'; + foreach ($cartest_questions as $key => $value){ + $view .= ' + + + '; + } + $view .= ' +
'.$key.''.((!empty($value)|| $value !='')?$value:$not_specified).'
'.$key.''.((!empty($value)|| $value !='')?$value:$not_specified).'
+
+
+ '; +} else { + //CREATE OUTPUT BASED ON ARRAY foreach($arrayQuestions_cartest as $group){ if ($group['Group_sequence'] == 1){ + $view .= ''; $view .= '
'; } else { + $view .= ''; $view .= '
'; } @@ -197,14 +198,8 @@ $view .= '
} $view .= '
'; - } - - } - $view .= ' - -
-
- '; + } +} if (isset($cartest_datapoints) && $cartest_datapoints !=''){ $view .= '
diff --git a/cartest_manage.php b/cartest_manage.php index 6a7c375..75bfa2c 100644 --- a/cartest_manage.php +++ b/cartest_manage.php @@ -1,7 +1,7 @@ [ 'CarVIN' => '', 'year' => '', - 'NameTester'=> $_SESSION['username'], + 'NameTester'=> $_SESSION['authorization']['clientID'], 'SN' =>'', 'HW' =>'', 'FW' =>'' @@ -181,7 +181,7 @@ if ($delete_allowed === 1){ $view .= ''; } if ($update_allowed === 1){ - $view .= ''; + $view .= ''; } $view .= '
'; @@ -242,17 +242,6 @@ $view .= '
'; //BUILD TO INPUT FORM BASED ON ARRAY // ------------------------------ -$view .= '
'; -foreach($arrayQuestions_cartest as $group){ - - if ($group['Group_sequence'] == 1){ - $view .= ''.$group['Group'].''; - } else { - $view .= ''.$group['Group'].''; - } -} -$view .= '
'; - foreach($arrayQuestions_cartest as $group){ if ($group['Group_sequence'] == 1){ diff --git a/cartests.php b/cartests.php index 5a49aca..c73fc58 100644 --- a/cartests.php +++ b/cartests.php @@ -1,7 +1,7 @@ '.$general_filters_clear.''; //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 .= ' Download '; diff --git a/catalog.php b/catalog.php index e53c7b1..bfaf6dd 100644 --- a/catalog.php +++ b/catalog.php @@ -1,7 +1,7 @@ '', @@ -104,7 +104,7 @@ if ($delete_allowed === 1){ $view .= ''; } if ($update_allowed === 1){ - $view .= ''; + $view .= ''; } $view .= '
'; diff --git a/communication.php b/communication.php index 9fb97e3..452ac13 100644 --- a/communication.php +++ b/communication.php @@ -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 .= ''; } if ($update_allowed === 1){ - $view .= ''; + $view .= ''; } $view .= '
'; diff --git a/communication_send.php b/communication_send.php index c9823b2..c5a6391 100644 --- a/communication_send.php +++ b/communication_send.php @@ -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'; diff --git a/communications.php b/communications.php index b1c6417..67bee9d 100644 --- a/communications.php +++ b/communications.php @@ -1,7 +1,7 @@ '.$button_create_communication.''; } -if (isAllowed('communication_send',$_SESSION['profile'],$_SESSION['permission'],'U') === 1){ +if (isAllowed('communication_send',$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U') === 1){ $view .= ''.$button_create_communication_send.''; } diff --git a/contract.php b/contract.php index 0edcb8a..6c4bfa9 100644 --- a/contract.php +++ b/contract.php @@ -1,7 +1,7 @@ ←':''; //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 .= '
'; //Check for assigned tools foreach ($servicetools as $tools){ - $view .= ''.(($view_equipment == 1)? ''.$tools.'':$tools).''; + $view .= ''.(($view_equipment == 1)? ''.$tools.' ':$tools).''; } $view .= ' diff --git a/contract_manage.php b/contract_manage.php index f2a6042..7c46e33 100644 --- a/contract_manage.php +++ b/contract_manage.php @@ -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 .= ''; } if ($update_allowed === 1){ - $view .= ''; + $view .= ''; } $view .= '
'; @@ -248,10 +248,10 @@ $view .=' $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 .= '
diff --git a/contracts.php b/contracts.php index e18eac2..f7dba17 100644 --- a/contracts.php +++ b/contracts.php @@ -1,7 +1,7 @@ ←':''; //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(); diff --git a/cronjob.php b/cronjob.php index 4902c3b..d9ed9be 100644 --- a/cronjob.php +++ b/cronjob.php @@ -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 diff --git a/custom/bewellwell/settings/bewellwell_settings.php b/custom/bewellwell/settings/bewellwell_settings.php index 8cd76de..1780d98 100644 --- a/custom/bewellwell/settings/bewellwell_settings.php +++ b/custom/bewellwell/settings/bewellwell_settings.php @@ -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 diff --git a/custom/bewellwell/settings/settingsmenu.php b/custom/bewellwell/settings/settingsmenu.php index 954eb5d..74edb4d 100644 --- a/custom/bewellwell/settings/settingsmenu.php +++ b/custom/bewellwell/settings/settingsmenu.php @@ -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 diff --git a/custom/morvalwatches/mail/email_template_new.php b/custom/morvalwatches/mail/email_template_new.php new file mode 100644 index 0000000..4760382 --- /dev/null +++ b/custom/morvalwatches/mail/email_template_new.php @@ -0,0 +1,102 @@ + + + + + + ' . $subject . ' + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ '.site_name.' +
+ ' . $newuser_header . ', +
+
+ '.$newuser_text.' '.$newuser_credential_text_1.''.$post_content['username'].' +
+
+ '.$newuser_credential_text_2.' +
+ + + + + +
+ Reset Password +
+
+ ' . $newuser_closure . ' +
+
+ Kind regards, +
+
+ Service team +
+
+
+
+ + +'; \ No newline at end of file diff --git a/custom/morvalwatches/mail/email_template_reset.php b/custom/morvalwatches/mail/email_template_reset.php new file mode 100644 index 0000000..f9fafb4 --- /dev/null +++ b/custom/morvalwatches/mail/email_template_reset.php @@ -0,0 +1,99 @@ + + + + + + ' . $subject . ' + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ '.site_title.' +
+ ' . $changeuser_header . ', +
+
+ '.$changeuser_text.' +
+
+ '.$changeuser_credential_text_1 .' +
+ + + + + +
+ Reset Password +
+
+ ' . $changeuser_closure . ' +
+
+ Kind regards, +
+
+ Service team +
+
+
+
+ + +'; \ No newline at end of file diff --git a/custom/morvalwatches/settings/morvalwatches_config.php b/custom/morvalwatches/settings/morvalwatches_config.php new file mode 100644 index 0000000..8ab12a2 --- /dev/null +++ b/custom/morvalwatches/settings/morvalwatches_config.php @@ -0,0 +1,55 @@ +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'; \ No newline at end of file diff --git a/custom/morvalwatches/settings/settingsmenu.php b/custom/morvalwatches/settings/settingsmenu.php new file mode 100644 index 0000000..b7ce39b --- /dev/null +++ b/custom/morvalwatches/settings/settingsmenu.php @@ -0,0 +1,365 @@ + [ + "main_menu" => [ + "url" => "dashboard", + "selected" => "dashboard", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_dashboard" + ] + ], + "sales" => [ + "main_menu" => [ + "url" => "contracts", + "selected" => "contracts", + "icon" => "fa-solid fa-bars", + "name" => "menu_sales" + ], + "accounts" => [ + "url" => "accounts", + "selected" => "accounts", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_sales_accounts" + ], + "catalog" => [ + "url" => "catalog", + "selected" => "catalog", + "icon" => "fa-solid fa-photo-film", + "name" => "menu_catalog" + ], + "contracts" => [ + "url" => "contracts", + "selected" => "contracts", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_sales_contracts" + ], + "orders" => [ + "url" => "orders", + "selected" => "orders", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_sales_orders" + ], + "identity" => [ + "url" => "identity", + "selected" => "identity", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_identity" + ] + ], + "dealers" => [ + "main_menu" => [ + "url" => "dealers", + "selected" => "dealers", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_dealers" + ] + ], + "buildtool" => [ + "main_menu" => [ + "url" => "buildtool", + "selected" => "buildtool", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_build" + ] + ], + "cartests" => [ + "main_menu" => [ + "url" => "cartests", + "selected" => "cartests", + "icon" => "fa-solid fa-car", + "name" => "menu_cartest" + ] + ], + "marketing" => [ + "main_menu" => [ + "url" => "marketing&product_group=Emergency_Plug&product_content=Images", + "selected" => "marketing", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_marketing" + ] + ], + "equipments" => [ + "main_menu" => [ + "url" => "equipments", + "selected" => "assets", + "icon" => "fa-solid fa-database", + "name" => "menu_assets" + ], + "equipments" =>[ + "url" => "equipments", + "selected" => "assets", + "icon" => "fa-solid fa-database", + "name" => "menu_assets" + ], + "servicereports" => [ + "url" => "servicereports", + "selected" => "servicereports", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_service_reports" + ], + "rmas" => [ + "url" => "rmas", + "selected" => "rmas", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_rmas" + ], + "histories" => [ + "url" => "histories", + "selected" => "histories", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_history" + ], + "firmwaretool" => [ + "url" => "firmwaretool", + "selected" => "firmwaretool", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_firmwaretool" + ] , + "equipments_mass_update" => [ + "url" => "equipments_mass_update", + "selected" => "equipments_mass_update", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_equipments_mass_update" + ] + ], + "products" => [ + "main_menu" => [ + "url" => "products&status=1", + "selected" => "products", + "icon" => "fas fa-box-open", + "name" => "menu_products" + ], + "products" => [ + "url" => "products&status=1", + "selected" => "products", + "icon" => "fas fa-box-open", + "name" => "menu_products" + ], + "products_software" => [ + "url" => "products_software_versions", + "selected" => "products_software_versions", + "icon" => "fas fa-box-open", + "name" => "menu_products_software_versions" + ], + "products_attributes" => [ + "url" => "products_attributes", + "selected" => "products_attributes", + "icon" => "fas fa-box-open", + "name" => "menu_products_attributes" + ], + "pricelists" => [ + "url" => "pricelists", + "selected" => "pricelists", + "icon" => "fa-solid fa-coins", + "name" => "menu_pricelists" + ] + ], + "reporting" => [ + "main_menu" => [ + "url" => "report_build", + "selected" => "report_build", + "icon" => "fa-solid fa-magnifying-glass-chart", + "name" => "menu_report_main" + ], + "report_build" => [ + "url" => "report_build", + "selected" => "report_build", + "icon" => "fa-solid fa-magnifying-glass-chart", + "name" => "menu_report_build" + ], + "report_contracts_billing" => [ + "url" => "report_contracts_billing", + "selected" => "report_contracts_billing", + "icon" => "fa-solid fa-magnifying-glass-chart", + "name" => "menu_report_contracts_billing" + ], + "report_healthindex" => [ + "url" => "report_healthindex", + "selected" => "report_healthindex", + "icon" => "fa-solid fa-magnifying-glass-chart", + "name" => "menu_report_healthindex" + ], + "report_usage" => [ + "url" => "report_usage", + "selected" => "report_usage", + "icon" => "fa-solid fa-magnifying-glass-chart", + "name" => "menu_report_usage" + ] + ], + "admin" =>[ + "main_menu" => [ + "url" => "partners", + "selected" => "partners", + "icon" => "fa-solid fa-bars", + "name" => "menu_admin" + ], + "partners" => [ + "url" => "partners", + "selected" => "partners", + "icon" => "fa-solid fa-bars", + "name" => "menu_admin_partners" + ], + "users" => [ + "url" => "users", + "selected" => "users", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_admin_users" + ], + "communications" => [ + "url" => "communications", + "selected" => "communications", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_admin_communications" + ], + "media" => [ + "url" => "media", + "selected" => "media", + "icon" => "fa-solid fa-photo-film", + "name" => "menu_media" + ], + "categories" => [ + "url" => "categories", + "selected" => "categories", + "icon" => "fa-solid fa-photo-film", + "name" => "menu_categories" + ], + "discounts" => [ + "url" => "discounts", + "selected" => "discounts", + "icon" => "fa-solid fa-photo-film", + "name" => "menu_discounts" + ], + "shipping" => [ + "url" => "shipping", + "selected" => "shipping", + "icon" => "fa-solid fa-truck-fast", + "name" => "menu_shipping" + ] + ], + "settings" => [ + "main_menu" => [ + "url" => "settings", + "selected" => "settings", + "icon" => "fas fa-tools", + "name" => "menu_settings" + ], + "config" => [ + "url" => "settings", + "selected" => "settings", + "icon" => "fas fa-tools", + "name" => "menu_config" + ], + "translations" => [ + "url" => "translations", + "selected" => "translations", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_translations" + ], + "uploader" => [ + "url" => "uploader", + "selected" => "uploader", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_uploader" + ], + "logfile" => [ + "url" => "logfile", + "selected" => "logfile", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_logfile" + ], + "maintenance" => [ + "url" => "maintenance", + "selected" => "maintenance", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_maintenance" + ], + "profiles" => [ + "url" => "profiles", + "selected" => "profiles", + "icon" => "fas fa-tachometer-alt", + "name" => "menu_profiles" + ] + ] +]; + +$routes = array( + '/' => 'equipments.php', + 'equipments' => 'equipments.php', + 'products' => 'products.php', + 'logout' => 'logout.php' +); + +//------------------------------------------ +// Paging +//------------------------------------------ +$page_rows_equipment = 25; //list Equipment +$page_rows_history = 15; //list History +$page_rows_products = 25;//list producst +$page_rows_users = 15;//list users +$page_rows_partners = 15;//list partners +$page_rows_communication = 25; //list communications +$page_rows_accounts = 25 ;// list accounts +$page_rows_contracts = 25 ;// list contracts +$page_rows_cartest = 25 ;// list contracts +$page_rows_equipment_servicereporst = 5 ;// Number of service reports on equipment +$page_rows_changelog = 50 ;// Number of changelogs returned +$page_rows_rma = 25; // list RMA +$page_rows_translations = 50; //list translation variables +$page_rows_products_attributes = 50; //list product attributes +$page_rows_media = 25; // list media +$page_rows_pricelists = 50;//pricelists +$page_rows_categories = 25;//categories +$page_rows_discounts = 25;//discounts +$page_rows_shipping = 25;//discounts +$page_rows_transactions = 25; //transactions +$page_rows_invoice = 25; //invoices +$page_rows_dealers = 25; //dealers +$page_rows_software_versions = 50; //software versions + +//------------------------------------------ +// Languages supported +//------------------------------------------ +$supportedLanguages = ['US', 'NL', 'DE', 'ES','PT']; + +//------------------------------------------ +// Pricing +//------------------------------------------ +$supportedCurrencies = ["0" =>"euro", "1"=>"dollar"]; + +$supportedModifiers = ["0" =>"subtract", "1"=>"add"]; + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// All individual views and APIs - Profile ++++++++++++++ +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ +$all_profiles = [ + "build", + "commerce", + "distribution", + "firmware", + "garage", + "interface", + "service", + "other" +]; + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// Marketing +++++++++++++++++++++++++++++++++++++ +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +$main_marketing_dir = './marketing/'; + +$marketing_structure = array( +"Emergency_Plug" => array( + "Documents", + "Images", + "Video" + ) + ); \ No newline at end of file diff --git a/custom/morvalwatches/settings/settingsprofiles.php b/custom/morvalwatches/settings/settingsprofiles.php new file mode 100644 index 0000000..2999088 --- /dev/null +++ b/custom/morvalwatches/settings/settingsprofiles.php @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/custom/morvalwatches/style/SoVeLiTi.png b/custom/morvalwatches/style/SoVeLiTi.png new file mode 100644 index 0000000..5572acc Binary files /dev/null and b/custom/morvalwatches/style/SoVeLiTi.png differ diff --git a/custom/morvalwatches/style/VeLiTi-Logo2.png b/custom/morvalwatches/style/VeLiTi-Logo2.png new file mode 100755 index 0000000..ea68b1b Binary files /dev/null and b/custom/morvalwatches/style/VeLiTi-Logo2.png differ diff --git a/custom/morvalwatches/style/VeLiTi.png b/custom/morvalwatches/style/VeLiTi.png new file mode 100644 index 0000000..16e9476 Binary files /dev/null and b/custom/morvalwatches/style/VeLiTi.png differ diff --git a/custom/morvalwatches/style/morvalwatches_login.css b/custom/morvalwatches/style/morvalwatches_login.css new file mode 100644 index 0000000..1c0c2fb --- /dev/null +++ b/custom/morvalwatches/style/morvalwatches_login.css @@ -0,0 +1,231 @@ +:root { + --color-white: #FFFFFF; + --color-light-blue: #344fd5c2; + --color-blue: #0b1054; + --color-red: #a75151; + --text-color: #333333; + --error-background: #f3c3c3; +} + + +* { + padding: 0; + margin: 0; + box-sizing: border-box; + font-family: "Open Sans", Helvetica, sans-serif; + accent-color: var(--color-blue); +} + +body { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + background-color: var(--color-white); + padding: 20px; +} + +.login-container { + display: flex; + width: 100%; + max-width: 1200px; + height: calc(100vh - 40px); + background-color: var(--color-white); + border-radius: 16px; + box-shadow: 0 10px 25px rgba(0,0,0,0.1); + overflow: hidden; +} + +.login-form { + width: 45%; + padding: 40px; + display: flex; + flex-direction: column; + justify-content: center; + position: relative; +} + +.logo { + position: absolute; + top: 20px; + left: 20px; + background-image: url(VeLiTi-Logo2.png); + background-repeat: no-repeat; + opacity: inherit; + width: 85px; + height: 120px; + margin: 0 auto; + -webkit-filter: drop-shadow(5px 5px 5px #222); + filter: drop-shadow(5px 5px 5px #222); +} + +.login-visual { + width: 55%; + position: relative; + overflow: hidden; + background-image: url(veliti_intro.png); + background-position: center center; + background-size: 100% 100%; + background-color: var(--color-light-blue); + background-repeat: no-repeat; +} + +.login-visual img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.register-link { + position: absolute; + top: 20px; + right: 20px; + text-decoration: none; + color: var(--text-color); +} + +.header { + margin-bottom: 30px; +} + +.header h1 { + font-size: 24px; + margin-bottom: 10px; +} + +.header p { + color: #666; +} + +.input-group { + margin-bottom: 15px; + position: relative; +} + +.input-group input { + width: 100%; + padding: 10px; + border: 1px solid #ddd; + border-radius: 4px; +} + +.input-group input[type="email"] { + padding-left: 40px; + background: url('data:image/svg+xml;utf8,') no-repeat left 10px center; +} + +.input-group input[type="password"] { + padding-left: 40px; + background: url('data:image/svg+xml;utf8,') no-repeat left 10px center; +} + +.forgot-password { + color: var(--text-color); + text-decoration: none; + text-align: right; + margin-top: 5px; + float: right; + font-size: 12px; +} + +.remember-me { + display: flex; + align-items: center; + margin-bottom: 20px; +} + +.remember-me input { + margin-right: 10px; +} + +.maintenance { + padding: 5px; +} + +.message p { + margin-top: 5px; + background-color: var(--error-background); + border-left: 4px solid var(--color-red); + color: var(--color-red); + padding: 5px; + border-radius: 4px; + text-align: center; +} + +.login-btn { + width: 100%; + padding: 12px; + background-color: var(--color-light-blue); + color: var(--color-white); + border: none; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.login-btn:hover { + background-color: var(--color-blue); +} + +.trademark { + position: absolute; + bottom: 20px; + left: 20px; + color: var(--text-color); + font-size: 12px; +} + +.language-selector { + position: absolute; + bottom: 20px; + right: 20px; + display: flex; + align-items: center; + color: var(--text-color); +} + +.language-selector select { + margin-left: 10px; + border: none; + background: transparent; +} + +/* Responsive Design */ +@media screen and (max-width: 1024px) { + .login-container { + flex-direction: column; + height: auto; + max-width: 500px; + } + + .login-form, .login-visual { + width: 100%; + height: auto; + } + + .login-visual { + height: 300px; + } + + .logo { + position: static; + margin: 20px auto; + margin-bottom: 0; + } + + .trademark, .language-selector { + position: static; + text-align: center; + margin: 20px 0; + } +} + +@media screen and (max-width: 480px) { + .login-form { + padding: 20px; + } + + .header h1 { + font-size: 20px; + } +} \ No newline at end of file diff --git a/custom/morvalwatches/style/morvalwatches_reset.css b/custom/morvalwatches/style/morvalwatches_reset.css new file mode 100644 index 0000000..a88f437 --- /dev/null +++ b/custom/morvalwatches/style/morvalwatches_reset.css @@ -0,0 +1,231 @@ +:root { + --color-white: #FFFFFF; + --color-light-blue: #344fd5c2; + --color-blue: #0b1054; + --color-red: #a75151; + --text-color: #333333; + --error-background: #f3c3c3; +} + + +* { + padding: 0; + margin: 0; + box-sizing: border-box; + font-family: "Open Sans", Helvetica, sans-serif; + accent-color: var(--color-blue); +} + +body { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + background-color: var(--color-white); + padding: 20px; +} + +.login-container { + display: flex; + width: 100%; + max-width: 1200px; + height: calc(100vh - 40px); + background-color: var(--color-white); + border-radius: 16px; + box-shadow: 0 10px 25px rgba(0,0,0,0.1); + overflow: hidden; +} + +.login-form { + width: 45%; + padding: 40px; + display: flex; + flex-direction: column; + justify-content: center; + position: relative; +} + +.logo { + position: absolute; + top: 20px; + left: 20px; + background-image: url(VeLiTi-Logo2.png); + background-repeat: no-repeat; + opacity: inherit; + width: 85px; + height: 120px; + margin: 0 auto; + -webkit-filter: drop-shadow(5px 5px 5px #222); + filter: drop-shadow(5px 5px 5px #222); +} + +.login-visual { + width: 55%; + position: relative; + overflow: hidden; + background-image: url(veliti_intro.png); + background-position: center center; + background-size: 100% 100%; + background-color: var(--color-light-blue); + background-repeat: no-repeat; +} + +.login-visual img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.register-link { + position: absolute; + top: 20px; + right: 20px; + text-decoration: none; + color: var(--text-color); +} + +.header { + margin-bottom: 30px; +} + +.header h1 { + font-size: 24px; + margin-bottom: 10px; +} + +.header p { + color: #666; +} + +.input-group { + margin-bottom: 15px; + position: relative; +} + +.input-group input { + width: 100%; + padding: 10px; + border: 1px solid #ddd; + border-radius: 4px; +} + +.input-group input[type="email"] { + padding-left: 40px; + background: url('data:image/svg+xml;utf8,') no-repeat left 10px center; +} + +.input-group input[type="password"] { + padding-left: 40px; + background: url('data:image/svg+xml;utf8,') no-repeat left 10px center; +} + +.forgot-password { + color: var(--text-color); + text-decoration: none; + text-align: right; + margin-top: 5px; + float: right; + font-size: 12px; +} + +.remember-me { + display: flex; + align-items: center; + margin-bottom: 20px; +} + +.remember-me input { + margin-right: 10px; +} + +.maintenance { + padding: 5px; +} + +.message p { + margin-top: 5px; + background-color: var(--error-background); + border-left: 4px solid var(--color-red); + color: var(--color-red); + padding: 5px; + border-radius: 4px; + text-align: center; +} + +.login-btn { + width: 100%; + padding: 12px; + background-color: var(--color-light-blue); + color: var(--color-white); + border: none; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.login-btn:hover { + background-color: var(--color-blue); +} + +.trademark { + position: absolute; + bottom: 20px; + left: 20px; + color: var(--text-color); + font-size: 12px; +} + +.language-selector { + position: absolute; + bottom: 20px; + right: 20px; + display: flex; + align-items: center; + color: var(--text-color); +} + +.language-selector select { + margin-left: 10px; + border: none; + background: transparent; +} + +/* Responsive Design */ +@media screen and (max-width: 1024px) { + .login-container { + flex-direction: column; + height: auto; + max-width: 500px; + } + + .login-form, .login-visual { + width: 100%; + height: auto; + } + + .login-visual { + height: 300px; + } + + .logo { + position: static; + margin: 20px auto; + margin-bottom: 0; + } + + .trademark, .language-selector { + position: static; + text-align: center; + margin: 20px 0; + } +} + +@media screen and (max-width: 480px) { + .login-form { + padding: 20px; + } + + .header h1 { + font-size: 20px; + } +} \ No newline at end of file diff --git a/custom/morvalwatches/style/soveliti.css b/custom/morvalwatches/style/soveliti.css new file mode 100644 index 0000000..0ed2857 --- /dev/null +++ b/custom/morvalwatches/style/soveliti.css @@ -0,0 +1,2971 @@ +:root { + --color-text-default: #555555; + --color-text-heading: #4a5361; + --color-bg-body: #fff; + --color-bg-body: #ffffff; + --color-bg-header: #F8f9fa; + --color-bg-header: #f8f9fa; + --color-border-light: #eee; + --color-hover-dark: #606c7e; + --color-hover-light: #fbfbfb; + --color-note-bg: #383c46; + --color-note-text: #afb1b5; + --color-selected-bg: #527ee5; + --color-sub-hover: #2a374a; + --color-title-muted: #959faf; + --color-title-muted-light: #99999a; + --color-title-bg: #dbdddf; + --color-icon-bg: #7c8394; + --color-msg-success-bg: #C3F3D7; + --color-msg-success: #51a775; + --color-msg-error-bg: #f3c3c3; + --color-msg-error: #a75151; + --color-border-input: #d0d3d5; + --color-border-input-hover: #b5b9bd; + --color-filter-text-hover: #343a44; + --color-btn-bg: #4a79b4; + --color-btn-hover-bg: #4672a9; + --color-tab-bg: #dedfe1; + --color-tab-hover: #d8dadc; + --color-muted: #8d9398; + --color-status-neutral: #81848a; + --color-status-enabled: #13b368; + --color-status-warning: #eb8a0d; + --color-status-disabled: #bd4141; + --color-danger: #e26060; + --color-btn-secondary: #bed4ea; + --color-btn-neutral: #f1f3f4; + --color-pagination-hover: #6d7c90; + --color-pagination-bg: #758497; + --color-accent-blue: #0060ba; + --color-accent-blue-hover: #003260; + --color-accent-red: #ba0000; + --color-accent-red-hover: #600000; + --color-disabled: #b1b3b4; + --color-disabled-hover: #a9abad; +} + +* { + box-sizing: border-box; + font-family: -apple-system, BlinkMacSystemFont, "segoe ui", roboto, oxygen, ubuntu, cantarell, "fira sans", "droid sans", "helvetica neue", Arial, sans-serif; + font-size: 16px; +} + +html { + height: 100%; +} + +body { + position: relative; + min-height: 100%; + color: var(--color-text-default); + background-color: var(--color-bg-body); + margin: 0; +} + +h1, h2, h3, h4, h5 { + color: var(--color-text-heading); + margin: 0; + padding: 0; +} + +header { + display: flex; + position: fixed; + top: 0; + left: 0; + padding-left: 260px; + z-index: 999; + width: 100%; + height: 55px; + background-color: var(--color-bg-header); + box-shadow: 0px 0px 4px 1px rgba(0, 0, 0, 0.15); +} + +header a { + display: inline-flex; + color: var(--color-text-heading); + height: 100%; + text-decoration: none; + justify-content: center; + align-items: center; + padding: 0 20px; +} + +header a i { + font-size: 16px; +} + +header a:hover, header a:active { + color: var(--color-hover-dark); +} + +header .space-between { + flex: 1; +} + +header .dropdown { + display: inline-flex; + color: var(--color-text-heading); + height: 100%; + cursor: pointer; + justify-content: center; + align-items: center; + padding: 0 30px; +} + +header .dropdown i { + font-size: 18px; +} + +header .dropdown:hover, header .dropdown:active { + color: var(--color-hover-dark); +} + +header .dropdown .list { + display: none; + position: absolute; + top: 100%; + right: 0; + width: 150px; + background-color: var(--color-bg-body); + border-top: 1px solid var(--color-border-light); + box-shadow: 0px 2px 2px 1px rgba(0, 0, 0, 0.05); +} + +header .dropdown .list a { + padding: 10px 15px; + font-size: 14px; +} + +header .dropdown .list a:hover { + background-color: var(--color-hover-light); +} + +header .dropdown:hover .list, header .dropdown:active .list { + display: flex; + flex-flow: column; +} + +header.full { + padding-left: 0; +} + +aside { + position: fixed; + z-index: 999999; + height: 100%; + width: 260px; + display: flex; + flex-flow: column; + background-color: var(--color-bg-header); + overflow-y: auto; +} + +aside h1 { + background: url("SoVeLiTi.png"); + background-position: center center; + background-repeat: no-repeat; + background-size: 50%; + padding: 30px; +} + +aside > a { + font-size: 14px; + font-weight: 600; + text-decoration: none; + color: var(--color-note-text); + padding: 15px 20px; +} + +aside > a i { + color: inherit; + width: 40px; +} + +aside > a:hover, aside > a.selected { + background-color: var(--color-selected-bg); + color: var(--color-bg-body); + padding: 15px 17px; + border-radius: 5px; +} + +aside > a.selected + .sub { + display: flex; +} + +aside > a .note { + background-color: var(--color-note-bg); + padding: 1px 5px; + border-radius: 4px; + font-size: 12px; + margin-left: 10px; +} + +aside .sub { + display: none; + flex-flow: column; + background-color: var(--color-bg-header); + padding: 13px 0; +} + +aside .sub a { + font-size: 14px; + color: var(--color-note-text); + text-decoration: none; + padding: 4px 20px; +} + +aside .sub a span { + display: inline-block; + width: 40px; + font-size: 12px; +} + +aside .sub a:hover, aside .sub a.selected { + color: var(--color-sub-hover); +} + +aside .footer { + display: flex; + flex-flow: column; + margin-top: auto; + padding: 15px; + font-size: 14px; + color: var(--color-note-text); +} + +aside .footer a { + text-decoration: none; + font-size: 14px; + color: var(--color-note-text); + padding-bottom: 2px; +} + +aside .footer a:hover { + color: #7c7f83; +} + +aside.closed { + display: none; +} + +main { + padding: 30px; + padding-left: 290px; + padding-top: 85px; +} + +main.full { + padding-left: 30px; + padding-right: 30px; +} + +main h2 { + font-size: 20px; + padding-bottom: 20px; + font-weight: 600; +} + +main h2 span { + font-size: 16px; + margin-left: 5px; + font-weight: 600; + color: var(--color-title-muted); +} + +main .content-title { + border-bottom: 1px solid var(--color-title-bg); + display: flex; +} + +main .content-title h2 { + flex: 1; +} + +main .content-title .btn { + height: 36px; +} + +main .content-title .title { + flex: 1; + display: flex; + align-items: center; + padding-bottom: 15px; +} + +main .content-title .title i { + display: inline-flex; + justify-content: center; + align-items: center; + background-color: var(--color-selected-bg); + color: var(--color-bg-body); + width: 52px; + height: 42px; + border-radius: 4px; + margin-right: 12px; +} + +main .content-title .title i.alt { + background-color: var(--color-icon-bg); +} + +main .content-title .title h2 { + padding: 0 0 3px 0; +} + +main .content-title .title p { + margin: 0; + font-size: 14px; + color: var(--color-title-muted-light); + font-weight: 500; +} + +main .msg { + display: flex; + align-items: center; + margin: 15px 0 0 0; + padding: 15px; + font-weight: 500; + box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, 0.1); +} + +main .msg p { + margin: 0; + padding: 0 15px; + font-size: 14px; + flex: 1; +} + +main .msg i.fa-times { + align-self: flex-end; + justify-content: flex-end; + cursor: pointer; +} + +main .msg i.fa-times:hover { + opacity: .9; +} + +main .msg.success { + background-color: #c3f3d7; + border-left: 4px solid var(--color-msg-success); + color: var(--color-msg-success); +} + +main .msg.success i { + color: var(--color-msg-success); +} + +main .msg.error { + background-color: var(--color-msg-error-bg); + border-left: 4px solid var(--color-msg-error); + color: var(--color-msg-error); +} + +main .msg.error i { + color: var(--color-msg-error); +} + +main .content-header { + display: flex; + justify-content: space-between; +} + +main .content-header form { + display: flex; + justify-content: space-between; +} + +main .content-header form .search input, main .content-header form > select { + background-color: transparent; + outline: none; + border: none; + height: 40px; + width: 220px; + border-bottom: 1px solid var(--color-border-input); + padding-right: 25px; + margin-left: 10px; +} + +main .content-header form .search input:hover, main .content-header form .search input:active, main .content-header form > select:hover, main .content-header form > select:active { + border-bottom: 1px solid var(--color-border-input-hover); +} + +main .content-header form > select { + width: 150px; +} + +main .content-header form > a { + text-decoration: none; + display: inline-flex; + color: #676d72; + justify-self: center; + align-items: center; + padding: 0 5px; +} + +main .content-header form > a:hover { + color: #4f5357; +} + +main .content-header .search label { + position: relative; +} + +main .content-header .search i { + position: absolute; + right: 4px; + top: 4px; + bottom: 0; + font-size: 14px; + margin-top: auto; + margin-bottom: auto; + color: var(--color-border-input-hover); +} + +main .content-header .filters { + display: flex; + position: relative; + margin-right: 10px; + margin-bottom: 3px; + align-items: center; +} + +main .content-header .filters a { + text-decoration: none; + font-weight: 600; + color: var(--color-text-heading); + white-space: nowrap; +} + +main .content-header .filters a:hover { + color: var(--color-filter-text-hover); +} + +main .content-header .filters .list { + display: none; + top: calc(100% + 5px); + width: 180px; + flex-flow: column; + position: absolute; + background-color: var(--color-bg-body); + padding: 10px; + box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.1); +} + +main .content-header .filters .list label { + padding-bottom: 5px; + font-size: 14px; +} + +main .content-header .filters .list input { + margin-right: 10px; +} + +main .content-header .filters .list select { + width: 100%; + margin-bottom: 10px; + font-size: 14px; + padding: 3px; + border: 1px solid var(--color-tab-bg); +} + +main .content-header .filters .list button { + background: var(--color-btn-bg); + border: 0; + color: var(--color-bg-body); + padding: 5px 0; + font-size: 12px; + font-weight: 600; + margin-top: 5px; + cursor: pointer; + border-radius: 4px; +} + +main .content-header .filters .list button:hover { + background: var(--color-btn-hover-bg); +} + + +main .content-header .sort { + display: flex; + position: relative; + margin-right: 10px; + margin-bottom: 3px; + align-items: center; +} + +main .content-header .sort a { + text-decoration: none; + font-weight: 600; + color: var(--color-text-heading); + white-space: nowrap; +} + +main .content-header .sort a:hover { + color: var(--color-filter-text-hover); +} + +main .content-header .sort .list { + display: none; + top: calc(100% + 5px); + width: 180px; + flex-flow: column; + position: absolute; + background-color: var(--color-bg-body); + padding: 10px; + box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.1); +} + +main .content-header .sort .list label { + padding-bottom: 5px; + font-size: 14px; +} + +main .content-header .sort .list input { + margin-right: 10px; +} + +main .content-header .sort .list select { + width: 100%; + margin-bottom: 10px; + font-size: 14px; + padding: 3px; + border: 1px solid var(--color-tab-bg); +} + +main .content-header .sort .list button { + background: var(--color-btn-bg); + border: 0; + color: var(--color-bg-body); + padding: 5px 0; + font-size: 12px; + font-weight: 600; + margin-top: 5px; + cursor: pointer; + border-radius: 4px; +} + +main .content-header .sort .list button:hover { + background: var(--color-btn-hover-bg); +} + +main .content-block { + background-color: var(--color-bg-body); + margin-top: 25px; + padding: 15px; + box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.03); + overflow: hidden; + border-radius: 3px; +} + +main .content-block .block-header { + position: relative; + border-bottom: 1px solid #f0f1f2; + margin-bottom: 20px; + padding: 0 15px 15px 15px; + margin-left: -15px; + margin-right: -15px; + font-size: 14px; + font-weight: 500; +} + +main .content-block .block-header i { + display: inline-flex; + height: 25px; + width: 25px; + padding-top: 2px; + justify-content: center; + align-items: center; + border-radius: 50%; + background-color: var(--color-selected-bg); + color: var(--color-bg-body); + margin-right: 10px; +} + +main .content-block-wrapper { + display: flex; + width: 100%; + padding-top: 25px; +} + +main .content-block-wrapper .content-block { + width: 100%; + margin: 0 10px; + border-radius: 3px; +} + +main .content-block-wrapper .content-block:first-child { + margin-left: 0; +} + +main .content-block-wrapper .content-block:last-child { + margin-right: 0; +} + +main .tabs { + display: flex; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + background-color: var(--color-tab-bg); + margin-top: 25px; + box-shadow: 0px 0px 4px 1px rgba(0, 0, 0, 0.03); + z-index: 100; +} + +main .tabs a { + display: flex; + text-decoration: none; + padding: 12px 15px; + border: 0; + color: #6b788c; + font-weight: 500; + font-size: 14px; +} + +main .tabs a:hover { + background-color: var(--color-tab-hover); +} + +main .tabs a.active { + color: var(--color-text-heading); + background-color: var(--color-bg-body); +} + +main .tabs ~ .content-block { + margin-top: 0; + box-shadow: 0px 6px 5px 1px rgba(0, 0, 0, 0.03); +} + +main .tab-content { + display: none; +} + +main .tab-content.active { + display: block; +} + +main .dashboard { + display: flex; + justify-content: space-between; + padding-bottom: 40px; +} + +main .dashboard .stat { + width: 24%; + padding: 0; + display: flex; + flex-flow: wrap; + border-radius: 3px; +} + +main .dashboard .stat > i { + display: inline-flex; + justify-content: center; + padding: 15px; + margin: 30px 25px 0 0; + align-items: center; + font-size: 18px; + height: 40px; + width: 40px; + border-radius: 5px; + background-color: var(--color-selected-bg); + color: var(--color-bg-body); +} + +main .dashboard .stat .data { + padding: 7px; + flex: 1; +} + +main .dashboard .stat .data h3 { + font-size: 16px; + font-weight: 400; + padding: 15px 15px 0 15px; +} + +main .dashboard .stat .data p { + margin: 0; + padding: 10px 15px 15px 15px; + font-size: 24px; + font-weight: 700; +} + +main .dashboard .stat .footer { + width: 100%; + border-top: 1px solid #ebeced; + background-color: var(--color-hover-light); + color: #9aa0a5; + font-size: 14px; + padding: 10px; +} + +main .dashboard .stat .footer i { + padding-right: 5px; +} + +main .form { + display: flex; + flex-flow: column; + padding: 20px; +} + +main .form input[type="text"], main .form input[type="password"], main .form input[type="datetime-local"], main .form input[type="email"], main .form input[type="number"], main .form textarea, main .form select { + width: 100%; + padding: 15px 5px; + margin-bottom: 25px; + border: 0; + border-bottom: 1px solid var(--color-tab-bg); +} + +main .form input[type="text"]:hover, main .form input[type="text"]:active, main .form input[type="password"]:hover, main .form input[type="password"]:active, main .form input[type="datetime-local"]:hover, main .form input[type="datetime-local"]:active, main .form input[type="email"]:hover, main .form input[type="email"]:active, main .form input[type="number"]:hover, main .form input[type="number"]:active, main .form textarea:hover, main .form textarea:active, main .form select:hover, main .form select:active { + border-bottom: 1px solid var(--color-border-input-hover); +} + +main .form textarea { + height: 200px; +} + +main .form input[type="checkbox"] { + width: auto; + margin: 15px 0 25px 2px; + transform: scale(1.2); +} + +main .form label { + display: block; + font-weight: 600; +} + +main .form label .required { + font-style: normal; + color: var(--color-danger); +} + +main .form button { + background-color: var(--color-tab-bg); + color: #676d72; + border: 0; + padding: 5px; + width: 100%; + font-weight: 600; + font-size: 14px; + cursor: pointer; +} + +main .form button:hover { + background-color: #d6d8da; +} + +main .form .multiselect { + position: relative; + display: flex; + flex-flow: wrap; + border-bottom: 1px solid var(--color-tab-bg); + padding-bottom: 10px; + margin: 15px 0 25px 0; + margin-bottom: 25px; +} + +main .form .multiselect > .item { + display: inline-flex; + border: 1px solid var(--color-tab-bg); + padding: 0 10px; + height: 40px; + margin: 0 5px 5px 0; + font-size: 14px; + justify-content: center; + align-items: center; +} +main .multiselect > .item { + display: inline-flex; + border: 1px solid var(--color-tab-bg); + padding: 0 10px; + height: 40px; + margin: 0 5px 5px 0; + font-size: 14px; + justify-content: center; + align-items: center; +} + +main .form .multiselect > .item .remove { + font-style: normal; + cursor: pointer; + font-size: 19px; + margin-right: 3px; + margin-top: -2px; + color: var(--color-border-input-hover); +} + +main .form .multiselect > .item .remove:hover { + color: #9aa0a5; +} + +main .form .multiselect input { + height: 40px; + width: 80px; + flex-grow: 1; + padding: 0; + margin: 0; + outline: 0; + border: 0; +} + +main .form .multiselect input:hover { + border: 0; +} + +main .form .multiselect .list { + display: none; + position: absolute; + top: 100%; + width: 100%; + flex-flow: column; + background-color: var(--color-bg-body); + box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.1); + max-height: 100px; + overflow-y: auto; + z-index: 1000000000; +} + +main .form .multiselect .list span { + display: flex; + padding: 5px 7px; + cursor: pointer; +} + +main .form .multiselect .list span:hover { + background-color: #f3f4f4; +} + +main .form .multiselect:hover, main .form .multiselect:active { + border-bottom: 1px solid var(--color-border-input-hover); +} + +main .top-nav { + display: flex; + flex-flow: wrap; + padding-top: 20px; +} + +main .error { + padding: 15px; + margin: 0; +} + +main .pagination { + display: flex; + align-items: center; + padding: 25px 0; +} + +main .pagination a { + display: inline-flex; + text-decoration: none; + background-color: var(--color-pagination-bg); + font-size: 14px; + font-weight: 600; + color: var(--color-bg-body); + border-radius: 4px; + padding: 7px 10px; +} + +main .pagination a:hover { + background-color: var(--color-pagination-hover); +} + +main .pagination a:first-child { + margin-right: 10px; +} + +main .pagination a:last-child { + margin-left: 10px; +} + +main .pagination span { + font-weight: 600; + margin: 10px; +} + +main .media-page .media { + display: flex; + flex-flow: wrap; + padding: 15px; +} + +main .media-page .media .image { + position: relative; + text-decoration: none; + border: 1px solid #e3e4e6; + padding: 10px; + width: 150px; + height: 135px; + margin: 0 15px 50px 0; +} + +main .media-page .media .image img { + width: 100%; + height: 100%; + object-fit: contain; + transition: all ease .3s; +} + +main .media-page .media .image::after { + content: attr(data-title); + display: block; + position: absolute; + color: #6e7581; + font-size: 14px; + font-weight: 500; + top: calc(100% + 5px); + left: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 100%; +} + +main .media-page .media .image:hover { + border: 1px solid var(--color-border-input); +} + +main .media-page .media .image:hover img { + transform: scale(1.05); +} + +main .media-page .media .image:hover .title { + color: #5c6471; +} + +main .order-details .order-detail { + display: flex; + justify-content: space-between; + padding-bottom: 15px; + word-break: break-all; +} + +main .order-details .order-detail h3 { + padding: 0; + margin: 0; + font-size: 14px; + font-weight: 500; +} + +main .order-details .order-detail p { + padding: 0; + margin: 0; +} + +main .order-table { + margin: 0; +} + +main .order-table .item-list-end { + border-bottom: 1px solid #f0f1f2; +} + +main .order-table .subtotal { + padding-top: 20px; +} + +main .order-table .subtotal, main .order-table .shipping, main .order-table .total { + text-align: right; + font-weight: 500; + font-size: 14px; +} + +main .order-table .num { + text-align: right; +} + +main .manage-order-table input, main .manage-order-table select { + border: 0; + padding: 5px 0; + height: 40px; + background: transparent; + width: 90%; + border-bottom: 1px solid var(--color-tab-bg); +} + +main .manage-order-table .add-item { + display: inline-block; + text-decoration: none; + color: #676d72; + padding: 25px 0; +} + +main .manage-order-table .add-item i { + padding-right: 5px; +} + +main .manage-order-table .add-item:hover { + color: #4f5357; +} + +main .manage-order-table .delete-item { + cursor: pointer; + color: #676d72; +} + +main .manage-order-table .delete-item:hover { + color: #b44a4a; +} + +.table { + overflow-x: auto; + padding: 0 10px; +} + +.table table { + width: 100%; + border-collapse: collapse; +} + +.table table thead td { + font-weight: 600; + font-size: 14px; + padding: 15px 0; +} + +.table table thead td a { + font-weight: inherit; + font-size: inherit; + color: inherit; + text-decoration: none; +} + +.table table thead td i { + padding-left: 5px; +} + +.table table thead tr { + border-bottom: 1px solid #f0f1f2; +} + +.table table tbody tr:first-child td { + padding-top: 10px; +} + +.table table tbody td { + padding: 5px; +} + +.table table tbody .img { + padding: 1px 0; +} + +.table table tbody .rrp { + color: var(--color-danger); +} + +.table table tbody .status { + padding: 4px 7px; + border-radius: 4px; + background-color: var(--color-status-neutral); + font-weight: 500; + font-size: 12px; + color: var(--color-bg-body); +} + +.table table tbody .status.enabled { + padding: 4px 7px; + border-radius: 4px; + background-color: var(--color-status-enabled); + font-weight: 500; + font-size: 12px; + color: var(--color-bg-body); +} + +.table table tbody .status.disabled { + padding: 4px 7px; + border-radius: 4px; + background-color: var(--color-status-disabled); + font-weight: 500; + font-size: 12px; + color: var(--color-bg-body); +} + + +.status { + padding: 4px 7px; + border-radius: 4px; + background-color: var(--color-status-neutral); + font-weight: 500; + font-size: 12px; + color: var(--color-bg-body); +} + +.status.enabled { + background-color: var(--color-status-enabled); +} + +.status.disabled { + background-color: var(--color-status-disabled); +} + +.status.id4 { + background-color: var(--color-status-enabled); +} + +.status.id3 { +background-color: var(--color-status-enabled); +} + +.status.id2 { + background-color: var(--color-status-warning); +} +.table table tbody .status.service_renewal{ + background-color: var(--color-status-warning); +} + +.table table tbody .status.firmware_update{ + background-color: var(--color-status-warning); +} + +.table table tbody .status.warranty_outdated{ + background-color: var(--color-status-warning); +} +.table table tbody .status.warranty { + background-color: var(--color-status-enabled); +} + +.table table tbody .status.service{ + background-color: var(--color-status-enabled); +} + +.table table tbody .status.firmware_recent{ + background-color: var(--color-status-enabled); +} + +.status.id5 { + background-color: var(--color-status-disabled); +} + +.table table tbody .status.id4, .table table tbody .status.id3, .table table tbody .status.warranty { + background-color: var(--color-status-enabled); +} + +.table table tbody .status.id2 { + background-color: var(--color-status-warning); +} + +.table table tbody .status.id5{ + background-color: var(--color-status-disabled); +} + +.product-media-tab, .product-options-tab, .product-downloads-tab { + max-width: 800px; +} + +.product-media-tab .product-media, .product-media-tab .product-option, .product-media-tab .product-download, .product-options-tab .product-media, .product-options-tab .product-option, .product-options-tab .product-download, .product-downloads-tab .product-media, .product-downloads-tab .product-option, .product-downloads-tab .product-download { + display: flex; + border-right: 4px solid #e8ebee; + margin-bottom: 15px; +} + +.product-media-tab .product-media:first-child, .product-media-tab .product-option:first-child, .product-media-tab .product-download:first-child, .product-options-tab .product-media:first-child, .product-options-tab .product-option:first-child, .product-options-tab .product-download:first-child, .product-downloads-tab .product-media:first-child, .product-downloads-tab .product-option:first-child, .product-downloads-tab .product-download:first-child { + border-right: 4px solid #72a0d4; +} + +.product-media-tab .product-media:first-child .move-up, .product-media-tab .product-option:first-child .move-up, .product-media-tab .product-download:first-child .move-up, .product-options-tab .product-media:first-child .move-up, .product-options-tab .product-option:first-child .move-up, .product-options-tab .product-download:first-child .move-up, .product-downloads-tab .product-media:first-child .move-up, .product-downloads-tab .product-option:first-child .move-up, .product-downloads-tab .product-download:first-child .move-up { + display: none; +} + +.product-media-tab .product-media:last-child .move-down, .product-media-tab .product-option:last-child .move-down, .product-media-tab .product-download:last-child .move-down, .product-options-tab .product-media:last-child .move-down, .product-options-tab .product-option:last-child .move-down, .product-options-tab .product-download:last-child .move-down, .product-downloads-tab .product-media:last-child .move-down, .product-downloads-tab .product-option:last-child .move-down, .product-downloads-tab .product-download:last-child .move-down { + display: none; +} + +.product-media-tab .product-media .media-index, .product-media-tab .product-media .option-index, .product-media-tab .product-media .download-index, .product-media-tab .product-option .media-index, .product-media-tab .product-option .option-index, .product-media-tab .product-option .download-index, .product-media-tab .product-download .media-index, .product-media-tab .product-download .option-index, .product-media-tab .product-download .download-index, .product-options-tab .product-media .media-index, .product-options-tab .product-media .option-index, .product-options-tab .product-media .download-index, .product-options-tab .product-option .media-index, .product-options-tab .product-option .option-index, .product-options-tab .product-option .download-index, .product-options-tab .product-download .media-index, .product-options-tab .product-download .option-index, .product-options-tab .product-download .download-index, .product-downloads-tab .product-media .media-index, .product-downloads-tab .product-media .option-index, .product-downloads-tab .product-media .download-index, .product-downloads-tab .product-option .media-index, .product-downloads-tab .product-option .option-index, .product-downloads-tab .product-option .download-index, .product-downloads-tab .product-download .media-index, .product-downloads-tab .product-download .option-index, .product-downloads-tab .product-download .download-index { + display: flex; + color: #bcc0c4; + font-weight: 500; + align-items: center; + padding-right: 15px; + font-weight: 14px; + width: 30px; +} + +.product-media-tab .product-media .media-img, .product-media-tab .product-option .media-img, .product-media-tab .product-download .media-img, .product-options-tab .product-media .media-img, .product-options-tab .product-option .media-img, .product-options-tab .product-download .media-img, .product-downloads-tab .product-media .media-img, .product-downloads-tab .product-option .media-img, .product-downloads-tab .product-download .media-img { + text-decoration: none; + display: flex; + height: 70px; + width: 100px; + border: 1px solid #ddd; + padding: 5px; +} + +.product-media-tab .product-media .media-img img, .product-media-tab .product-option .media-img img, .product-media-tab .product-download .media-img img, .product-options-tab .product-media .media-img img, .product-options-tab .product-option .media-img img, .product-options-tab .product-download .media-img img, .product-downloads-tab .product-media .media-img img, .product-downloads-tab .product-option .media-img img, .product-downloads-tab .product-download .media-img img { + width: 100%; + height: 100%; + object-fit: contain; +} + +.product-media-tab .product-media .media-text, .product-media-tab .product-media .option-text, .product-media-tab .product-media .download-text, .product-media-tab .product-option .media-text, .product-media-tab .product-option .option-text, .product-media-tab .product-option .download-text, .product-media-tab .product-download .media-text, .product-media-tab .product-download .option-text, .product-media-tab .product-download .download-text, .product-options-tab .product-media .media-text, .product-options-tab .product-media .option-text, .product-options-tab .product-media .download-text, .product-options-tab .product-option .media-text, .product-options-tab .product-option .option-text, .product-options-tab .product-option .download-text, .product-options-tab .product-download .media-text, .product-options-tab .product-download .option-text, .product-options-tab .product-download .download-text, .product-downloads-tab .product-media .media-text, .product-downloads-tab .product-media .option-text, .product-downloads-tab .product-media .download-text, .product-downloads-tab .product-option .media-text, .product-downloads-tab .product-option .option-text, .product-downloads-tab .product-option .download-text, .product-downloads-tab .product-download .media-text, .product-downloads-tab .product-download .option-text, .product-downloads-tab .product-download .download-text { + display: flex; + flex-flow: column; + justify-content: center; + flex: 1; + padding-left: 15px; +} + +.product-media-tab .product-media .media-text h3, .product-media-tab .product-media .option-text h3, .product-media-tab .product-media .download-text h3, .product-media-tab .product-option .media-text h3, .product-media-tab .product-option .option-text h3, .product-media-tab .product-option .download-text h3, .product-media-tab .product-download .media-text h3, .product-media-tab .product-download .option-text h3, .product-media-tab .product-download .download-text h3, .product-options-tab .product-media .media-text h3, .product-options-tab .product-media .option-text h3, .product-options-tab .product-media .download-text h3, .product-options-tab .product-option .media-text h3, .product-options-tab .product-option .option-text h3, .product-options-tab .product-option .download-text h3, .product-options-tab .product-download .media-text h3, .product-options-tab .product-download .option-text h3, .product-options-tab .product-download .download-text h3, .product-downloads-tab .product-media .media-text h3, .product-downloads-tab .product-media .option-text h3, .product-downloads-tab .product-media .download-text h3, .product-downloads-tab .product-option .media-text h3, .product-downloads-tab .product-option .option-text h3, .product-downloads-tab .product-option .download-text h3, .product-downloads-tab .product-download .media-text h3, .product-downloads-tab .product-download .option-text h3, .product-downloads-tab .product-download .download-text h3 { + font-weight: 500; +} + +.product-media-tab .product-media .media-text p, .product-media-tab .product-media .option-text p, .product-media-tab .product-media .download-text p, .product-media-tab .product-option .media-text p, .product-media-tab .product-option .option-text p, .product-media-tab .product-option .download-text p, .product-media-tab .product-download .media-text p, .product-media-tab .product-download .option-text p, .product-media-tab .product-download .download-text p, .product-options-tab .product-media .media-text p, .product-options-tab .product-media .option-text p, .product-options-tab .product-media .download-text p, .product-options-tab .product-option .media-text p, .product-options-tab .product-option .option-text p, .product-options-tab .product-option .download-text p, .product-options-tab .product-download .media-text p, .product-options-tab .product-download .option-text p, .product-options-tab .product-download .download-text p, .product-downloads-tab .product-media .media-text p, .product-downloads-tab .product-media .option-text p, .product-downloads-tab .product-media .download-text p, .product-downloads-tab .product-option .media-text p, .product-downloads-tab .product-option .option-text p, .product-downloads-tab .product-option .download-text p, .product-downloads-tab .product-download .media-text p, .product-downloads-tab .product-download .option-text p, .product-downloads-tab .product-download .download-text p { + margin: 0; + color: var(--color-muted); + font-size: 14px; +} + +.product-media-tab .product-media .media-position, .product-media-tab .product-media .option-position, .product-media-tab .product-media .download-position, .product-media-tab .product-option .media-position, .product-media-tab .product-option .option-position, .product-media-tab .product-option .download-position, .product-media-tab .product-download .media-position, .product-media-tab .product-download .option-position, .product-media-tab .product-download .download-position, .product-options-tab .product-media .media-position, .product-options-tab .product-media .option-position, .product-options-tab .product-media .download-position, .product-options-tab .product-option .media-position, .product-options-tab .product-option .option-position, .product-options-tab .product-option .download-position, .product-options-tab .product-download .media-position, .product-options-tab .product-download .option-position, .product-options-tab .product-download .download-position, .product-downloads-tab .product-media .media-position, .product-downloads-tab .product-media .option-position, .product-downloads-tab .product-media .download-position, .product-downloads-tab .product-option .media-position, .product-downloads-tab .product-option .option-position, .product-downloads-tab .product-option .download-position, .product-downloads-tab .product-download .media-position, .product-downloads-tab .product-download .option-position, .product-downloads-tab .product-download .download-position { + display: flex; + width: 100px; + justify-content: flex-end; + align-items: center; + padding-right: 20px; +} + +.product-media-tab .product-media .media-position i, .product-media-tab .product-media .option-position i, .product-media-tab .product-media .download-position i, .product-media-tab .product-option .media-position i, .product-media-tab .product-option .option-position i, .product-media-tab .product-option .download-position i, .product-media-tab .product-download .media-position i, .product-media-tab .product-download .option-position i, .product-media-tab .product-download .download-position i, .product-options-tab .product-media .media-position i, .product-options-tab .product-media .option-position i, .product-options-tab .product-media .download-position i, .product-options-tab .product-option .media-position i, .product-options-tab .product-option .option-position i, .product-options-tab .product-option .download-position i, .product-options-tab .product-download .media-position i, .product-options-tab .product-download .option-position i, .product-options-tab .product-download .download-position i, .product-downloads-tab .product-media .media-position i, .product-downloads-tab .product-media .option-position i, .product-downloads-tab .product-media .download-position i, .product-downloads-tab .product-option .media-position i, .product-downloads-tab .product-option .option-position i, .product-downloads-tab .product-option .download-position i, .product-downloads-tab .product-download .media-position i, .product-downloads-tab .product-download .option-position i, .product-downloads-tab .product-download .download-position i { + cursor: pointer; + font-size: 20px; + padding-left: 15px; + color: #cbcfd4; +} + +.product-media-tab .product-media .media-position i:hover, .product-media-tab .product-media .option-position i:hover, .product-media-tab .product-media .download-position i:hover, .product-media-tab .product-option .media-position i:hover, .product-media-tab .product-option .option-position i:hover, .product-media-tab .product-option .download-position i:hover, .product-media-tab .product-download .media-position i:hover, .product-media-tab .product-download .option-position i:hover, .product-media-tab .product-download .download-position i:hover, .product-options-tab .product-media .media-position i:hover, .product-options-tab .product-media .option-position i:hover, .product-options-tab .product-media .download-position i:hover, .product-options-tab .product-option .media-position i:hover, .product-options-tab .product-option .option-position i:hover, .product-options-tab .product-option .download-position i:hover, .product-options-tab .product-download .media-position i:hover, .product-options-tab .product-download .option-position i:hover, .product-options-tab .product-download .download-position i:hover, .product-downloads-tab .product-media .media-position i:hover, .product-downloads-tab .product-media .option-position i:hover, .product-downloads-tab .product-media .download-position i:hover, .product-downloads-tab .product-option .media-position i:hover, .product-downloads-tab .product-option .option-position i:hover, .product-downloads-tab .product-option .download-position i:hover, .product-downloads-tab .product-download .media-position i:hover, .product-downloads-tab .product-download .option-position i:hover, .product-downloads-tab .product-download .download-position i:hover { + color: #afb5bd; +} + +.product-media-tab .product-media .media-position i.media-delete, .product-media-tab .product-media .media-position i.option-delete, .product-media-tab .product-media .media-position i.download-delete, .product-media-tab .product-media .option-position i.media-delete, .product-media-tab .product-media .option-position i.option-delete, .product-media-tab .product-media .option-position i.download-delete, .product-media-tab .product-media .download-position i.media-delete, .product-media-tab .product-media .download-position i.option-delete, .product-media-tab .product-media .download-position i.download-delete, .product-media-tab .product-option .media-position i.media-delete, .product-media-tab .product-option .media-position i.option-delete, .product-media-tab .product-option .media-position i.download-delete, .product-media-tab .product-option .option-position i.media-delete, .product-media-tab .product-option .option-position i.option-delete, .product-media-tab .product-option .option-position i.download-delete, .product-media-tab .product-option .download-position i.media-delete, .product-media-tab .product-option .download-position i.option-delete, .product-media-tab .product-option .download-position i.download-delete, .product-media-tab .product-download .media-position i.media-delete, .product-media-tab .product-download .media-position i.option-delete, .product-media-tab .product-download .media-position i.download-delete, .product-media-tab .product-download .option-position i.media-delete, .product-media-tab .product-download .option-position i.option-delete, .product-media-tab .product-download .option-position i.download-delete, .product-media-tab .product-download .download-position i.media-delete, .product-media-tab .product-download .download-position i.option-delete, .product-media-tab .product-download .download-position i.download-delete, .product-options-tab .product-media .media-position i.media-delete, .product-options-tab .product-media .media-position i.option-delete, .product-options-tab .product-media .media-position i.download-delete, .product-options-tab .product-media .option-position i.media-delete, .product-options-tab .product-media .option-position i.option-delete, .product-options-tab .product-media .option-position i.download-delete, .product-options-tab .product-media .download-position i.media-delete, .product-options-tab .product-media .download-position i.option-delete, .product-options-tab .product-media .download-position i.download-delete, .product-options-tab .product-option .media-position i.media-delete, .product-options-tab .product-option .media-position i.option-delete, .product-options-tab .product-option .media-position i.download-delete, .product-options-tab .product-option .option-position i.media-delete, .product-options-tab .product-option .option-position i.option-delete, .product-options-tab .product-option .option-position i.download-delete, .product-options-tab .product-option .download-position i.media-delete, .product-options-tab .product-option .download-position i.option-delete, .product-options-tab .product-option .download-position i.download-delete, .product-options-tab .product-download .media-position i.media-delete, .product-options-tab .product-download .media-position i.option-delete, .product-options-tab .product-download .media-position i.download-delete, .product-options-tab .product-download .option-position i.media-delete, .product-options-tab .product-download .option-position i.option-delete, .product-options-tab .product-download .option-position i.download-delete, .product-options-tab .product-download .download-position i.media-delete, .product-options-tab .product-download .download-position i.option-delete, .product-options-tab .product-download .download-position i.download-delete, .product-downloads-tab .product-media .media-position i.media-delete, .product-downloads-tab .product-media .media-position i.option-delete, .product-downloads-tab .product-media .media-position i.download-delete, .product-downloads-tab .product-media .option-position i.media-delete, .product-downloads-tab .product-media .option-position i.option-delete, .product-downloads-tab .product-media .option-position i.download-delete, .product-downloads-tab .product-media .download-position i.media-delete, .product-downloads-tab .product-media .download-position i.option-delete, .product-downloads-tab .product-media .download-position i.download-delete, .product-downloads-tab .product-option .media-position i.media-delete, .product-downloads-tab .product-option .media-position i.option-delete, .product-downloads-tab .product-option .media-position i.download-delete, .product-downloads-tab .product-option .option-position i.media-delete, .product-downloads-tab .product-option .option-position i.option-delete, .product-downloads-tab .product-option .option-position i.download-delete, .product-downloads-tab .product-option .download-position i.media-delete, .product-downloads-tab .product-option .download-position i.option-delete, .product-downloads-tab .product-option .download-position i.download-delete, .product-downloads-tab .product-download .media-position i.media-delete, .product-downloads-tab .product-download .media-position i.option-delete, .product-downloads-tab .product-download .media-position i.download-delete, .product-downloads-tab .product-download .option-position i.media-delete, .product-downloads-tab .product-download .option-position i.option-delete, .product-downloads-tab .product-download .option-position i.download-delete, .product-downloads-tab .product-download .download-position i.media-delete, .product-downloads-tab .product-download .download-position i.option-delete, .product-downloads-tab .product-download .download-position i.download-delete { + padding-right: 5px; + color: #e1e4e6; +} + +.product-media-tab .product-media .media-position i.media-delete:hover, .product-media-tab .product-media .media-position i.option-delete:hover, .product-media-tab .product-media .media-position i.download-delete:hover, .product-media-tab .product-media .option-position i.media-delete:hover, .product-media-tab .product-media .option-position i.option-delete:hover, .product-media-tab .product-media .option-position i.download-delete:hover, .product-media-tab .product-media .download-position i.media-delete:hover, .product-media-tab .product-media .download-position i.option-delete:hover, .product-media-tab .product-media .download-position i.download-delete:hover, .product-media-tab .product-option .media-position i.media-delete:hover, .product-media-tab .product-option .media-position i.option-delete:hover, .product-media-tab .product-option .media-position i.download-delete:hover, .product-media-tab .product-option .option-position i.media-delete:hover, .product-media-tab .product-option .option-position i.option-delete:hover, .product-media-tab .product-option .option-position i.download-delete:hover, .product-media-tab .product-option .download-position i.media-delete:hover, .product-media-tab .product-option .download-position i.option-delete:hover, .product-media-tab .product-option .download-position i.download-delete:hover, .product-media-tab .product-download .media-position i.media-delete:hover, .product-media-tab .product-download .media-position i.option-delete:hover, .product-media-tab .product-download .media-position i.download-delete:hover, .product-media-tab .product-download .option-position i.media-delete:hover, .product-media-tab .product-download .option-position i.option-delete:hover, .product-media-tab .product-download .option-position i.download-delete:hover, .product-media-tab .product-download .download-position i.media-delete:hover, .product-media-tab .product-download .download-position i.option-delete:hover, .product-media-tab .product-download .download-position i.download-delete:hover, .product-options-tab .product-media .media-position i.media-delete:hover, .product-options-tab .product-media .media-position i.option-delete:hover, .product-options-tab .product-media .media-position i.download-delete:hover, .product-options-tab .product-media .option-position i.media-delete:hover, .product-options-tab .product-media .option-position i.option-delete:hover, .product-options-tab .product-media .option-position i.download-delete:hover, .product-options-tab .product-media .download-position i.media-delete:hover, .product-options-tab .product-media .download-position i.option-delete:hover, .product-options-tab .product-media .download-position i.download-delete:hover, .product-options-tab .product-option .media-position i.media-delete:hover, .product-options-tab .product-option .media-position i.option-delete:hover, .product-options-tab .product-option .media-position i.download-delete:hover, .product-options-tab .product-option .option-position i.media-delete:hover, .product-options-tab .product-option .option-position i.option-delete:hover, .product-options-tab .product-option .option-position i.download-delete:hover, .product-options-tab .product-option .download-position i.media-delete:hover, .product-options-tab .product-option .download-position i.option-delete:hover, .product-options-tab .product-option .download-position i.download-delete:hover, .product-options-tab .product-download .media-position i.media-delete:hover, .product-options-tab .product-download .media-position i.option-delete:hover, .product-options-tab .product-download .media-position i.download-delete:hover, .product-options-tab .product-download .option-position i.media-delete:hover, .product-options-tab .product-download .option-position i.option-delete:hover, .product-options-tab .product-download .option-position i.download-delete:hover, .product-options-tab .product-download .download-position i.media-delete:hover, .product-options-tab .product-download .download-position i.option-delete:hover, .product-options-tab .product-download .download-position i.download-delete:hover, .product-downloads-tab .product-media .media-position i.media-delete:hover, .product-downloads-tab .product-media .media-position i.option-delete:hover, .product-downloads-tab .product-media .media-position i.download-delete:hover, .product-downloads-tab .product-media .option-position i.media-delete:hover, .product-downloads-tab .product-media .option-position i.option-delete:hover, .product-downloads-tab .product-media .option-position i.download-delete:hover, .product-downloads-tab .product-media .download-position i.media-delete:hover, .product-downloads-tab .product-media .download-position i.option-delete:hover, .product-downloads-tab .product-media .download-position i.download-delete:hover, .product-downloads-tab .product-option .media-position i.media-delete:hover, .product-downloads-tab .product-option .media-position i.option-delete:hover, .product-downloads-tab .product-option .media-position i.download-delete:hover, .product-downloads-tab .product-option .option-position i.media-delete:hover, .product-downloads-tab .product-option .option-position i.option-delete:hover, .product-downloads-tab .product-option .option-position i.download-delete:hover, .product-downloads-tab .product-option .download-position i.media-delete:hover, .product-downloads-tab .product-option .download-position i.option-delete:hover, .product-downloads-tab .product-option .download-position i.download-delete:hover, .product-downloads-tab .product-download .media-position i.media-delete:hover, .product-downloads-tab .product-download .media-position i.option-delete:hover, .product-downloads-tab .product-download .media-position i.download-delete:hover, .product-downloads-tab .product-download .option-position i.media-delete:hover, .product-downloads-tab .product-download .option-position i.option-delete:hover, .product-downloads-tab .product-download .option-position i.download-delete:hover, .product-downloads-tab .product-download .download-position i.media-delete:hover, .product-downloads-tab .product-download .download-position i.option-delete:hover, .product-downloads-tab .product-download .download-position i.download-delete:hover { + color: #b44a4a; +} + +.product-media-tab .product-media .media-position i.option-edit, .product-media-tab .product-media .option-position i.option-edit, .product-media-tab .product-media .download-position i.option-edit, .product-media-tab .product-option .media-position i.option-edit, .product-media-tab .product-option .option-position i.option-edit, .product-media-tab .product-option .download-position i.option-edit, .product-media-tab .product-download .media-position i.option-edit, .product-media-tab .product-download .option-position i.option-edit, .product-media-tab .product-download .download-position i.option-edit, .product-options-tab .product-media .media-position i.option-edit, .product-options-tab .product-media .option-position i.option-edit, .product-options-tab .product-media .download-position i.option-edit, .product-options-tab .product-option .media-position i.option-edit, .product-options-tab .product-option .option-position i.option-edit, .product-options-tab .product-option .download-position i.option-edit, .product-options-tab .product-download .media-position i.option-edit, .product-options-tab .product-download .option-position i.option-edit, .product-options-tab .product-download .download-position i.option-edit, .product-downloads-tab .product-media .media-position i.option-edit, .product-downloads-tab .product-media .option-position i.option-edit, .product-downloads-tab .product-media .download-position i.option-edit, .product-downloads-tab .product-option .media-position i.option-edit, .product-downloads-tab .product-option .option-position i.option-edit, .product-downloads-tab .product-option .download-position i.option-edit, .product-downloads-tab .product-download .media-position i.option-edit, .product-downloads-tab .product-download .option-position i.option-edit, .product-downloads-tab .product-download .download-position i.option-edit { + font-size: 14px; + padding-right: 5px; + color: #e1e4e6; +} + +.product-media-tab .product-media .media-position i.option-edit:hover, .product-media-tab .product-media .option-position i.option-edit:hover, .product-media-tab .product-media .download-position i.option-edit:hover, .product-media-tab .product-option .media-position i.option-edit:hover, .product-media-tab .product-option .option-position i.option-edit:hover, .product-media-tab .product-option .download-position i.option-edit:hover, .product-media-tab .product-download .media-position i.option-edit:hover, .product-media-tab .product-download .option-position i.option-edit:hover, .product-media-tab .product-download .download-position i.option-edit:hover, .product-options-tab .product-media .media-position i.option-edit:hover, .product-options-tab .product-media .option-position i.option-edit:hover, .product-options-tab .product-media .download-position i.option-edit:hover, .product-options-tab .product-option .media-position i.option-edit:hover, .product-options-tab .product-option .option-position i.option-edit:hover, .product-options-tab .product-option .download-position i.option-edit:hover, .product-options-tab .product-download .media-position i.option-edit:hover, .product-options-tab .product-download .option-position i.option-edit:hover, .product-options-tab .product-download .download-position i.option-edit:hover, .product-downloads-tab .product-media .media-position i.option-edit:hover, .product-downloads-tab .product-media .option-position i.option-edit:hover, .product-downloads-tab .product-media .download-position i.option-edit:hover, .product-downloads-tab .product-option .media-position i.option-edit:hover, .product-downloads-tab .product-option .option-position i.option-edit:hover, .product-downloads-tab .product-option .download-position i.option-edit:hover, .product-downloads-tab .product-download .media-position i.option-edit:hover, .product-downloads-tab .product-download .option-position i.option-edit:hover, .product-downloads-tab .product-download .download-position i.option-edit:hover { + color: #4ab46d; +} + +.link1, .link2 { + text-decoration: none; + color: var(--color-accent-blue); + border-bottom: 1px dotted; + margin: 0 5px 0 0; +} + +.link1:hover, .link2:hover { + color: var(--color-accent-blue-hover); +} + +.link2 { + color: var(--color-accent-red); +} + +.link2:hover { + color: var(--color-accent-red-hover); +} + +.title1 { + color: #6a6e75; + border-bottom: 1px solid #f0f1f2; + margin-bottom: 15px; + padding-bottom: 15px; + font-weight: 500; +} + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + text-decoration: none; + appearance: none; + cursor: pointer; + border: 0; + background: var(--color-selected-bg); + color: var(--color-bg-body); + padding: 0 14px; + font-size: 14px; + font-weight: 600; + border-radius: 4px; + height: 38px; + margin: 2px; + font-style: italic; +} + +.btn2 { + display: inline-flex; + align-items: center; + justify-content: center; + text-decoration: none; + appearance: none; + cursor: pointer; + border: 0; + background: var(--color-btn-secondary); + color: var(--color-bg-body); + padding: 0px 10px; + font-size: 14px; + font-weight: 600; + border-radius: 4px; + height: 20px; + margin: 2px; + font-style: italic; +} + +a.btn:after{ + content: ' '; + display: inline-block; + border-bottom: 1px solid var(--color-bg-body); + border-right: 1px solid var(--color-bg-body); + height: 8px; + width: 8px; + transform: rotate(-45deg); + margin-left: 1rem; +} + +.btn_link { + display: inline-flex; + align-items: center; + justify-content: center; + text-decoration: none; + appearance: none; + cursor: pointer; + border: 0; + background: var(--color-btn-neutral); + color: #75797e; + padding: 0 14px; + font-size: 12px; + font-weight: 600; + border-radius: 4px; + height: 30px; + margin: 2px; + font-style: italic; +} + +a.btn_link:after{ + content: ' '; + display: inline-block; + border-bottom: 1px solid #75797e; + border-right: 1px solid #75797e; + height: 8px; + width: 8px; + transform: rotate(-45deg); + margin-left: 1rem; +} + +.btn:hover { + background: var(--color-btn-hover-bg); +} + +.btn.green { + background: #4ab46d; +} + +.btn.green:hover { + background: #46a966; +} + +.btn.red { + background: #b44a4a; +} + +.btn.red:hover { + background: #a94646; +} + +.btn.alt { + color: #75797e; + border: 1px solid #d4dbde; + box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.03); + background: var(--color-btn-neutral); +} + +.btn.alt:hover { + background: #eef1f2; +} + +.btn.disabled { + background: var(--color-disabled); +} + +.btn.disabled:hover { + background: var(--color-disabled-hover); +} + +.btn.small { + padding: 8px 12px; + font-size: 12px; +} + +.btn .loader, +.btn .loader::after { + width: 15px; + height: 15px; +} + +.btn .loader { + margin: 0; + border-top: 2px solid rgba(255, 255, 255, 0.4); + border-right: 2px solid rgba(255, 255, 255, 0.4); + border-bottom: 2px solid rgba(255, 255, 255, 0.4); + border-left: 2px solid rgba(255, 255, 255, 0.9); +} + +.dialog { + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + position: fixed; + top: 0; + left: 0; + display: none; + z-index: 999999; + align-items: center; + justify-content: center; +} + +.dialog .content { + transform: scale(0.5); + background-color: var(--color-bg-body); + box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.03); + width: 400px; +} + +.dialog .content .heading { + display: flex; + padding: 20px; + font-weight: 600; + justify-content: space-between; + border-bottom: 1px solid #ebeced; + align-items: center; +} + +.dialog .content .heading span { + font-size: 24px; + line-height: 24px; + padding-bottom: 4px; + cursor: pointer; + color: var(--color-title-muted); +} + +.dialog .content .heading span:hover { + color: var(--color-hover-dark); +} + +.dialog .content .footer { + border-top: 1px solid #ebeced; + background-color: var(--color-hover-light); +} + +.dialog.large .content { + width: 900px; +} + +.dialog.medium .content { + width: 600px; +} + +.dialog.open { + display: flex; +} + +.dialog.open .content { + transform: scale(1); + transition: all 0.2s ease; +} + +.media-library-modal .media { + display: flex; +} + +.media-library-modal .media .list { + display: flex; + flex-flow: wrap; + align-items: flex-start; + align-content: flex-start; + width: 75%; + height: 460px; + overflow-y: auto; + scrollbar-width: thin; + padding: 10px; +} + +.media-library-modal .media .list > a { + position: relative; + display: flex; + height: 100px; + width: 23.4%; + border: 1px solid #ddd; + padding: 5px; + margin: 5px; +} + +.media-library-modal .media .list > a img { + width: 100%; + height: 100%; + object-fit: contain; +} + +.media-library-modal .media .list > a.selected { + border: 2px solid #237fe8; +} + +.media-library-modal .media .list > a.selected::before { + position: absolute; + font-family: 'Font Awesome 5 Free'; + content: '\f00c'; + display: inline-block; + vertical-align: middle; + font-weight: 900; + bottom: 0; + right: 0; + color: var(--color-bg-body); + background-color: #237fe8; + font-size: 12px; + padding: 4px 4px 1px 4px; +} + +.media-library-modal .media .list .list-header { + display: flex; + width: 100%; + justify-content: space-between; + padding: 5px 5px 10px 5px; +} + +.media-library-modal .media .list .list-header input { + border: 0; + padding: 5px 0; + border-bottom: 1px solid var(--color-tab-bg); +} + +.media-library-modal .media .details { + width: 25%; + padding: 15px; + background-color: #f9f9fa; + border-left: 1px solid #ebeced; + height: 460px; + overflow-y: auto; + scrollbar-width: thin; +} + +.media-library-modal .media .details h3 { + margin: 0; + padding: 0; + font-size: 14px; + word-break: break-all; +} + +.media-library-modal .media .details label { + display: block; + margin: 0; + padding: 15px 0 0 0; + font-size: 14px; + font-weight: 500; +} + +.media-library-modal .media .details img { + max-width: 100%; + max-height: 100px; + padding-top: 10px; +} + +.media-library-modal .media .details input { + border: 0; + padding: 5px 0; + background: transparent; + width: 100%; + font-size: 14px; + border-bottom: 1px solid var(--color-tab-bg); +} + +.media-library-modal .media .details .media-links { + padding-top: 15px; +} + +.media-library-modal .media .details .media-links .save-media { + display: none; +} + +.media-library-modal .media .details .media-links a { + font-size: 14px; +} + +.media-library-modal .media .list::-webkit-scrollbar, .media-library-modal .media .details::-webkit-scrollbar { + width: 6px; + background: var(--color-bg-body); +} + +.media-library-modal .media .list::-webkit-scrollbar-thumb, .media-library-modal .media .details::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.1); + border-radius: 50px; +} + +.options-modal .body { + max-height: 400px; + overflow-y: auto; + padding: 20px; +} + +.options-modal .body input[type="text"], .options-modal .body input[type="number"], .options-modal .body input[type="datetime-local"], .options-modal .body select { + border: 0; + padding: 10px 0; + height: 40px; + background: transparent; + width: 100%; + border-bottom: 1px solid var(--color-tab-bg); +} + +.options-modal .body select { + padding: 0; +} + +.options-modal .body .option-header { + display: flex; + width: 70%; +} + +.options-modal .body .option-header input { + margin-right: 10px; +} + +.options-modal .body .option-header label { + display: flex; + justify-content: center; + align-items: center; + padding-left: 10px; +} + +.options-modal .body .option-content { + display: none; + flex-flow: column; +} + +.options-modal .body .table { + width: 100%; + padding: 0; +} + +.options-modal .body .table .input-group { + display: flex; +} + +.options-modal .body .table tbody td input { + font-size: 14px; +} + +.options-modal .body .table tbody td { + padding-right: 10px; +} + +.options-modal .body .table tbody td .modifier { + width: 45px; +} + +.options-modal .body .table tbody td .modifier ~ input { + padding-left: 5px; +} + +.options-modal .body .add-option-value-btn { + text-decoration: none; + font-weight: 400; + color: #676d72; + font-size: 14px; + padding: 15px 0; +} + +.options-modal .body .add-option-value-btn i { + padding-right: 5px; +} + +.options-modal .body .add-option-value-btn:hover { + color: #4f5357; +} + +.options-modal .body .remove-option-value { + cursor: pointer; + color: #676d72; +} + +.options-modal .body .remove-option-value:hover { + color: #b44a4a; +} + +.downloads-modal .body { + padding: 20px; +} + +.downloads-modal .body input { + border: 0; + padding: 10px 0; + height: 40px; + font-size: 14px; + background: transparent; + width: 100%; + border-bottom: 1px solid var(--color-tab-bg); +} + +.downloads-modal .body label { + font-weight: 500; +} + +.edit-media-modal .body { + padding: 20px; +} + +.edit-media-modal .body label { + display: block; + margin: 0; + padding: 15px 0 0 0; + font-size: 14px; + font-weight: 500; +} + +.edit-media-modal .body img { + max-width: 100%; + max-height: 100px; + padding-top: 10px; +} + +.edit-media-modal .body input { + border: 0; + padding: 5px 0; + background: transparent; + width: 100%; + font-size: 14px; + border-bottom: 1px solid var(--color-tab-bg); +} + +.loader, +.loader::after { + border-radius: 50%; + width: 50px; + height: 50px; +} + +.loader { + position: relative; + margin: 60px auto; + border-top: 2px solid rgba(154, 158, 162, 0.2); + border-right: 2px solid rgba(154, 158, 162, 0.2); + border-bottom: 2px solid rgba(154, 158, 162, 0.2); + border-left: 2px solid #9a9ea2; + transform: translateZ(0); + animation: loader 1.1s infinite linear; +} + +@keyframes loader { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.pad-1 { + padding: 5px; +} + +.mar-1 { + margin: 5px; +} + +.pad-2 { + padding: 10px; +} + +.mar-2 { + margin: 10px; +} + +.pad-3 { + padding: 15px; +} + +.mar-3 { + margin: 15px; +} + +.pad-4 { + padding: 20px; +} + +.mar-4 { + margin: 20px; +} + +.pad-5 { + padding: 25px; +} + +.mar-5 { + margin: 25px; +} + +.pad-bot-1 { + padding-bottom: 5px; +} + +.pad-top-1 { + padding-top: 5px; +} + +.pad-left-1 { + padding-left: 5px; +} + +.pad-right-1 { + padding-right: 5px; +} + +.pad-x-1 { + padding-left: 5px; + padding-right: 5px; +} + +.pad-y-1 { + padding-top: 5px; + padding-bottom: 5px; +} + +.mar-bot-1 { + margin-bottom: 5px; +} + +.mar-top-1 { + margin-top: 5px; +} + +.mar-left-1 { + margin-left: 5px; +} + +.mar-right-1 { + margin-right: 5px; +} + +.mar-x-1 { + margin-top: 5px; + margin-bottom: 5px; +} + +.mar-y-1 { + margin-left: 5px; + margin-right: 5px; +} + +.pad-bot-2 { + padding-bottom: 10px; +} + +.pad-top-2 { + padding-top: 10px; +} + +.pad-left-2 { + padding-left: 10px; +} + +.pad-right-2 { + padding-right: 10px; +} + +.pad-x-2 { + padding-left: 10px; + padding-right: 10px; +} + +.pad-y-2 { + padding-top: 10px; + padding-bottom: 10px; +} + +.mar-bot-2 { + margin-bottom: 10px; +} + +.mar-top-2 { + margin-top: 10px; +} + +.mar-left-2 { + margin-left: 10px; +} + +.mar-right-2 { + margin-right: 10px; +} + +.mar-x-2 { + margin-top: 10px; + margin-bottom: 10px; +} + +.mar-y-2 { + margin-left: 10px; + margin-right: 10px; +} + +.pad-bot-3 { + padding-bottom: 15px; +} + +.pad-top-3 { + padding-top: 15px; +} + +.pad-left-3 { + padding-left: 15px; +} + +.pad-right-3 { + padding-right: 15px; +} + +.pad-x-3 { + padding-left: 15px; + padding-right: 15px; +} + +.pad-y-3 { + padding-top: 15px; + padding-bottom: 15px; +} + +.mar-bot-3 { + margin-bottom: 15px; +} + +.mar-top-3 { + margin-top: 15px; +} + +.mar-left-3 { + margin-left: 15px; +} + +.mar-right-3 { + margin-right: 15px; +} + +.mar-x-3 { + margin-top: 15px; + margin-bottom: 15px; +} + +.mar-y-3 { + margin-left: 15px; + margin-right: 15px; +} + +.pad-bot-4 { + padding-bottom: 20px; +} + +.pad-top-4 { + padding-top: 20px; +} + +.pad-left-4 { + padding-left: 20px; +} + +.pad-right-4 { + padding-right: 20px; +} + +.pad-x-4 { + padding-left: 20px; + padding-right: 20px; +} + +.pad-y-4 { + padding-top: 20px; + padding-bottom: 20px; +} + +.mar-bot-4 { + margin-bottom: 20px; +} + +.mar-top-4 { + margin-top: 20px; +} + +.mar-left-4 { + margin-left: 20px; +} + +.mar-right-4 { + margin-right: 20px; +} + +.mar-x-4 { + margin-top: 20px; + margin-bottom: 20px; +} + +.mar-y-4 { + margin-left: 20px; + margin-right: 20px; +} + +.pad-bot-5 { + padding-bottom: 25px; +} + +.pad-top-5 { + padding-top: 25px; +} + +.pad-left-5 { + padding-left: 25px; +} + +.pad-right-5 { + padding-right: 25px; +} + +.pad-x-5 { + padding-left: 25px; + padding-right: 25px; +} + +.pad-y-5 { + padding-top: 25px; + padding-bottom: 25px; +} + +.mar-bot-5 { + margin-bottom: 25px; +} + +.mar-top-5 { + margin-top: 25px; +} + +.mar-left-5 { + margin-left: 25px; +} + +.mar-right-5 { + margin-right: 25px; +} + +.mar-x-5 { + margin-top: 25px; + margin-bottom: 25px; +} + +.mar-y-5 { + margin-left: 25px; + margin-right: 25px; +} + +@media screen and (max-width: 1000px) { + header { + padding-left: 0; + } + .responsive-hidden { + display: none !important; + } + .responsive-width-100 { + width: 100% !important; + flex: auto !important; + flex-basis: 100% !important; + } + .responsive-flex-column { + flex-flow: column; + } + .responsive-flex-wrap { + flex-flow: wrap; + } + .responsive-flex { + display: flex; + } + main { + padding: 70px 7px 20px 7px; + } + main .content-header { + max-width: 100%; + } + main .content-header form { + padding-top: 10px; + } + main .content-header form .search input, main .content-header form > select { + padding-right: 0; + margin-left: 0; + } + main .content-block { + padding: 5px; + } + main .content-block .block-header { + padding-top: 10px; + } + main .dashboard { + flex-flow: column; + } + main .dashboard .stat { + width: 100%; + } + .responsive-pad-1 { + padding: 5px; + } + .responsive-mar-1 { + margin: 5px; + } + .responsive-pad-2 { + padding: 10px; + } + .responsive-mar-2 { + margin: 10px; + } + .responsive-pad-3 { + padding: 15px; + } + .responsive-mar-3 { + margin: 15px; + } + .responsive-pad-4 { + padding: 20px; + } + .responsive-mar-4 { + margin: 20px; + } + .responsive-pad-5 { + padding: 25px; + } + .responsive-mar-5 { + margin: 25px; + } + .responsive-pad-bot-1 { + padding-bottom: 5px; + } + .responsive-pad-top-1 { + padding-top: 5px; + } + .responsive-pad-left-1 { + padding-left: 5px; + } + .responsive-pad-right-1 { + padding-right: 5px; + } + .responsive-pad-x-1 { + padding-left: 5px; + padding-right: 5px; + } + .responsive-pad-y-1 { + padding-top: 5px; + padding-bottom: 5px; + } + .responsive-mar-bot-1 { + margin-bottom: 5px; + } + .responsive-mar-top-1 { + margin-top: 5px; + } + .responsive-mar-left-1 { + margin-left: 5px; + } + .responsive-mar-right-1 { + margin-right: 5px; + } + .responsive-mar-x-1 { + margin-top: 5px; + margin-bottom: 5px; + } + .responsive-mar-y-1 { + margin-left: 5px; + margin-right: 5px; + } + .responsive-pad-bot-2 { + padding-bottom: 10px; + } + .responsive-pad-top-2 { + padding-top: 10px; + } + .responsive-pad-left-2 { + padding-left: 10px; + } + .responsive-pad-right-2 { + padding-right: 10px; + } + .responsive-pad-x-2 { + padding-left: 10px; + padding-right: 10px; + } + .responsive-pad-y-2 { + padding-top: 10px; + padding-bottom: 10px; + } + .responsive-mar-bot-2 { + margin-bottom: 10px; + } + .responsive-mar-top-2 { + margin-top: 10px; + } + .responsive-mar-left-2 { + margin-left: 10px; + } + .responsive-mar-right-2 { + margin-right: 10px; + } + .responsive-mar-x-2 { + margin-top: 10px; + margin-bottom: 10px; + } + .responsive-mar-y-2 { + margin-left: 10px; + margin-right: 10px; + } + .responsive-pad-bot-3 { + padding-bottom: 15px; + } + .responsive-pad-top-3 { + padding-top: 15px; + } + .responsive-pad-left-3 { + padding-left: 15px; + } + .responsive-pad-right-3 { + padding-right: 15px; + } + .responsive-pad-x-3 { + padding-left: 15px; + padding-right: 15px; + } + .responsive-pad-y-3 { + padding-top: 15px; + padding-bottom: 15px; + } + .responsive-mar-bot-3 { + margin-bottom: 15px; + } + .responsive-mar-top-3 { + margin-top: 15px; + } + .responsive-mar-left-3 { + margin-left: 15px; + } + .responsive-mar-right-3 { + margin-right: 15px; + } + .responsive-mar-x-3 { + margin-top: 15px; + margin-bottom: 15px; + } + .responsive-mar-y-3 { + margin-left: 15px; + margin-right: 15px; + } + .responsive-pad-bot-4 { + padding-bottom: 20px; + } + .responsive-pad-top-4 { + padding-top: 20px; + } + .responsive-pad-left-4 { + padding-left: 20px; + } + .responsive-pad-right-4 { + padding-right: 20px; + } + .responsive-pad-x-4 { + padding-left: 20px; + padding-right: 20px; + } + .responsive-pad-y-4 { + padding-top: 20px; + padding-bottom: 20px; + } + .responsive-mar-bot-4 { + margin-bottom: 20px; + } + .responsive-mar-top-4 { + margin-top: 20px; + } + .responsive-mar-left-4 { + margin-left: 20px; + } + .responsive-mar-right-4 { + margin-right: 20px; + } + .responsive-mar-x-4 { + margin-top: 20px; + margin-bottom: 20px; + } + .responsive-mar-y-4 { + margin-left: 20px; + margin-right: 20px; + } + .responsive-pad-bot-5 { + padding-bottom: 25px; + } + .responsive-pad-top-5 { + padding-top: 25px; + } + .responsive-pad-left-5 { + padding-left: 25px; + } + .responsive-pad-right-5 { + padding-right: 25px; + } + .responsive-pad-x-5 { + padding-left: 25px; + padding-right: 25px; + } + .responsive-pad-y-5 { + padding-top: 25px; + padding-bottom: 25px; + } + .responsive-mar-bot-5 { + margin-bottom: 25px; + } + .responsive-mar-top-5 { + margin-top: 25px; + } + .responsive-mar-left-5 { + margin-left: 25px; + } + .responsive-mar-right-5 { + margin-right: 25px; + } + .responsive-mar-x-5 { + margin-top: 25px; + margin-bottom: 25px; + } + .responsive-mar-y-5 { + margin-left: 25px; + margin-right: 25px; + } + .media-library-modal { + padding: 10px; + } + .media-library-modal .media { + flex-flow: column; + } + .media-library-modal .media .list { + width: 100%; + height: 220px; + } + .media-library-modal .media .list > a { + width: 29%; + } + .media-library-modal .media .list .list-header input { + max-width: 150px; + width: auto; + } + .media-library-modal .media .details { + width: 100%; + height: 110px; + } + .options-modal { + padding: 10px; + } + .options-modal .body { + max-height: 260px; + } + .options-modal .body .option-header { + width: 100%; + } + .options-modal .body .table thead td { + font-size: 12px; + } + .options-modal .body .table tbody td input { + font-size: 12px; + } + .options-modal .body .table tbody td { + padding-right: 5px; + } + .options-modal .body .table tbody td .modifier { + width: 45px; + } + .options-modal .body .table tbody td .modifier ~ input { + padding-left: 0; + } + .downloads-modal { + padding: 10px; + } + .content-block-wrapper { + flex-flow: column; + } + .content-block-wrapper .content-block { + margin: 15px 0 0 0 !important; + } +} + +#tableView thead { + background-color: #ffd717; + color: var(--color-selected-bg); +} + +#tableView thead td { + font-weight: bold; +} + +#tableView { + border-collapse: collapse; + width: 100%; + border-radius: 5px; + border-style: hidden; + margin: auto; + margin-top: 10px; + background-color: var(--color-bg-body); +} + +#content { + margin-top:20px; + width: 90%; + margin: auto; +} + +.datalist { + width: 100%; + padding: 15px 5px; + margin-bottom: 25px; + border: 0; + border-bottom: 1px solid var(--color-tab-bg); +} + +#readBar { + width:1%; + height: 100%; + background-color: #04aa6d; + text-align: center; + color: white; + border-radius: 5px; + padding: 5px; +} + +input[type='file'] { + display: inline-block; + width: 100%; + padding: 120px 0 0 0; + height: 100px; + overflow: hidden; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + background: url('https://cdn1.iconfinder.com/data/icons/hawcons/32/698394-icon-130-cloud-upload-512.png') center center no-repeat #e4e4e4; + border-radius: 20px; + background-size: 60px 60px; +} + +.dot { + height: 15px; + width: 15px; + font-size: 10px; + color: var(--color-border-light); + display: table-cell; + text-align: center; + vertical-align: middle; + border-radius: 50%; + background-color: #bbb; + } + + .content-wrapper { + width: 1050px; + margin: 0 auto; + } + + .content-wrapper.error { + padding: 40px 0; + } + + .recentlyadded .products, main .products .products-wrapper { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + padding: 40px 0 0 0; + } + + .recentlyadded .products .product, main .products .products-wrapper .product { + display: block; + overflow: hidden; + text-decoration: none; + padding-bottom: 20px; + margin-left: 10px; + text-align: center; + } + .recentlyadded .products .product img, main .products .products-wrapper .product img { + transform: scale(1); + transition: transform 1s; + mix-blend-mode: multiply; + } + + .recentlyadded .products .product .name, main .products .products-wrapper .product .name { + display: block; + color: var(--color-text-default); + font-size: 12px; + text-align: center; + } + + main > .products .products-header { + display: flex; + justify-content: space-between; + align-items: center; + padding-bottom: 40px; + font-size: 10px; + } + + main > .products .products-header label { + padding-left: 20px; + font-size: 10px; + } + + main > .products .products-header select { + padding: 5px; + margin-left: 15px; + border: 1px solid #d5d5d5; + color: var(--color-text-default); + border-radius: 4px; + font-size: 10px; + } + + .product_category_nav { + display: inline-block; + } + + .product_category_nav a { + white-space: nowrap; + text-decoration: none; + color: var(--color-bg-body); + padding: 5px 5px; + margin: 5px; + border: 1px solid var(--color-text-default); + border-radius: 5px; + display: block; + float: left; + background:var(--color-btn-bg); + } + + /* Responsive CSS below */ + @media screen and (max-width: 1050px) { + .content-wrapper { + width: 100%; + margin: 0 auto; + } + + .recentlyadded .products, main .products .products-wrapper { + justify-content: center; + } + .recentlyadded .products .product, main .products .products-wrapper .product { + width: auto; + } + .products .products-header { + flex-flow: column; + } + .products .products-header p { + padding-bottom: 10px; + } + } + + th a { + text-decoration: none; + color: var(--color-text-default); + text-transform: capitalize; +} + +.chart { + display:table; + table-layout: fixed; + width:60%; + max-width:500px; +} + +.chart li{ + position:relative; + display:table-cell; + vertical-align:bottom; + height:100px; + color: var(--color-selected-bg); +} + +.chart span{ + margin:0 1em; + display: block; + background-color: var(--color-selected-bg); + animation: draw 1s ease-in-out; + border-radius: 3px; +} + +.chart span::before{ + position:absolute; + left:0;right:0;top:100%; + padding:5px; + display:block; + color: var(--color-selected-bg); + text-align:center; + content:attr(title); + word-wrap: break-word; + font-size: 10px; +} + +.servicereport { + border-collapse: collapse; + width: 100%; + border-radius: 5px; + border-style: hidden; + margin: auto; + background-color: var(--color-bg-body); + margin-top: 20px; +} + +.servicereport.fs-normal td { + font-size: 18px; +} + +.servicereport.fs-small td { + font-size: 14px; +} + +.servicereport thead { + background-color: var(--color-selected-bg); + color: var(--color-bg-body); +} + +.servicereport td { + padding: 5px 15px; +} + +.servicereport thead td { + font-weight: bold; +} + +.servicereport tbody td.success { + text-decoration: underline; + text-decoration-thickness: 2px; + text-decoration-color: #3ead48; + text-underline-offset: 2px; +} + +.servicereport tbody td.warning { + text-decoration: underline; + text-decoration-thickness: 2px; + text-decoration-color: #ffc107; + text-underline-offset: 2px; +} + +.servicereport tbody td.error { + text-decoration: underline; + text-decoration-thickness: 2px; + text-decoration-color: #d1232a; + text-underline-offset: 2px; +} + +.servicereport .col-key { + width: 70%; +} + +.servicereport .col-value { + width: 30%; +} + +.servicereport--details tbody tr { + border-bottom: 1px solid #035754; +} + +.servicereport--striped tbody tr:nth-child(odd) { + background-color: #f3fafa; +} + +.servicereport--striped tbody tr:nth-child(even) { + background-color: #e7f6f6; +} + +.servicereport--striped tbody tr { + border-bottom: 1px solid #9bdcd9; +} + +.service_summary { + background-image: linear-gradient(#e7f6f6, #fafdfd); + margin-top: 20px; + padding-bottom: 15px; +} + +.service_summary__title { + background-color: #035754; + color: var(--color-bg-body); + font-weight: bold; + padding: 5px 15px; +} + +.service_notes { + margin: 15px 15px 0 15px; + padding: 15px; + background-color: var(--color-bg-body); + color: #035754; +} + +h4.label { + padding: 5px 15px; + background-color: #035754; + color: var(--color-bg-body); + margin-top: 20px; + margin-bottom: 20px; +} + +.text-center { + text-align: center; +} + +@media screen and (max-width: 600px) { +#support_btn { + display:none; + } +} +.form-popup { + display: none; + position: fixed; + bottom: 0; + right: 15px; + z-index: 9; +} + +.close { + position: absolute; + top: 0px; + right: 0px; +} + +#support_btn img:hover{ + transform: scale(1.5); +} + +.cancel_link { + white-space: nowrap; + text-align: center; + background: #b44a4a; + border: 0; + padding: 5px 0; + font-size: 9px; + margin-top: 5px; + cursor: pointer; + border-radius: 4px; +} + +.order-detail input { + border: 0; + border-bottom: 1px solid var(--color-tab-bg) +} + +.loading-container { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.8); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 9999; + opacity: 0; + visibility: hidden; + transition: opacity 0.3s, visibility 0.3s; +} + +.loading-container.active { + opacity: 1; + visibility: visible; +} + +.loading-bar { + width: 200px; + height: 10px; + background-color: #f0f0f0; + border-radius: 5px; + overflow: hidden; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); +} + +.progress { + height: 100%; + width: 0%; + background-color: #4caf50; + animation: progressAnimation 2s infinite ease-in-out; +} + +.loading-text { + margin-top: 10px; + font-size: 14px; + color: #333; +} + +@keyframes progressAnimation { + 0% { width: 0%; } + 50% { width: 100%; } + 100% { width: 0%; } +} + +/* Hide the loading bar when page is loaded */ +.loaded .loading-container { + display: none; +} + +.hidden { + display: none !important; +} + +/*CATALOG VIEW*/ + +.stock p { + font-size: 10px; + margin: auto; + padding: 5px; + width: fit-content; + border-radius: 5px; + color: white; +} + +.stock p:before { + content : '\1F4E6'; +} + +.filtersection { + display: flex; + margin: 0 auto; + background: white; + border-radius: 12px; + margin-bottom: 50px; + padding: 10px; + width: 95%; +} + +.filter-section { + width: 250px; + padding: 20px; + border-right: 1px solid #e0e0e0; +} + +.filter-section h2 { + margin-bottom: 15px; + color: #333; +} + +.filter-group { + margin-bottom: 20px; +} + +.filter-group label { + margin-bottom: 10px; +} + +.filter-group input[type="checkbox"] { + margin-right: 10px; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .filtersection { + flex-direction: column; + text-align: center; + } + + .filter-section { + width: 100%; + border-right: none; + border-bottom: 1px solid #e0e0e0; + } +} + +main .recentlyadded .products .product, main .products .products-wrapper .product { + display: block; + overflow: hidden; + text-decoration: none; + padding-bottom: 30px; + text-align: center; + margin: 5px; +} + +main .recentlyadded .products .product img, main .products .products-wrapper .product img { + transform: scale(1); + transition: transform 1s; +} + +main .products .product .name, main .products .products-wrapper .product .name { + display: block; + color: var(--color-text-default); + padding: 20px 0 2px 0; + text-align: center; + font-family: 'gerb'; + word-wrap: break-word; +} +main .products .product .price, main .products .products-wrapper .product .price { + display: block; + color: #999999; + text-align: center; +} + +.product .rrp { + color: #bbbbbb; + text-decoration: line-through; + font-size: 22px; + padding-left: 10px; +} + +.img_config { + border-radius:5px; + width: 25px; + height: 25px; + margin: 1px; +} + +/* Button alignment styles */ +.form-actions, +.modal-actions, +.dialog-actions, +.button-actions { + display: flex; + gap: 10px; + justify-content: flex-end; + align-items: center; + margin-top: 20px; +} + +.title-actions { + display: flex; + gap: 10px; + align-items: center; + justify-content: flex-end; +} + +.filter-actions { + display: flex; + gap: 10px; + justify-content: flex-end; + align-items: center; + flex-wrap: wrap; +} + +main .form .button-container, +main .form .form-actions, +main .content-block .button-container { + display: flex; + gap: 10px; + justify-content: flex-end; + align-items: center; + margin-top: 15px; +} + +.dialog .content .footer, +.modal .modal-footer { + display: flex; + gap: 10px; + justify-content: flex-end; + align-items: center; + padding: 20px; + border-top: 1px solid #eee; +} \ No newline at end of file diff --git a/custom/morvalwatches/style/veliti_intro.png b/custom/morvalwatches/style/veliti_intro.png new file mode 100644 index 0000000..60b2864 Binary files /dev/null and b/custom/morvalwatches/style/veliti_intro.png differ diff --git a/dashboard.php b/dashboard.php index da7f6a1..7328425 100644 --- a/dashboard.php +++ b/dashboard.php @@ -1,7 +1,7 @@ '', 'full_path' =>'', 'created' => '', - 'createdby' => $_SESSION['username'], + 'createdby' => $_SESSION['authorization']['clientID'], 'updated' => '', 'updatedby' => '' ]; @@ -148,7 +148,7 @@ if ($delete_allowed === 1){ $view .= ''; } if ($update_allowed === 1){ - $view .= ''; + $view .= ''; } $view .= '
'; diff --git a/dealers.php b/dealers.php index 7ff341a..7612a80 100644 --- a/dealers.php +++ b/dealers.php @@ -1,7 +1,7 @@ '', @@ -120,7 +120,7 @@ if ($delete_allowed === 1){ $view .= ''; } if ($update_allowed === 1){ - $view .= ''; + $view .= ''; } $view .= ''; diff --git a/discounts.php b/discounts.php index b30eedc..f4409fc 100644 --- a/discounts.php +++ b/discounts.php @@ -1,7 +1,7 @@ equipmentID; +$api_url = '/v2/equipment_data/equipmentid='.$responses->equipmentID; $equipment_data = ioServer($api_url,''); //Decode Payload -if (!empty($equipment_data )){$equipment_data = decode_payload($equipment_data );}else{$equipment_data = null;} +if (!empty($equipment_data )){$equipment_data = json_decode($equipment_data );}else{$equipment_data = null;} + +//CALL TO API FOR CUSTOMER AND WARRANTY DATA (type = customer,Warranty in history) +$api_url = '/v2/history/equipmentID='.$responses->equipmentID.'&type=customer,Warranty'; +$registration_data = ioServer($api_url,''); + +//Decode Payload +if (!empty($registration_data)){$registration_data = json_decode($registration_data);}else{$registration_data = null;} + +// Separate customer and warranty data +$latest_customer = null; +$latest_warranty = null; + +if (is_array($registration_data) && count($registration_data) > 0) { + foreach ($registration_data as $record) { + if ($record->type === 'Customer' && $latest_customer === null) { + $latest_customer = $record; + } + if ($record->type === 'Warranty' && $latest_warranty === null) { + $latest_warranty = $record; + } + } +} //GET PRODUCTS_SOFTWARE -$api_url = '/v1/products_software/productrowid='.$responses->productrowid.'&status=1'; +$api_url = '/v2/products_software_versions/hw_version='.$responses->hw_version.'&status=1'; $products_software = ioServer($api_url,''); //Decode Payload -if (!empty($products_software)){$products_software = decode_payload($products_software);}else{$products_software = null;} +if (!empty($products_software)){$products_software = json_decode($products_software);}else{$products_software = null;} //------------------------------ //Variables @@ -74,8 +96,19 @@ $warrantydate = warrantyStatus($responses->warranty_date); $service_date_due = serviceStatus($responses->service_date); $firmware_status = availableFirmware($responses->sw_version, $responses->sw_version_latest); +// Get upgrade version text if exists +$sw_version_upgrade_text = ''; +if (!empty($responses->sw_version_upgrade) && isset($products_software) && $products_software != '') { + foreach ($products_software as $products_soft) { + if ($products_soft->rowID == $responses->sw_version_upgrade) { + $sw_version_upgrade_text = $products_soft->version; + break; + } + } +} + //Calculate Healthindex based on last test -$total_score = assetHealthIndex($_SESSION['profile'],$_SESSION['permission'],$equipment_data,0); +$total_score = assetHealthIndex($_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],$equipment_data,0); //GetPartnerDetails $partner_data = json_decode($responses->accounthierarchy); @@ -95,15 +128,12 @@ if ($update_allowed === 1){ //GET ALL POST DATA $data = json_encode($_POST, JSON_UNESCAPED_UNICODE); - //Secure data - $payload = generate_payload($data); - //API call - $responses = ioServer('/v1/history', $payload); + $responses = ioServer('/v2/history', $data); if ($responses === 'NOK'){ } else { - header('Location: index.php?page=equipment&equipmentID='.$_POST['equipmentid'].'&success_msg=2'); + header('Location: index.php?page=equipment&equipmentID='.$_POST['equipmentid'].'&success_msg=2'); exit; } } @@ -133,7 +163,7 @@ $view = ' //------------------------------------ //CHECK IF USER IS ALSO CREATOR OF RECORD THEN OVERRIDE UPDATE_ALLOWED_EDIT //------------------------------------ -$equipment_owner = (($responses->createdby == $_SESSION['username'])? 1 : 0); +$equipment_owner = (($responses->createdby == $_SESSION['authorization']['clientID'])? 1 : 0); //------------------------------------ // //------------------------------------ @@ -163,7 +193,7 @@ $view .= '

'.$view_asset_data_ranking.'

-

'.$total_score.'

+

'.$total_score.'

'.$equipment_label2.'

@@ -175,11 +205,11 @@ $view .= '

'.$product_code.'

-

'.(($view_product == 1)? ''.$responses->productcode.'':$responses->productcode).'

+

'.(($view_product == 1)? ''.$responses->productcode.' ':$responses->productcode).'

'.$product_name.'

-

'.(($view_product == 1)? ''.(${$responses->productname} ?? $responses->productname).'':(${$responses->productname} ?? $responses->productname)).'

+

'.(($view_product == 1)? ''.(${$responses->productname} ?? $responses->productname).' ':(${$responses->productname} ?? $responses->productname)).'

'; if (!empty($media_responses['full_path'])){ $view .=' @@ -217,6 +247,9 @@ $view .='

'.$general_section.'

'.$section.'

+
+

'.(($latest_customer || $latest_warranty) ? ''.($register_title ?? 'Registration').' ' : '').'

+
'; $view .= ''; @@ -236,7 +269,7 @@ $view .= '
'.$software_status.' - '.$firmware_status.' + '.$firmware_status.((!empty($responses->sw_version_upgrade)) ? ' ' : '').' '.$equipment_label5.' @@ -246,6 +279,46 @@ $view .= '
'.$equipment_label6.' '.$responses->sw_version.' '; +//Check if license is attached +if (!empty($responses->sw_version_license)) { + $view .= ' + '.($equipment_license ?? 'License').' + '.$responses->sw_version_license; + + // Check if license is active + $current_date = date('Y-m-d H:i:s'); + $is_active = false; + + if (!empty($responses->license_status) && $responses->license_status == 1) { + $starts_at = $responses->starts_at ?? null; + $expires_at = $responses->expires_at ?? null; + + if ($starts_at && $expires_at) { + if ($current_date >= $starts_at && $current_date <= $expires_at) { + $is_active = true; + } + } elseif ($starts_at && !$expires_at) { + if ($current_date >= $starts_at) { + $is_active = true; + } + } elseif (!$starts_at && $expires_at) { + if ($current_date <= $expires_at) { + $is_active = true; + } + } elseif (!$starts_at && !$expires_at) { + $is_active = true; + } + } + + if ($is_active) { + $view .= ' / '.$enabled ?? 'Active'; + } else { + $view .= ' / '.$disabled ?? 'Inactive'; + } + + $view .= ' + '; +} //SHOW ONLY SW_UPGRADE WHEN SET if (isset($products_software) && $products_software !=''){ foreach ($products_software as $products_soft){ @@ -296,7 +369,7 @@ if (!empty($responses->geolocation) || $responses->geolocation != ''){ } //Get all related service events -if (isAllowed('servicereports',$_SESSION['profile'],$_SESSION['permission'],'R') === 1){ +if (isAllowed('servicereports',$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 1){ $service_events = serviceEvents($history,$page); $view .= '
@@ -309,7 +382,7 @@ $view .= '
} //Show equipment_data when available and allowed -if (isAllowed('equipment_data',$_SESSION['profile'],$_SESSION['permission'],'R') === 1 && !empty($equipment_data)){ +if (isAllowed('equipment_data',$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 1 && !empty($equipment_data)){ $view .= '
'.($view_asset_data_text ?? '').' @@ -328,8 +401,8 @@ $view .= '
$view .= ' '.$data->rowID.' - '.(($view_history == 1)? ''.$data->historyid.'':$data->historyid).' - '.$data->healthindex.' + '.(($view_history == 1)? ''.$data->historyid.' ':$data->historyid).' + '.$data->healthindex.' '; } $view .= ' @@ -367,9 +440,15 @@ $view .= '
'.getRelativeTime($responses->created).' - '.$general_updated.' - '.getRelativeTime($responses->updated).' - + '.$general_updated.''; + + if ($update_allowed === 1){ + $view .= ''.getRelativeTime($responses->updated).''; + } else { + $view .= ''.getRelativeTime($responses->updated).''; + } + + $view .= '
@@ -382,13 +461,13 @@ $shipto_id = explode("-",$partner_data->shipto) ?? ''; $partner_users_id = ($shipto_id[0] != '')? $shipto_id[0] : (($soldto_id[0] != '')? $soldto_id[0] : 1); $view_communication = ''; -if ($partner_users_id != 1 && ($_SESSION['permission'] == 3 || $_SESSION['permission'] == 4)){ +if ($partner_users_id != 1 && ($_SESSION['authorization']['permission'] == 3 || $_SESSION['authorization']['permission'] == 4)){ $view_communication = ' '.$button_partner_assigned_communication.''; } //DISPLAY RELATED USERS $view_users =''; -if ($partner_users_id != 1 && ($_SESSION['permission'] == 3 || $_SESSION['permission'] == 4)){ +if ($partner_users_id != 1 && ($_SESSION['authorization']['permission'] == 3 || $_SESSION['authorization']['permission'] == 4)){ $view_users = ' '.$button_partner_assigned_users.''; } @@ -403,12 +482,259 @@ $view .= '
if ($update_allowed === 1){ - $view .=''.$button_firmware.''; + $view .=''.$button_firmware.''; } $view .='
'; //OUTPUT -echo $view; +echo $view; + +// Add customer data modal if data exists +if ($latest_customer || $latest_warranty) { + $customer_payload = $latest_customer ? json_decode($latest_customer->description, true) : null; + $warranty_payload = $latest_warranty ? json_decode($latest_warranty->description, true) : null; + + echo ' +
+
+
+

'.($register_title ?? 'Registration').'

+ +
+ + + + + +
+
'; + + if ($warranty_payload) { + // Check if warranty is still active + $warranty_end_date = $warranty_payload['end_date'] ?? null; + $warranty_display = '-'; + $warranty_payload['createdby'] = $latest_warranty->createdby ?? '-'; + + if ($warranty_end_date) { + $end_date_obj = new DateTime(substr($warranty_end_date, 0, 10)); // Extract date part (YYYY-MM-DD) + $current_date_obj = new DateTime(date('Y-m-d')); + + if ($end_date_obj >= $current_date_obj) { + // Warranty is still active + $warranty_display = ($warranty_recent ?? 'Active') . ' - ' . htmlspecialchars($warranty_end_date); + } else { + // Warranty has expired + $warranty_display = ($warranty_outdated_text ?? 'Expired'); + } + } + + echo ' +
+ +

'.$warranty_display.'

+
+
+ +

'.htmlspecialchars($warranty_payload['organization'] ?? '-').'

+
+
+ +

+ '.htmlspecialchars($warranty_payload['createdby'] ?? '-').' +

+
+
+ +

'.htmlspecialchars($warranty_payload['phone'] ?? '-').'

+
+
+
+ +

'.htmlspecialchars($warranty_payload['city'] ?? '-').'

+
+
+ +

'.htmlspecialchars($warranty_payload['country'] ?? '-').'

+
+
+
+ +

'.getRelativeTime($latest_warranty->created).'

+
'; + } else { + echo ' +
+ +

No warranty information available

+
'; + } + + echo ' +
+
+ + + +
+
+ + + '; +} + +// Modal for log +echo ' + + +'; template_footer() diff --git a/equipment_data.php b/equipment_data.php index 174f861..598c92a 100644 --- a/equipment_data.php +++ b/equipment_data.php @@ -3,14 +3,14 @@ defined(page_security_key) or exit; $page = 'equipment_data'; //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 $equipment_data = [ @@ -18,7 +18,7 @@ $equipment_data = [ 'equipmentid' => '', 'historyid' => '', 'created' => '', - 'createdby' => $_SESSION['username'], + 'createdby' => $_SESSION['authorization']['clientID'], 'measurement' => '' ]; diff --git a/equipment_manage.php b/equipment_manage.php index a44a706..c077f6e 100644 --- a/equipment_manage.php +++ b/equipment_manage.php @@ -1,7 +1,7 @@ '', 'productrowid' => '', 'created' => '', - 'createdby' => $_SESSION['username'], + 'createdby' => $_SESSION['authorization']['clientID'], 'status' => 1, - 'accounthierarchy' => $_SESSION['partnerhierarchy'], + 'accounthierarchy' => $_SESSION['authorization']['partnerhierarchy'], 'serialnumber' => '', 'hw_version' => '', 'sw_version' => '', @@ -53,31 +53,31 @@ if ($equipment_ID !=''){ } //GET PRODUCTS -$api_url = '/v1/products/list='; +$api_url = '/v2/products/list='; $responses = ioServer($api_url,''); //Decode Payload -if (!empty($responses)){$products = decode_payload($responses);}else{$products = null;} +if (!empty($responses)){$products = json_decode($responses);}else{$products = null;} if (isset($_GET['equipmentID'])) { // ID param exists, edit an existing product //CALL TO API - $api_url = '/v1/equipments/equipmentID='.$equipment_ID; + $api_url = '/v2/equipments/equipmentID='.$equipment_ID; $responses = ioServer($api_url,''); //Decode Payload - if (!empty($responses)){$responses = decode_payload($responses);}else{$responses = null;} + if (!empty($responses)){$responses = json_decode($responses,true);}else{$responses = null;} - $equipment = json_decode(json_encode($responses[0]), true); + $equipment = $responses[0]; - //GET PRODUCTS_SOFTWARE - $api_url = '/v1/products_software/productrowid='.$equipment['productrowid'].'&status=1'; + //GET PRODUCTS_SOFTWARE_VERSIONS + $api_url = '/v2/products_software_versions/hw_version='.$equipment['hw_version'].'&status=1'; $products_software = ioServer($api_url,''); //Decode Payload - if (!empty($products_software)){$products_software = decode_payload($products_software);}else{$products_software = null;} + if (!empty($products_software)){$products_software = json_decode($products_software);}else{$products_software = null;} //------------------------------------ //CHECK IF USER IS ALSO CREATOR OF RECORD THEN OVERRIDE UPDATE_ALLOWED //------------------------------------ - $equipment_owner = (($equipment['createdby'] == $_SESSION['username'])? 1 : 0); + $equipment_owner = (($equipment['createdby'] == $_SESSION['authorization']['clientID'])? 1 : 0); if ($update_allowed === 1 || $equipment_owner === 1 || $update_allowed_special === 1){ if (isset($_POST['submit'])) { @@ -85,15 +85,14 @@ if (isset($_GET['equipmentID'])) { $_POST['geolocation'] = json_encode($_POST['geolocation'],JSON_UNESCAPED_UNICODE); //GET ALL POST DATA $data = json_encode($_POST, JSON_UNESCAPED_UNICODE); - //Secure data - $payload = generate_payload($data); //API call - $responses = ioServer('/v1/equipments', $payload); + $responses = ioServer('/v2/equipments', $data); + if ($responses === 'NOK'){ } else { - header('Location: index.php?page=equipment&equipmentID='.$equipment_ID.'&success_msg=2'); - exit; + header('Location: index.php?page=equipment&equipmentID='.$equipment_ID.'&success_msg=2'); + exit; } } } @@ -102,10 +101,8 @@ if (isset($_GET['equipmentID'])) { if (isset($_POST['delete'])) { //GET ALL POST DATA $data = json_encode($_POST , JSON_UNESCAPED_UNICODE); - //Secure data - $payload = generate_payload($data); //API call - $responses = ioServer('/v1/equipments', $payload); + $responses = ioServer('/v2/equipments', $data); // Redirect and delete equipment if ($responses === 'NOK'){ @@ -123,10 +120,8 @@ if (isset($_GET['equipmentID'])) { $_POST['geolocation'] = json_encode($_POST['geolocation'],JSON_UNESCAPED_UNICODE); //GET ALL POST DATA $data = json_encode($_POST, JSON_UNESCAPED_UNICODE); - //Secure data - $payload = generate_payload($data); //API call - $responses = ioServer('/v1/equipments', $payload); + $responses = ioServer('/v2/equipments', $data); if ($responses === 'NOK'){ } else { @@ -152,7 +147,7 @@ if ($delete_allowed === 1 || $equipment_owner === 1){ $view .= ''; } if ($update_allowed === 1 || $equipment_owner === 1){ - $view .= ''; + $view .= ''; } $view .= '
'; @@ -174,7 +169,7 @@ if (isset($products_software) && $products_software !=''){ '; foreach ($products_software as $products_soft ){ if ($products_soft->hw_version == $equipment['hw_version']){ - $product_software_list .= ' + $product_software_list .= ' '; } } @@ -221,10 +216,10 @@ $view .= '
//GET PARTNERDATA $partner_data = json_decode($equipment['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,''); if (isset($partner_data->section)){$section = getPartnerName($partner_data->section) ?? 'Not specified';} else {$section = 'Not specified';} diff --git a/equipments.php b/equipments.php index 2f4f7dd..31af2bf 100644 --- a/equipments.php +++ b/equipments.php @@ -1,7 +1,7 @@ ←':''; //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 = 'equipment_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 && STORE in SESSION for FURTHER USE/NAVIGATION $pagination_page = $_SESSION['p'] = isset($_GET['p']) ? $_GET['p'] : 1; @@ -52,6 +52,12 @@ $responses = ioServer($api_url,''); //Decode Payload if (!empty($responses)){$responses = json_decode($responses);}else{$responses = null;} +// Redirect if only one equipment is found +if (is_array($responses) && count($responses) === 1 && isset($responses[0]->equipmentID)) { + header('Location: index.php?page=equipment&equipmentID=' . $responses[0]->equipmentID); + exit; +} + //Return QueryTotal from API $total_url = ((!empty($GET_VALUES) && $GET_VALUES !='') ? '&totals=' : 'totals=' ); $api_url = '/v2/equipments/'.$GET_VALUES.$total_url; @@ -186,7 +192,7 @@ $view .= ' X'; //SHOW DOWNLOAD TO EXCELL OPTION ONLY TO ADMIN USERS -if ($_SESSION['permission'] == 3 || $_SESSION['permission'] == 4){ +if (isAllowed('equipments',$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'CRU') === 1){ $view .= ' '; diff --git a/equipments_mass_update.php b/equipments_mass_update.php index 500cf4f..1bbc639 100644 --- a/equipments_mass_update.php +++ b/equipments_mass_update.php @@ -1,7 +1,7 @@ $val){ //GET ROW ID - $api_url = '/v1/application/serialnumber='.$val.'/get_rowID'; + $api_url = '/v2/application/serialnumber='.$val.'/get_rowID'; $responses = ioServer($api_url,''); //Decode Payload - if (!empty($responses)){$responses = decode_payload($responses);}else{$responses = null;} + if (!empty($responses)){$responses = json_decode($responses);}else{$responses = null;} //IF rowID is found add to array if (isset ($responses->rowID)){ @@ -92,10 +92,8 @@ if ($update_allowed === 1){ foreach ($output_excel as $data_to_update){ $data = json_encode($data_to_update, JSON_UNESCAPED_UNICODE); - //Secure data - $payload = generate_payload($data); //API call - $responses = ioServer('/v1/equipments', $payload); + $responses = ioServer('/v2/equipments', $data); if ($responses === 'NOK'){ } @@ -127,6 +125,27 @@ if ($update_allowed === 1){ } } +// Create success modal if update was completed +$success_modal = ''; +if ($update_allowed === 1 && isset($_POST['excel_data']) && $output_excel_display != ''){ + $success_modal = ' + '; +} + // Handle success messages if (isset($_GET['success_msg'])) { if ($_GET['success_msg'] == 1) { @@ -147,6 +166,12 @@ $view = ' '; +if ($update_allowed === 1){ + $print_btn_class = ($output_excel_display != '') ? 'btn' : 'btn alt'; + $view .= ''; + $view .= ''; +} + $view .= '
'; if (isset($success_msg)){ @@ -157,93 +182,85 @@ if (isset($success_msg)){
'; } -$view .= ' +$view .= '
'; //BUID UP DROPDOWNS -$partner = json_decode($_SESSION['partnerhierarchy'],true); +$partner = json_decode($_SESSION['authorization']['partnerhierarchy'],true); -$salesid_dropdown = listPartner('salesid',$_SESSION['permission'],$partner['salesid'],'yes'); -$soldto_dropdown = listPartner('soldto',$_SESSION['permission'],'','yes'); -$shipto_dropdown = listPartner('shipto',$_SESSION['permission'],'',''); -$location_dropdown = listPartner('location',$_SESSION['permission'],'',''); +$salesid_dropdown = listPartner('salesid',$_SESSION['authorization']['permission'],$partner['salesid'],'yes'); +$soldto_dropdown = listPartner('soldto',$_SESSION['authorization']['permission'],'','yes'); +$shipto_dropdown = listPartner('shipto',$_SESSION['authorization']['permission'],'',''); +$location_dropdown = listPartner('location',$_SESSION['authorization']['permission'],'',''); -$view .='
+$view .='
- '.$mass_update_partners.' -
'; + +
+
'; // SHOW SALESID and SOLDTO ONLY TO ADMIN -if ($_SESSION['permission'] == 3 || $_SESSION['permission'] == 4){ - $view .='
-

'.$general_salesid.'

-

'.$salesid_dropdown.'

+if ($_SESSION['authorization']['permission'] == 3 || $_SESSION['authorization']['permission'] == 4){ + $view .='
+ + '.$salesid_dropdown.'
-
-

'.$general_soldto.'

-

'.$soldto_dropdown.'

+
+ + '.$soldto_dropdown.'
'; } -$view .='
-

'.$general_shipto.'

-

'.$shipto_dropdown.'

+$view .='
+ + '.$shipto_dropdown.'
-
-

'.$general_location.'

-

'.$location_dropdown.'

+
+ + '.$location_dropdown.'
- -
'; +
+ + +
+
+ + +
+
+
+ +
+
+ +
+
+ + -$view .= '
-
- '.$mass_update_input.' -
-
-

-
-
- -
-
-

-
-
- -
-
-

'.$paste_excel_h3.'

-

-
- -
- " onclick="return confirm(\''.$mass_update_confirm_message.'\')" class="btn"> - '; -$view .=' +
-'; +
'; $view .= ''; $view .= '
'; if ($update_allowed === 1){ - $view .= '
-
- '.$tab3.' '.$total_summary.' - -
+ $view .= ' + '; + +//STATUS CHANGE FORM +if ($update_allowed === 1){ + $view .=' +
+

Payment Status

+
+

+ +
+
'; +} else { + $view .='

Payment Status

' . (${$payment_status} ?? $order['header']['payment_status'] ). '

-
+
'; +} +$view .='

Date

'.getRelativeTime($order['header']['created']). '

@@ -159,7 +199,7 @@ if ($order['customer']['email']) {

Contact

-

' . htmlspecialchars($order['customer']['phone'], ENT_QUOTES) . '

+

' . htmlspecialchars($order['customer']['phone'] ?? '-', ENT_QUOTES) . '

'; } else { $view .='

The order is not associated with an account.

'; @@ -184,14 +224,14 @@ $view .='

Address

' . htmlspecialchars($order['customer']['street'], ENT_QUOTES) . '
- ' . htmlspecialchars($order['customer']['city'], ENT_QUOTES) . '
- ' . htmlspecialchars($order['customer']['state'], ENT_QUOTES) . '
- ' . htmlspecialchars($order['customer']['zip'], ENT_QUOTES) . '
- ' . htmlspecialchars($order['customer']['country'], ENT_QUOTES) . '

+ ' . htmlspecialchars($order['customer']['city'] ?? '-', ENT_QUOTES) . '
+ ' . htmlspecialchars($order['customer']['state'] ?? '-', ENT_QUOTES) . '
+ ' . htmlspecialchars($order['customer']['zip'] ?? '-', ENT_QUOTES) . '
+ ' . htmlspecialchars($order['customer']['country'] ?? '-', ENT_QUOTES) . '

Contact

-

' . htmlspecialchars($order['customer']['phone'], ENT_QUOTES) . '

+

' . htmlspecialchars($order['customer']['phone'] ?? '-', ENT_QUOTES) . '

'; @@ -270,7 +310,7 @@ $view .=' Giftcards
- Relate giftcards + Relate giftcards @@ -315,12 +355,6 @@ $view .='
- - + '; diff --git a/product_manage.php b/product_manage.php index b6a5dd6..e118b35 100644 --- a/product_manage.php +++ b/product_manage.php @@ -3,14 +3,14 @@ defined(page_security_key) or exit; $page = 'product_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'); // if ($_GET['id'] !=''){ @@ -30,13 +30,13 @@ $product = [ 'url_slug' => '', 'full_path' =>'', 'created' => '', - 'createdby' => $_SESSION['username'], + 'createdby' => $_SESSION['authorization']['clientID'], 'parttype' => 1, 'price' => '0', 'salesflag' => 0, 'quantity' => 1, 'updated' => '', - 'updatedby' => $_SESSION['username'], + 'updatedby' => $_SESSION['authorization']['clientID'], 'product_category' => '', 'status' => 1, 'build' => 1, @@ -152,7 +152,7 @@ if ($delete_allowed === 1){ $view .= ''; } if ($update_allowed === 1){ - $view .= ''; + $view .= ''; } $view .= ''; diff --git a/products.php b/products.php index cda7274..110829c 100644 --- a/products.php +++ b/products.php @@ -1,7 +1,7 @@ '; } if ($update_allowed === 1){ - $view .= ''; + $view .= ''; } $view .= ''; diff --git a/products_configurations.php b/products_configurations.php index f00c524..5e82393 100644 --- a/products_configurations.php +++ b/products_configurations.php @@ -3,14 +3,14 @@ defined(page_security_key) or exit; $page = 'products_configurations'; //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 $products_configurations = [ @@ -20,7 +20,7 @@ $products_configurations = [ 'version' => '', 'assignment' => '', 'created' => '', - 'createdby' => $_SESSION['username'], + 'createdby' => $_SESSION['authorization']['clientID'], 'updated' => '', 'updatedby' => '' ]; @@ -134,7 +134,7 @@ if ($delete_allowed === 1){ $view .= ''; } if ($update_allowed === 1){ - $view .= ''; + $view .= ''; } $view .= ''; diff --git a/products_software.php b/products_software.php index 6f396ea..7244cdd 100644 --- a/products_software.php +++ b/products_software.php @@ -3,14 +3,14 @@ defined(page_security_key) or exit; $page = 'products_software'; //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 $products_software = [ @@ -21,7 +21,7 @@ $products_software = [ 'hw_version' => '', 'software' => '', 'created' => '', - 'createdby' => $_SESSION['username'], + 'createdby' => $_SESSION['authorization']['clientID'], 'mandatory' => '', 'latest' => '' ]; @@ -181,7 +181,7 @@ if ($delete_allowed === 1){ $view .= ''; } if ($update_allowed === 1){ - $view .= ''; + $view .= ''; } $view .= ''; diff --git a/products_software_assignments.php b/products_software_assignments.php index 5904720..51e40ad 100644 --- a/products_software_assignments.php +++ b/products_software_assignments.php @@ -3,14 +3,14 @@ defined(page_security_key) or exit; $page = 'products_software_assignments'; //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 product details $productrowid = $_GET['productrowid'] ?? ''; diff --git a/products_software_upgrade_paths_manage.php b/products_software_upgrade_paths_manage.php index 592a0be..19a28c1 100644 --- a/products_software_upgrade_paths_manage.php +++ b/products_software_upgrade_paths_manage.php @@ -6,14 +6,14 @@ if (!isset($button_cancel)) $button_cancel = 'Cancel'; $page = 'products_software_upgrade_paths_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'); // Determine redirect URL if (isset($_GET['id'])) { @@ -28,13 +28,13 @@ $path = [ 'from_version_id' => '', 'to_version_id' => '', 'price' => '', - 'currency' => 'USD', + 'currency' => 'EUR', 'description' => '', 'is_active' => 1, 'created' => '', - 'createdby' => $_SESSION['username'], + 'createdby' => $_SESSION['authorization']['clientID'], 'updated' => '', - 'updatedby' => $_SESSION['username'] + 'updatedby' => $_SESSION['authorization']['clientID'] ]; // Check if coming from version page (id parameter) or editing existing path @@ -177,7 +177,7 @@ if ($delete_allowed === 1 && isset($_GET['path_id']) && $_GET['path_id'] != ''){ $view .= ''; } if (($update_allowed === 1 && isset($_GET['path_id'])) || ($create_allowed === 1 && !isset($_GET['path_id']))){ - $view .= ''; + $view .= ''; } $view .= ''; @@ -186,7 +186,8 @@ $view .= '
'; } if (($update_allowed === 1 && isset($_GET['id'])) || ($create_allowed === 1 && !isset($_GET['id']))){ - $view .= ''; + $view .= ''; } $view .= '
'; @@ -152,7 +163,7 @@ $view .= '
Choose File - + No file selected
diff --git a/products_software_versions.php b/products_software_versions.php index 2cd45e0..3b44347 100644 --- a/products_software_versions.php +++ b/products_software_versions.php @@ -1,7 +1,7 @@ '', 'config' => '', 'created' => '', - 'createdby' => $_SESSION['username'], + 'createdby' => $_SESSION['authorization']['clientID'], 'measurement' => '', ]; @@ -103,7 +103,7 @@ if ($delete_allowed === 1){ $view .= ''; } if ($update_allowed === 1){ - $view .= ''; + $view .= ''; } $view .= '
'; diff --git a/profile.php b/profile.php index f94e321..2883637 100644 --- a/profile.php +++ b/profile.php @@ -3,14 +3,14 @@ defined(page_security_key) or exit; $page = 'profile'; //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 $user = [ @@ -29,7 +29,7 @@ $user = [ // ID param exists, edit an existing product //CALL TO API -$api_url = '/v1/profile/userkey='.$_SESSION['userkey']; +$api_url = '/v1/profile/userkey='.$_SESSION['authorization']['userkey']; $responses = ioServer($api_url,''); //Decode Payload if (!empty($responses)){$responses = decode_payload($responses);}else{$responses = null;} @@ -75,7 +75,7 @@ if ($update_allowed === 1){ //------------------------------------ //CHECK IF USER IS ALSO CREATOR OF RECORD THEN OVERRIDE UPDATE_ALLOWED_EDIT //------------------------------------ -$update_allowed = (($user['username'] == $_SESSION['username'])? 1 : 0); +$update_allowed = (($user['username'] == $_SESSION['authorization']['clientID'])? 1 : 0); template_header('Profile', 'profile', 'manage'); @@ -85,8 +85,8 @@ $view ='

'.$user_h2.'

'; -if ($update_allowed === 1 && $_SESSION['permission'] != 0){ - $view .= ''; +if ($update_allowed === 1 && $_SESSION['authorization']['permission'] != 0){ + $view .= ''; } $view .= ''; @@ -108,7 +108,7 @@ $view .= '
- + @@ -120,7 +120,7 @@ $view .= '
$view .=' '; - if ($update_allowed === 1 && $_SESSION['permission'] != 0){ + if ($update_allowed === 1 && $_SESSION['authorization']['permission'] != 0){ $view .= ' '; diff --git a/profiles.php b/profiles.php index 1d3e377..f31df19 100644 --- a/profiles.php +++ b/profiles.php @@ -12,7 +12,7 @@ include dirname(__FILE__).'/settings/settingsviews.php'; $page = 'profiles'; //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; } @@ -281,7 +281,7 @@ $view .= '

Profiles

- +
'; if (isset($success_msg)){ diff --git a/register.php b/register.php index 4fa55f0..e2e46fe 100644 --- a/register.php +++ b/register.php @@ -9,9 +9,15 @@ $lang = in_array($lang, $supportedLanguages) ? $lang : 'US'; //INCLUDE THE TRANSLATION include_once './settings/translations/translations_'.$lang.'.php'; +include_once './settings/countries.php'; + +//========================================= +//GET DOMAIN FOR CORRECT STYLING AND SETTINGS +//========================================= +$domain = getDomainName($_SERVER['SERVER_NAME']); +$custom_css = (file_exists(dirname(__FILE__).'/custom/'.$domain.'/style/'.$domain.'_login.css') ? './custom/'.$domain.'/style/'.$domain.'_login.css' : './style/admin_login.css'); // Default input values -$display =''; $register = [ 'sn' => '', 'organization' => '', @@ -26,12 +32,10 @@ if (isset($_POST['register'])){ $register = $_POST; //GET USERKEY $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,true);}else{$responses = '400';} if ($responses === 'NOK' || $responses === '400'){ //Not allowed @@ -41,7 +45,7 @@ if (isset($_POST['register'])){ // Start a new session session_start(); // Store data in session - $_SESSION['userkey'] = $responses->userkey; + $_SESSION['authorization']['userkey'] = $responses['userkey']; // GET ALL POST DATA $data = json_encode($register, JSON_UNESCAPED_UNICODE); // Secure data @@ -50,7 +54,6 @@ if (isset($_POST['register'])){ $message_return = ioServer('/v1/application/register', $payload); if (!empty($message_return)){ $message_return = decode_payload($message_return); - $display = 'display:none;'; } else{ $message_return = '400'; @@ -58,264 +61,600 @@ if (isset($_POST['register'])){ } } echo ' + - + '.$register_title.' - - + } + - -
-
-

'.$register_title.'

- -
+ + + + + + +
+ + +'; + +echo $view; +template_footer(); diff --git a/report_contracts_billing.php b/report_contracts_billing.php index 7941247..1a471a6 100644 --- a/report_contracts_billing.php +++ b/report_contracts_billing.php @@ -1,7 +1,7 @@ ←':''; //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; } diff --git a/report_healthindex.php b/report_healthindex.php index 9084994..a459bff 100644 --- a/report_healthindex.php +++ b/report_healthindex.php @@ -1,7 +1,7 @@ ←':''; //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; } @@ -135,7 +135,7 @@ $view .= ' '; //SHOW DOWNLOAD TO EXCELL OPTION ONLY TO ADMIN USERS -if ($_SESSION['permission'] == 3 || $_SESSION['permission'] == 4){ +if (isAllowed('report_healthindex',$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'CRU') === 1){ $view .='
'; diff --git a/report_usage.php b/report_usage.php index a64b808..b8fdfe3 100644 --- a/report_usage.php +++ b/report_usage.php @@ -1,7 +1,7 @@ $username, "resetkey" => ''), 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'){ $username_err = $password_err_1 ?? 'Not authorized, please retry'; @@ -107,11 +105,9 @@ if (isset($_POST['resetkey']) && $_POST['resetkey'] !='' && $_POST['password_upd else { //UPDATE PASSWORD $data = json_encode(array("password" => $password, "resetkey" => $resetkey), JSON_UNESCAPED_UNICODE); - //Secure data - $payload = generate_payload($data); //API call - $responses = ioServer('/v1/authorization', $payload); - if (!empty($responses)){$responses = decode_payload($responses);}else{$responses = '400';} + $responses = ioServer('/v2/authorization', $data); + if (!empty($responses)){$responses = json_decode($responses);}else{$responses = '400';} if ($responses === 'NOK'){ $username_err = $password_err_1 ?? 'Not authorized, please retry'; diff --git a/rma.php b/rma.php index 9457f52..6fb2a05 100644 --- a/rma.php +++ b/rma.php @@ -1,7 +1,7 @@

'.$product_code.'

-

'.(($view_product == 1)? ''.$rma_header['productcode'].'':'').'

+

'.(($view_product == 1)? ''.$rma_header['productcode'].' ':'').'

'.$product_name.'

-

'.(($view_product == 1)? ''.(${$rma_header['productname']} ?? $rma_header['productname']).'':'').'

+

'.(($view_product == 1)? ''.(${$rma_header['productname']} ?? $rma_header['productname']).' ':'').'

'.$equipment_label10.'

@@ -158,16 +158,9 @@ $view .= '
'; //BUILD TO INPUT FORM BASED ON ARRAY // ------------------------------ // -$view .='
- '.($rma_return_tab1 ?? 'Return reason').''; - -if($rma_header['servicereport_available'] == 0 ){ - $view .=''.($rma_return_tab2 ?? 'Questions').''; -} - -$view .= ''.($general_actions ?? 'Actions').' - '.($tab3 ?? 'Log').' -
'; +$view .= ''; $view .= '
@@ -184,8 +177,9 @@ $view .= '
'; - if($rma_header['servicereport_available'] == 0 ){ - $view .= '
+ if($rma_header['servicereport_available'] == 0 ){ $view .= ''; $view .= '
'; foreach($arrayQuestions_rma as $group){ @@ -221,18 +215,27 @@ $view .= '
} } } - } $view .= '
-
- '; +
'; + } + +$view .= ''; -$view .= '
+$view .= ''; + +$view .= '
'; -$view .= '
+$view .= ''; + +$view .= '
diff --git a/rma_manage.php b/rma_manage.php index 4fa7b8d..cd9d0fd 100644 --- a/rma_manage.php +++ b/rma_manage.php @@ -1,7 +1,7 @@

'.$product_code.'

-

'.(($view_product == 1)? ''.$rma['header']['productcode'].'':'').'

+

'.(($view_product == 1)? ''.$rma['header']['productcode'].' ':'').'

'.$equipment_label10.'

@@ -330,17 +330,8 @@ $view .= '
'; //BUILD TO INPUT FORM BASED ON ARRAY // ------------------------------ // -$view .='
- '.($rma_return_tab1 ?? 'Return reason').''; - -if($rma['header']['servicereport_available'] == 0 ){ - $view .=''.($rma_return_tab2 ?? 'Questions').''; -} - -$view .= '
'; - $view .= ''; $view .= '
@@ -359,11 +350,10 @@ $view .= '
'; - +if($rma['header']['servicereport_available'] == 0 ){ $view .= ''; - $view .= '
'; if($rma['header']['servicereport_available'] == 0 ){ @@ -415,5 +405,5 @@ $view .= ' //OUTPUT echo $view; -template_footer(); +echo template_footer(); ?> \ No newline at end of file diff --git a/rmas.php b/rmas.php index a6f5ba8..f3cb901 100644 --- a/rmas.php +++ b/rmas.php @@ -1,7 +1,7 @@ '.$general_filters_clear.''; //SHOW DOWNLOAD TO EXCELL OPTION ONLY TO ADMIN USERS -if ($_SESSION['permission'] == 3 || $_SESSION['permission'] == 4){ +if (isAllowed('rmas',$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'CRU') === 1){ $view .= ' Download '; diff --git a/servicereport.php b/servicereport.php index 426803e..aee156e 100644 --- a/servicereport.php +++ b/servicereport.php @@ -1,7 +1,7 @@ ←':''; //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; } diff --git a/settings.php b/settings.php index 9dd34f7..1841d9a 100644 --- a/settings.php +++ b/settings.php @@ -8,7 +8,7 @@ $domain = getDomainName($_SERVER['SERVER_NAME']); $file = ((file_exists(dirname(__FILE__).'/custom/'.$domain.'/settings/'.$domain.'_config.php')) ? dirname(__FILE__).'/custom/'.$domain.'/settings/'.$domain.'_config.php' : dirname(__FILE__).'/settings/config.php'); //Check if allowed -if (isAllowed('settings',$_SESSION['profile'],$_SESSION['permission'],'R') === 0){ +if (isAllowed('settings',$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){ header('location: index.php'); exit; } @@ -101,7 +101,7 @@ if (isset($_POST['submit']) && !empty($_POST)) { if (isset($_POST['geoupdate'])){ //GEOLOCATION UPDATE - geolocationUpdate($_SESSION['userkey']); + geolocationUpdate($_SESSION['authorization']['userkey']); } if (isset($_POST['updatecartest'])){ @@ -125,7 +125,7 @@ $view .= '

Settings

- +
'; diff --git a/settings/countries.php b/settings/countries.php new file mode 100644 index 0000000..1b97ff3 --- /dev/null +++ b/settings/countries.php @@ -0,0 +1,148 @@ + ['country' => 'Austria', 'taxes' => 20.00], + 2 => ['country' => 'Belgium', 'taxes' => 21.00], + 3 => ['country' => 'Bulgaria', 'taxes' => 20.00], + 4 => ['country' => 'Croatia', 'taxes' => 25.00], + 5 => ['country' => 'Cyprus', 'taxes' => 19.00], + 6 => ['country' => 'Czech Republic', 'taxes' => 21.00], + 7 => ['country' => 'Denmark', 'taxes' => 25.00], + 8 => ['country' => 'Estonia', 'taxes' => 24.00], + 9 => ['country' => 'Finland', 'taxes' => 25.50], + 10 => ['country' => 'France', 'taxes' => 20.00], + 11 => ['country' => 'Germany', 'taxes' => 19.00], + 12 => ['country' => 'Greece', 'taxes' => 24.00], + 13 => ['country' => 'Hungary', 'taxes' => 27.00], + 14 => ['country' => 'Ireland', 'taxes' => 23.00], + 15 => ['country' => 'Italy', 'taxes' => 22.00], + 16 => ['country' => 'Latvia', 'taxes' => 21.00], + 17 => ['country' => 'Lithuania', 'taxes' => 21.00], + 18 => ['country' => 'Luxembourg', 'taxes' => 16.00], + 19 => ['country' => 'Malta', 'taxes' => 18.00], + 20 => ['country' => 'Netherlands', 'taxes' => 21.00], + 21 => ['country' => 'Poland', 'taxes' => 23.00], + 22 => ['country' => 'Portugal', 'taxes' => 23.00], + 23 => ['country' => 'Romania', 'taxes' => 19.00], + 24 => ['country' => 'Slovakia', 'taxes' => 23.00], + 25 => ['country' => 'Slovenia', 'taxes' => 22.00], + 26 => ['country' => 'Spain', 'taxes' => 21.00], + 27 => ['country' => 'Sweden', 'taxes' => 25.00], + 28 => ['country' => 'United Kingdom', 'taxes' => 20.00], + 29 => ['country' => 'Switzerland', 'taxes' => 8.10], + 30 => ['country' => 'Norway', 'taxes' => 25.00], + 31 => ['country' => 'Iceland', 'taxes' => 24.00], + 32 => ['country' => 'Albania', 'taxes' => 20.00], + 33 => ['country' => 'Serbia', 'taxes' => 20.00], + 34 => ['country' => 'North Macedonia', 'taxes' => 18.00], + 35 => ['country' => 'Bosnia and Herzegovina', 'taxes' => 17.00], + 36 => ['country' => 'Montenegro', 'taxes' => 21.00], + 37 => ['country' => 'Moldova', 'taxes' => 20.00], + 38 => ['country' => 'Ukraine', 'taxes' => 20.00], + 39 => ['country' => 'Belarus', 'taxes' => 20.00], + 40 => ['country' => 'Turkey', 'taxes' => 20.00], + 41 => ['country' => 'Andorra', 'taxes' => 4.50], + 42 => ['country' => 'Australia', 'taxes' => 10.00], + 43 => ['country' => 'New Zealand', 'taxes' => 15.00], + 44 => ['country' => 'Japan', 'taxes' => 10.00], + 45 => ['country' => 'China', 'taxes' => 13.00], + 46 => ['country' => 'India', 'taxes' => 18.00], + 47 => ['country' => 'South Korea', 'taxes' => 10.00], + 48 => ['country' => 'Singapore', 'taxes' => 9.00], + 49 => ['country' => 'Indonesia', 'taxes' => 11.00], + 50 => ['country' => 'Thailand', 'taxes' => 7.00], + 51 => ['country' => 'Vietnam', 'taxes' => 8.00], + 52 => ['country' => 'Philippines', 'taxes' => 12.00], + 53 => ['country' => 'Malaysia', 'taxes' => 0.00], + 54 => ['country' => 'Taiwan', 'taxes' => 5.00], + 55 => ['country' => 'Pakistan', 'taxes' => 18.00], + 56 => ['country' => 'Bangladesh', 'taxes' => 15.00], + 57 => ['country' => 'Sri Lanka', 'taxes' => 18.00], + 58 => ['country' => 'Nepal', 'taxes' => 13.00], + 59 => ['country' => 'Cambodia', 'taxes' => 10.00], + 60 => ['country' => 'Myanmar', 'taxes' => 5.00], + 61 => ['country' => 'Laos', 'taxes' => 10.00], + 62 => ['country' => 'Mongolia', 'taxes' => 10.00], + 63 => ['country' => 'Kazakhstan', 'taxes' => 12.00], + 64 => ['country' => 'Uzbekistan', 'taxes' => 12.00], + 65 => ['country' => 'Armenia', 'taxes' => 20.00], + 66 => ['country' => 'Georgia', 'taxes' => 18.00], + 67 => ['country' => 'Azerbaijan', 'taxes' => 18.00], + 68 => ['country' => 'Fiji', 'taxes' => 9.00], + 69 => ['country' => 'Papua New Guinea', 'taxes' => 10.00], + 70 => ['country' => 'Samoa', 'taxes' => 15.00], + 71 => ['country' => 'Tonga', 'taxes' => 15.00], + 72 => ['country' => 'Vanuatu', 'taxes' => 15.00], + 73 => ['country' => 'Bhutan', 'taxes' => 7.00], + 74 => ['country' => 'Saudi Arabia', 'taxes' => 15.00], + 75 => ['country' => 'United Arab Emirates', 'taxes' => 5.00], + 76 => ['country' => 'Bahrain', 'taxes' => 10.00], + 77 => ['country' => 'Kuwait', 'taxes' => 0.00], + 78 => ['country' => 'Oman', 'taxes' => 5.00], + 79 => ['country' => 'Qatar', 'taxes' => 0.00], + 80 => ['country' => 'Israel', 'taxes' => 17.00], + 81 => ['country' => 'Jordan', 'taxes' => 16.00], + 82 => ['country' => 'Lebanon', 'taxes' => 11.00], + 83 => ['country' => 'Egypt', 'taxes' => 14.00], + 85 => ['country' => 'South Africa', 'taxes' => 15.00], + 86 => ['country' => 'Nigeria', 'taxes' => 7.50], + 87 => ['country' => 'Kenya', 'taxes' => 16.00], + 88 => ['country' => 'Ghana', 'taxes' => 15.00], + 89 => ['country' => 'Morocco', 'taxes' => 20.00], + 90 => ['country' => 'Tunisia', 'taxes' => 19.00], + 91 => ['country' => 'Algeria', 'taxes' => 19.00], + 92 => ['country' => 'Egypt', 'taxes' => 14.00], + 93 => ['country' => 'Ethiopia', 'taxes' => 15.00], + 94 => ['country' => 'Tanzania', 'taxes' => 18.00], + 95 => ['country' => 'Uganda', 'taxes' => 18.00], + 96 => ['country' => 'Zimbabwe', 'taxes' => 15.00], + 97 => ['country' => 'Zambia', 'taxes' => 16.00], + 98 => ['country' => 'Botswana', 'taxes' => 14.00], + 99 => ['country' => 'Mauritius', 'taxes' => 15.00], + 100 => ['country' => 'Namibia', 'taxes' => 15.00], + 101 => ['country' => 'Rwanda', 'taxes' => 18.00], + 102 => ['country' => 'Senegal', 'taxes' => 18.00], + 103 => ['country' => 'Ivory Coast', 'taxes' => 18.00], + 104 => ['country' => 'Cameroon', 'taxes' => 19.25], + 105 => ['country' => 'Angola', 'taxes' => 14.00], + 106 => ['country' => 'Mozambique', 'taxes' => 16.00], + 107 => ['country' => 'Madagascar', 'taxes' => 20.00], + 108 => ['country' => 'Mali', 'taxes' => 18.00], + 109 => ['country' => 'Burkina Faso', 'taxes' => 18.00], + 110 => ['country' => 'Niger', 'taxes' => 19.00], + 111 => ['country' => 'Benin', 'taxes' => 18.00], + 112 => ['country' => 'Togo', 'taxes' => 18.00], + 113 => ['country' => 'Guinea', 'taxes' => 18.00], + 114 => ['country' => 'Malawi', 'taxes' => 16.50], + 115 => ['country' => 'Gabon', 'taxes' => 18.00], + 116 => ['country' => 'Mauritania', 'taxes' => 16.00], + 117 => ['country' => 'Lesotho', 'taxes' => 15.00], + 118 => ['country' => 'Eswatini', 'taxes' => 15.00], + 119 => ['country' => 'Liberia', 'taxes' => 18.00], + 120 => ['country' => 'Canada', 'taxes' => 5.00], + 121 => ['country' => 'United States', 'taxes' => 10.00], + 122 => ['country' => 'Mexico', 'taxes' => 16.00], + 123 => ['country' => 'Argentina', 'taxes' => 21.00], + 124 => ['country' => 'Brazil', 'taxes' => 17.00], + 125 => ['country' => 'Chile', 'taxes' => 19.00], + 126 => ['country' => 'Colombia', 'taxes' => 19.00], + 127 => ['country' => 'Peru', 'taxes' => 18.00], + 128 => ['country' => 'Ecuador', 'taxes' => 15.00], + 129 => ['country' => 'Uruguay', 'taxes' => 22.00], + 130 => ['country' => 'Paraguay', 'taxes' => 10.00], + 131 => ['country' => 'Bolivia', 'taxes' => 13.00], + 132 => ['country' => 'Venezuela', 'taxes' => 16.00], + 133 => ['country' => 'Costa Rica', 'taxes' => 13.00], + 134 => ['country' => 'Panama', 'taxes' => 7.00], + 135 => ['country' => 'Guatemala', 'taxes' => 12.00], + 136 => ['country' => 'Honduras', 'taxes' => 15.00], + 137 => ['country' => 'El Salvador', 'taxes' => 13.00], + 138 => ['country' => 'Nicaragua', 'taxes' => 15.00], + 139 => ['country' => 'Dominican Republic', 'taxes' => 18.00], + 140 => ['country' => 'Jamaica', 'taxes' => 15.00], + 141 => ['country' => 'Trinidad and Tobago', 'taxes' => 12.50], + 142 => ['country' => 'Barbados', 'taxes' => 17.50], + 143 => ['country' => 'Bahamas', 'taxes' => 10.00], +]; diff --git a/settings/settingsmenu.php b/settings/settingsmenu.php index 85cf303..4f0ee83 100644 --- a/settings/settingsmenu.php +++ b/settings/settingsmenu.php @@ -101,7 +101,7 @@ $main_menu = [ ], "equipments" =>[ "url" => "equipments", - "selected" => "assets", + "selected" => "equipments", "icon" => "fa-solid fa-database", "name" => "menu_assets" ], @@ -176,8 +176,14 @@ $main_menu = [ ], "reporting" => [ "main_menu" => [ - "url" => "report_build", - "selected" => "report_build", + "url" => "report_builder", + "selected" => "report_builder", + "icon" => "fa-solid fa-magnifying-glass-chart", + "name" => "menu_report_main" + ], + "report_builder" => [ + "url" => "report_builder", + "selected" => "report_builder", "icon" => "fa-solid fa-magnifying-glass-chart", "name" => "menu_report_main" ], @@ -293,6 +299,18 @@ $main_menu = [ "icon" => "fas fa-tachometer-alt", "name" => "menu_maintenance" ], + "Access_elements" => [ + "url" => "access_elements", + "selected" => "access_elements", + "icon" => "fas fa-tools", + "name" => "menu_access_elements" + ], + "user_roles" => [ + "url" => "user_roles", + "selected" => "user_roles", + "icon" => "fas fa-tools", + "name" => "menu_user_roles" + ], "profiles" => [ "url" => "profiles", "selected" => "profiles", @@ -336,11 +354,12 @@ $page_rows_invoice = 25; //invoices $page_rows_dealers = 25; //dealers $page_rows_software_versions = 50; //software versions $page_rows_software_assignment = 50; //software assignment +$page_rows_folders = 25; //marketing folders //------------------------------------------ // Languages supported //------------------------------------------ -$supportedLanguages = ['US', 'NL', 'DE', 'ES','PT']; +$supportedLanguages = ['US', 'NL', 'DE', 'ES','PL','PT']; //------------------------------------------ // Pricing diff --git a/settings/settingsprofiles.php b/settings/settingsprofiles.php index 4ec0352..012f7ae 100644 --- a/settings/settingsprofiles.php +++ b/settings/settingsprofiles.php @@ -1,27 +1,27 @@ \ No newline at end of file diff --git a/settings/settingsviews.php b/settings/settingsviews.php index 0e7e408..f366609 100644 --- a/settings/settingsviews.php +++ b/settings/settingsviews.php @@ -4,6 +4,9 @@ // All individual views and APIs - Profile ++++++++++++++ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++ $all_views = [ + "access_element", + "access_element_manage", + "access_elements", "account", "account_manage", "accounts", @@ -44,11 +47,14 @@ $all_views = [ "equipment", "equipment_data", "equipment_healthindex", + "equipment_history", "equipment_manage", "equipment_manage_edit", "equipments", "equipments_mass_update", + "factuur", "firmwaretool", + "functions", "generate_download_token", "histories", "history", @@ -63,6 +69,13 @@ $all_views = [ "mailer", "maintenance", "marketing", + "marketing_delete", + "marketing_files", + "marketing_folders", + "marketing_migrate", + "marketing_tags", + "marketing_update", + "marketing_upload", "media", "media_manage", "media_scanner", @@ -102,6 +115,7 @@ $all_views = [ "register", "render_service_report", "report_build", + "report_builder", "report_contracts_billing", "report_healthindex", "report_usage", @@ -112,8 +126,10 @@ $all_views = [ "rma_history_manage", "rma_manage", "rmas", + "role_access_permissions", "sales", "security", + "service", "servicereport", "servicereports", "settings", @@ -138,9 +154,14 @@ $all_views = [ "user", "user_credentials", "user_manage", + "user_role", + "user_role_assignments", + "user_role_manage", + "user_roles", "users", "vin", "webhook_mollie", + "webhook_paypal", ]; ?> \ No newline at end of file diff --git a/settings/systemservicetool_init.php b/settings/systemservicetool_init.php index 1648ef8..bc3971f 100644 --- a/settings/systemservicetool_init.php +++ b/settings/systemservicetool_init.php @@ -2,7 +2,7 @@ //================================================================= //Software version SERVICE Tool==================================== -//================================================================= +/*================================================================= $latest_version = getLatestVersion('EPSK01',$clientsecret) ?? ''; $service_tool_current_version = ($latest_version !='') ? $latest_version['version'] : ''; @@ -11,7 +11,7 @@ $software_download_url = 'https://'.$_SERVER['SERVER_NAME'].'/firmware'.'/'; //getSoftware (legacy) $software_url = ($latest_version !='') ? $latest_version['source'] : 'https://'.$_SERVER['SERVER_NAME'].'/firmware'.'/'.$service_tool_current_filename; - +*/ //================================================================= //SERVICE Tool manual =================================== //================================================================= @@ -90,9 +90,9 @@ $init = array( "ManualURL"=> $manual_url, "termsURL"=> "https://emergency-plug.com/en/terms-and-conditions", "Application" => array( - "current_version" => $service_tool_current_version, - "current_filename" => $service_tool_current_filename, - "location" => $software_download_url + "current_version" => '', + "current_filename" => '', + "location" => '' ) ); diff --git a/settings/translations/translations_DE.php b/settings/translations/translations_DE.php index 091bfef..46a773d 100644 --- a/settings/translations/translations_DE.php +++ b/settings/translations/translations_DE.php @@ -28,6 +28,9 @@ $menu_report_usage = 'Systemnutzung'; $menu_maintenance = 'Maintenance'; $menu_profiles = 'Profiles'; $menu_upgrades = 'Software Upgrades'; +$menu_sales_licenses = 'Lizenzen'; +$menu_softwaretool = 'Software-Aktualisierung'; +$menu_products_software_versions = 'Software'; $tab1 = 'Allgemein'; $tab2 = 'Partner'; $tab3 = 'Protokoll'; @@ -319,6 +322,21 @@ $firmwaretool_step_5 = 'Wenn Firmware verfügbar ist: Die Statusleiste zeigt "
Falls Sie unsere E-Mail nicht erhalten, können Sie über unsere Website auf unser Software-Update-Tool zugreifen.

Gehen Sie zu Mein Login und verwenden Sie Benutzername/Passwort:'; $communication_h2 = 'Kommunikation'; $communication_p = 'Kommunikation anzeigen, verwalten und suchen.'; $communication_status = 'Status'; @@ -807,5 +824,9 @@ $focus_offering_0 = 'Economy - Premium'; $focus_offering_1 = 'Premium - Highend'; $dealer_type_0 = 'Lokal'; $dealer_type_1 = 'Professionell'; -$dealer_type_2 = 'Unternehmens'; +$dealer_type_2 = 'Unternehmens';$user_information_required = 'Benutzerinformationen Erforderlich'; +$user_information_description = 'Bitte geben Sie Ihre Daten ein, um mit Software-Updates fortzufahren'; +$general_continue = 'Fortfahren'; +$payment_method_1 = 'Debit/Credit'; +$payment_method_3 = 'PayPal'; ?> \ No newline at end of file diff --git a/settings/translations/translations_ES.php b/settings/translations/translations_ES.php index 3d53eac..2a6964a 100644 --- a/settings/translations/translations_ES.php +++ b/settings/translations/translations_ES.php @@ -28,6 +28,9 @@ $menu_report_usage = 'Uso del Sistema'; $menu_maintenance = 'Mantenimiento'; $menu_profiles = 'Perfiles'; $menu_upgrades = 'Actualizaciones de Software'; +$menu_sales_licenses = 'Licencias'; +$menu_softwaretool = 'Actualización de Software'; +$menu_products_software_versions = 'Software'; $tab1 = 'General'; $tab2 = 'Socios'; $tab3 = 'Registro'; @@ -319,6 +322,21 @@ $firmwaretool_step_5 = 'Cuando el firmware esté disponible: La barra de estado $firmwaretool_step_6 = 'Cuando el firmware esté disponible: Asegúrese de que el SN y el HW se lean del dispositivo y confirme esto seleccionando la casilla "Confirmo que el SN y el HW se han leído del dispositivo"'; $firmwaretool_step_7 = 'Presione el botón "Actualizar firmware" para iniciar el diálogo de actualización de firmware y siga las instrucciones en pantalla'; $firmwaretool_step_8 = 'Tenga en cuenta: Este proceso no se puede detener y debe finalizar.'; +$softwaretool_h2 = 'Herramienta de actualización de software'; +$softwaretool_p = 'Opciones de actualización de software.'; +$softwaretool_step = 'Instrucciones'; +$softwaretool_step_1 = 'Conecte el dispositivo a la computadora por USB. (Se encuentra debajo de la tapa de la batería)'; +$softwaretool_step_2 = 'Presione el botón "conectar"'; +$softwaretool_step_3 = 'Aparecerá una ventana emergente pidiendo seleccionar un dispositivo. Seleccione el dispositivo haciendo clic en él y luego presione el botón conectar.'; +$softwaretool_step_4 = 'Después de que desaparezca la ventana emergente, el dispositivo será leído, la barra de estado mostrará el progreso'; +$softwaretool_step_5 = 'Las actualizaciones de software disponibles se mostrarán con Nombre, Descripción y Precio'; +$softwaretool_step_6 = 'Seleccione una versión de software'; +$softwaretool_step_7 = 'Para actualizaciones de pago, siga el proceso de pago'; +$softwaretool_step_8 = 'Tenga en cuenta: Este proceso no se puede detener y debe finalizar.'; +$softwaretool_no_updates = 'No se encontraron actualizaciones de software'; +$softwaretool_checking = 'Buscando actualizaciones de software...'; +$softwaretool_available = 'Actualizaciones de software disponibles'; +$softwaretool_select_upgrade = 'Seleccione una opción de actualización:'; $newuser_subject = 'Usuario de CustomerPortal creado'; $newuser_header = 'Estimado usuario de CustomerPortal'; $newuser_text = 'Su administrador de CustomerPortal ha proporcionado acceso al CustomerPortal. Para completar su cuenta, debe actualizar su contraseña a través del siguiente enlace.'; @@ -351,16 +369,14 @@ $register_3_term_consent_1 = 'Acepto '; $register_3_term_consent_2 = 'términos y condiciones'; $register_button = 'Registrar'; $register_mandatory = 'obligatorio'; -$register_4_completed = '4. Registro Completado'; -$register_5_details = '5. Detalles del Registro'; -$register_6_return = '6. Volver al registro'; +$register_4_completed = 'Registro Completado'; +$register_5_details = 'Detalles del Registro'; +$register_6_return = 'Volver al registro'; $register_6_button = 'Atrás'; $register_message_1 = 'Número de serie no reconocido, comuníquese con su proveedor'; $register_message_2 = 'Producto bajo garantía o garantía expirada. Si no es correcto, comuníquese con su proveedor'; $register_message_3 = 'La garantía se extiende por 1 año adicional'; -$register_message_4 = 'Gracias por su registro. Recibirá el enlace a nuestra herramienta de actualización de software a través del correo electrónico proporcionado durante el registro. - -En caso de que no reciba nuestro correo electrónico, puede acceder a nuestra herramienta de actualización de software a través de nuestro sitio web. Vaya a Mi Login y utilice nombre de usuario/contraseña:'; +$register_message_4 = 'Gracias por su registro. Recibirá el enlace a nuestra herramienta de actualización de software a través del correo electrónico proporcionado durante el registro.

En caso de que no reciba nuestro correo electrónico, puede acceder a nuestra herramienta de actualización de software a través de nuestro sitio web.

Vaya a Mi Login y utilice nombre de usuario/contraseña:'; $communication_h2 = 'Comunicación'; $communication_p = 'Ver, gestionar y buscar comunicación.'; $communication_status = 'Estado'; @@ -814,4 +830,9 @@ $focus_offering_1 = 'Premium - Alta gama'; $dealer_type_0 = 'Local'; $dealer_type_1 = 'Profesional'; $dealer_type_2 = 'Corporativo'; +$user_information_required = 'Información del Usuario Requerida'; +$user_information_description = 'Por favor proporcione su información para continuar con las actualizaciones de software'; +$general_continue = 'Continuar'; +$payment_method_1 = 'Debit/Credit'; +$payment_method_3 = 'PayPal'; ?> \ No newline at end of file diff --git a/settings/translations/translations_NL.php b/settings/translations/translations_NL.php index d91ea9a..4fd1e27 100644 --- a/settings/translations/translations_NL.php +++ b/settings/translations/translations_NL.php @@ -28,6 +28,9 @@ $menu_report_usage = 'Systeemgebruik'; $menu_maintenance = 'Maintenance'; $menu_profiles = 'Profielen'; $menu_upgrades = 'Software Upgrades'; +$menu_sales_licenses = 'Licenties'; +$menu_softwaretool = 'Software Update'; +$menu_products_software_versions = 'Software'; $tab1 = 'Algemeen'; $tab2 = 'Hierarchy'; $tab3 = 'Log'; @@ -319,6 +322,21 @@ $firmwaretool_step_5 = 'Wanneer software beschikbaar is zal de voortgang "Fir $firmwaretool_step_6 = 'Wanneer software beschikbaar is: Bevestig dat SW and HW zijn gelezen van het apparaat door op de checkbox te klikken bij Ik bevestig dat SN en HW zijn uitgelezen van de activa.'; $firmwaretool_step_7 = 'Druk op de "Update firmware" knop om de update te starten en de instructies op het scherm te volgen'; $firmwaretool_step_8 = 'Opgelet: Dit proces kan niet onderbroken worden.'; +$softwaretool_h2 = 'Software upgrade tool'; +$softwaretool_p = 'Software upgrade opties.'; +$softwaretool_step = 'Instructies'; +$softwaretool_step_1 = 'Sluit het apparaat via USB aan op de computer.(USB bevindt zich onder de batterijklep)'; +$softwaretool_step_2 = 'Druk "connect" knop'; +$softwaretool_step_3 = 'In het popup venster selecteer het apparaat door er op te klikken. Druk daarna op de verbinding maken knop.'; +$softwaretool_step_4 = 'Het apparaat wordt nu gelezen. Een voortgang status wordt getoond'; +$softwaretool_step_5 = 'Beschikbare software upgrades worden weergegeven met Naam, Beschrijving en Prijs'; +$softwaretool_step_6 = 'Selecteer een software versie'; +$softwaretool_step_7 = 'Voor betaalde upgrades, volg het betalingsproces'; +$softwaretool_step_8 = 'Opgelet: Dit proces kan niet onderbroken worden.'; +$softwaretool_no_updates = 'Geen software updates gevonden'; +$softwaretool_checking = 'Controleren op software updates...'; +$softwaretool_available = 'Software updates beschikbaar'; +$softwaretool_select_upgrade = 'Selecteer een upgrade optie:'; $newuser_subject = 'CustomerPortal user created'; $newuser_header = 'Dear CustomerPortal user'; $newuser_text = 'Your CustomerPortal administrator has provided access to the CustomerPortal. To complete your account you need to update your password via the link below.'; @@ -351,16 +369,14 @@ $register_3_term_consent_1 = 'Ik ga akkoord met de '; $register_3_term_consent_2 = 'algemene voorwaarden'; $register_button = 'Registreren'; $register_mandatory = 'verplicht'; -$register_4_completed = '4. Registratie Voltooid'; -$register_5_details = '5. Registratiegegevens'; -$register_6_return = '6. Terug naar registratie'; +$register_4_completed = 'Registratie Voltooid'; +$register_5_details = 'Registratiegegevens'; +$register_6_return = 'Terug naar registratie'; $register_6_button = 'Terug'; $register_message_1 = 'Serienummer niet herkend, neem contact op met uw leverancier'; $register_message_2 = 'Product onder garantie of garantie verlopen. Neem contact op met uw leverancier als dit niet klopt'; $register_message_3 = 'Garantie is met 1 extra jaar verlengd'; -$register_message_4 = 'Bedankt voor uw registratie. U ontvangt de link naar onze software-update tool via de e-mail die u tijdens de registratie heeft opgegeven. - -Als u onze e-mail niet ontvangt, kunt u via onze website toegang krijgen tot onze software updatetool. Ga naar Mijn Login en gebruik gebruikersnaam/wachtwoord:'; +$register_message_4 = 'Bedankt voor uw registratie. U ontvangt de link naar onze software-update tool via de e-mail die u tijdens de registratie heeft opgegeven.

Als u onze e-mail niet ontvangt, kunt u via onze website toegang krijgen tot onze software updatetool.

Ga naar Mijn Login en gebruik gebruikersnaam/wachtwoord:'; $communication_h2 = 'Communicatie'; $communication_p = 'Bekijk, update, en zoek communicatie.'; $communication_status = 'Status'; @@ -961,7 +977,6 @@ $payment_status_101 = 'In behandeling'; $payment_status_102 = 'Mislukt'; $payment_status_103 = 'Verlopen'; $payment_status_999 = 'Geannuleerd'; -$payment_method_3 = ' Debit/Credit'; $contract_billing4 = ' Eenmalig vooraf'; $message_build = ' Gelieve opnieuw in te loggen'; $username_enter = 'Vul je gebruikersnaam in'; @@ -1020,4 +1035,8 @@ $focus_offering_1 = 'Premium - Highend'; $dealer_type_0 = 'Lokaal'; $dealer_type_1 = 'Professioneel'; $dealer_type_2 = 'Bedrijf'; +$user_information_required = 'Gebruikersinformatie Vereist'; +$user_information_description = 'Geef uw gegevens op om door te gaan met software-updates'; +$general_continue = 'Doorgaan'; +$payment_method_3 = 'PayPal'; ?> \ No newline at end of file diff --git a/settings/translations/translations_PL.php b/settings/translations/translations_PL.php index f07f593..9658c67 100644 --- a/settings/translations/translations_PL.php +++ b/settings/translations/translations_PL.php @@ -28,6 +28,9 @@ $menu_report_usage = 'Użycie systemu'; $menu_maintenance = 'Konserwacja'; $menu_profiles = 'Profile'; $menu_upgrades = 'Aktualizacje Oprogramowania'; +$menu_sales_licenses = 'Licencje'; +$menu_softwaretool = 'Aktualizacja Oprogramowania'; +$menu_products_software_versions = 'Oprogramowanie'; $tab1 = 'Ogólne'; $tab2 = 'Partnerzy'; $tab3 = 'Dziennik'; @@ -319,6 +322,21 @@ $firmwaretool_step_5 = 'Gdy oprogramowanie jest dostępne: Pasek stanu pokaże " $firmwaretool_step_6 = 'Gdy oprogramowanie jest dostępne: Upewnij się, że SN i HW zostały odczytane z urządzenia i potwierdź to zaznaczając pole "Potwierdzam, że SN i HW zostały odczytane z urządzenia"'; $firmwaretool_step_7 = 'Naciśnij przycisk "Aktualizuj oprogramowanie", aby uruchomić okno aktualizacji oprogramowania i postępuj zgodnie z instrukcjami na ekranie'; $firmwaretool_step_8 = 'Uwaga: Ten proces nie może zostać przerwany i musi się zakończyć.'; +$softwaretool_h2 = 'Narzędzie aktualizacji oprogramowania'; +$softwaretool_p = 'Opcje aktualizacji oprogramowania.'; +$softwaretool_step = 'Instrukcje'; +$softwaretool_step_1 = 'Podłącz urządzenie do komputera przez USB. (Znajduje się pod pokrywą baterii)'; +$softwaretool_step_2 = 'Naciśnij przycisk "połącz"'; +$softwaretool_step_3 = 'Pojawi się okno popup z prośbą o wybranie urządzenia. Wybierz urządzenie klikając na nie, a następnie naciśnij przycisk połącz.'; +$softwaretool_step_4 = 'Po zniknięciu okna popup urządzenie zostanie odczytane, pasek stanu pokaże postęp'; +$softwaretool_step_5 = 'Dostępne aktualizacje oprogramowania zostaną wyświetlone z Nazwą, Opisem i Ceną'; +$softwaretool_step_6 = 'Wybierz wersję oprogramowania'; +$softwaretool_step_7 = 'W przypadku płatnych aktualizacji postępuj zgodnie z procesem płatności'; +$softwaretool_step_8 = 'Uwaga: Ten proces nie może zostać przerwany i musi się zakończyć.'; +$softwaretool_no_updates = 'Nie znaleziono aktualizacji oprogramowania'; +$softwaretool_checking = 'Sprawdzanie aktualizacji oprogramowania...'; +$softwaretool_available = 'Dostępne aktualizacje oprogramowania'; +$softwaretool_select_upgrade = 'Wybierz opcję aktualizacji:'; $newuser_subject = 'Użytkownik CustomerPortal utworzony'; $newuser_header = 'Szanowny użytkowniku CustomerPortal'; $newuser_text = 'Administrator CustomerPortal przyznał Ci dostęp do CustomerPortal. Aby ukończyć konfigurację konta, musisz zaktualizować hasło za pomocą poniższego linku.'; @@ -358,7 +376,7 @@ $register_6_button = 'Wstecz'; $register_message_1 = 'Numer seryjny nie został rozpoznany, skontaktuj się z dostawcą'; $register_message_2 = 'Produkt objęty gwarancją lub gwarancja wygasła. Jeśli jest to nieprawidłowe, skontaktuj się z dostawcą'; $register_message_3 = 'Gwarancja przedłużona o 1 dodatkowy rok'; -$register_message_4 = 'Dziękujemy za rejestrację. Otrzymasz link do naszego narzędzia aktualizacji oprogramowania na adres e-mail podany podczas rejestracji. W przypadku nieotrzymania naszego e-maila możesz uzyskać dostęp do naszego narzędzia aktualizacji oprogramowania przez naszą stronę internetową. Przejdź do Moje logowanie i użyj nazwy użytkownika/hasła:'; +$register_message_4 = 'Dziękujemy za rejestrację. Otrzymasz link do naszego narzędzia aktualizacji oprogramowania na adres e-mail podany podczas rejestracji.

W przypadku nieotrzymania naszego e-maila możesz uzyskać dostęp do naszego narzędzia aktualizacji oprogramowania przez naszą stronę internetową.

Przejdź do Moje logowanie i użyj nazwy użytkownika/hasła:'; $communication_h2 = 'Komunikacja'; $communication_p = 'Przeglądaj, zarządzaj i wyszukuj komunikację.'; $communication_status = 'Status'; @@ -746,4 +764,9 @@ $general_currency_1 = '$'; $general_currency_0 = '€'; $general_modifier_0 = '-'; $general_modifier_1 = '+'; +$user_information_required = 'Wymagane Informacje o Użytkowniku'; +$user_information_description = 'Podaj swoje dane, aby kontynuować aktualizacje oprogramowania'; +$general_continue = 'Kontynuuj'; +$payment_method_1 = 'Debit/Credit'; +$payment_method_3 = 'PayPal'; ?> \ No newline at end of file diff --git a/settings/translations/translations_PT.php b/settings/translations/translations_PT.php index 1f7fd5d..245ceb4 100644 --- a/settings/translations/translations_PT.php +++ b/settings/translations/translations_PT.php @@ -28,6 +28,9 @@ $menu_report_usage = 'Uso do Sistema'; $menu_maintenance = 'Manutenção'; $menu_profiles = 'Perfis'; $menu_upgrades = 'Atualizações de Software'; +$menu_sales_licenses = 'Licenças'; +$menu_softwaretool = 'Atualização de Software'; +$menu_products_software_versions = 'Software'; $tab1 = 'Geral'; $tab2 = 'Parceiros'; $tab3 = 'Registro'; @@ -319,6 +322,21 @@ $firmwaretool_step_5 = 'Quando o firmware estiver disponível: A barra de status $firmwaretool_step_6 = 'Quando o firmware estiver disponível: Certifique-se de que o SN e o HW sejam lidos do dispositivo e confirme isso selecionando a caixa "Eu confirmo que o SN e o HW foram lidos do dispositivo"'; $firmwaretool_step_7 = 'Pressione o botão "Atualizar firmware" para iniciar o diálogo de atualização de firmware e siga as instruções na tela'; $firmwaretool_step_8 = 'Esteja ciente: Este processo não pode ser interrompido e precisa ser concluído.'; +$softwaretool_h2 = 'Ferramenta de atualização de software'; +$softwaretool_p = 'Opções de atualização de software.'; +$softwaretool_step = 'Instruções'; +$softwaretool_step_1 = 'Conecte o dispositivo ao computador por USB. (Encontrado sob a tampa da bateria)'; +$softwaretool_step_2 = 'Pressione o botão "conectar"'; +$softwaretool_step_3 = 'Um popup aparecerá pedindo para selecionar um dispositivo. Selecione o dispositivo clicando nele e depois pressione o botão conectar.'; +$softwaretool_step_4 = 'Após o popup desaparecer, o dispositivo será lido, a barra de status mostrará o progresso'; +$softwaretool_step_5 = 'Atualizações de software disponíveis serão exibidas com Nome, Descrição e Preço'; +$softwaretool_step_6 = 'Selecione uma versão de software'; +$softwaretool_step_7 = 'Para atualizações pagas, siga o processo de pagamento'; +$softwaretool_step_8 = 'Esteja ciente: Este processo não pode ser interrompido e precisa ser concluído.'; +$softwaretool_no_updates = 'Nenhuma atualização de software encontrada'; +$softwaretool_checking = 'Verificando atualizações de software...'; +$softwaretool_available = 'Atualizações de software disponíveis'; +$softwaretool_select_upgrade = 'Selecione uma opção de atualização:'; $newuser_subject = 'Usuário do CustomerPortal criado'; $newuser_header = 'Caro usuário do CustomerPortal'; $newuser_text = 'Seu administrador do CustomerPortal forneceu acesso ao CustomerPortal. Para completar sua conta, você precisa atualizar sua senha através do link abaixo.'; @@ -351,16 +369,14 @@ $register_3_term_consent_1 = 'Eu concordo com '; $register_3_term_consent_2 = 'termos e condições'; $register_button = 'Registrar'; $register_mandatory = 'obrigatório'; -$register_4_completed = '4. Registro Concluído'; -$register_5_details = '5. Detalhes do Registro'; -$register_6_return = '6. Retornar ao registro'; +$register_4_completed = 'Registro Concluído'; +$register_5_details = 'Detalhes do Registro'; +$register_6_return = 'Retornar ao registro'; $register_6_button = 'Voltar'; $register_message_1 = 'Número de série não reconhecido, entre em contato com seu fornecedor'; $register_message_2 = 'Produto sob garantia ou garantia expirada. Se não estiver correto, entre em contato com seu fornecedor'; $register_message_3 = 'Garantia é estendida por 1 ano adicional'; -$register_message_4 = 'Obrigado pelo seu registro. Você receberá o link para nossa ferramenta de atualização de software pelo e-mail fornecido durante o registro. - -Caso não receba o nosso e-mail, pode aceder à nossa ferramenta de atualização de software através do nosso website. Vá para Meu Login e utilize o nome de utilizador/palavra-passe:'; +$register_message_4 = 'Obrigado pelo seu registro. Você receberá o link para nossa ferramenta de atualização de software pelo e-mail fornecido durante o registro.

Caso não receba o nosso e-mail, pode aceder à nossa ferramenta de atualização de software através do nosso website.

Vá para Meu Login e utilize o nome de utilizador/palavra-passe:'; $communication_h2 = 'Comunicação'; $communication_p = 'Ver, gerenciar e buscar comunicação.'; $communication_status = 'Status'; @@ -814,4 +830,9 @@ $focus_offering_1 = 'Premium - Alta gama'; $dealer_type_0 = 'Local'; $dealer_type_1 = 'Profissional'; $dealer_type_2 = 'Corporativo'; +$user_information_required = 'Informações do Usuário Obrigatórias'; +$user_information_description = 'Por favor, forneça suas informações para continuar com as atualizações de software'; +$general_continue = 'Continuar'; +$payment_method_1 = 'Debit/Credit'; +$payment_method_3 = 'PayPal'; ?> \ No newline at end of file diff --git a/settings/translations/translations_US.php b/settings/translations/translations_US.php index 2daac2d..7479fae 100644 --- a/settings/translations/translations_US.php +++ b/settings/translations/translations_US.php @@ -29,6 +29,9 @@ $menu_report_usage = 'System usage'; $menu_maintenance = 'Maintenance'; $menu_profiles = 'Profiles'; $menu_upgrades = 'Software Upgrades'; +$menu_sales_licenses = 'Licenses'; +$menu_softwaretool = 'Software Update'; +$menu_products_software_versions = 'Software'; $tab1 = 'General'; $tab2 = 'Partners'; $tab3 = 'Log'; @@ -336,8 +339,8 @@ $softwaretool_step_2 = 'Press "connect" button"'; $softwaretool_step_3 = 'A popup will appear asking to select a device. Select the device by clicking on it and the press the connect button.'; $softwaretool_step_4 = 'After pop-up disappears the device will be read, status bar will show progress'; $softwaretool_step_5 = 'Available software upgrades will be displayed with Name, Description and Price'; -$softwaretool_step_6 = 'Select a free upgrade (price = 0) to download and install'; -$softwaretool_step_7 = 'For paid upgrades, please contact support'; +$softwaretool_step_6 = 'Select a software version'; +$softwaretool_step_7 = 'For paid upgrades, please follow the payment process'; $softwaretool_step_8 = 'Be aware: This process cannot be stopped and needs to finish.'; $softwaretool_no_updates = 'No software updates found'; $softwaretool_checking = 'Checking for software updates...'; @@ -375,16 +378,14 @@ $register_3_term_consent_1 = 'I agree with '; $register_3_term_consent_2 = 'terms and conditions'; $register_button = 'Register'; $register_mandatory = 'mandatory'; -$register_4_completed = '4. Registration Completed'; -$register_5_details = '5. Registration details'; -$register_6_return = '6. Return to registration'; +$register_4_completed = 'Registration Completed'; +$register_5_details = 'Registration details'; +$register_6_return = 'Return to registration'; $register_6_button = 'Back'; $register_message_1 = 'Serialnumber not recognized, please contact your supplier'; $register_message_2 = 'Product under warranty or warranty expired. If not correct please contact your supplier'; $register_message_3 = 'Warranty is extended with 1 additional year'; -$register_message_4 = 'Thank you for your registration. You will receive the link to our software update tool via the mail provided during the registration. - -In case you do not receive our email you can access our software update tool via our website. Go to My Login and use username/password:'; +$register_message_4 = 'Thank you for your registration. You will receive the link to our software update tool via the mail provided during the registration.

In case you do not receive our email you can access our software update tool via our website.

Go to My Login and use username/password:'; $communication_h2 = 'Communication'; $communication_p = 'View, manage, and search communication.'; $communication_status = 'Status'; @@ -843,7 +844,6 @@ $shipping_zip = 'Zip'; $shipping_country = 'Country'; $shipping_phone = 'Phone'; $payment_method = 'Payment Method'; -$payment_method_1 = 'Debit (NL/BE customers)'; $payment_method_2 = 'Pay on delivery'; $h2_shoppingcart = 'Shopping Cart'; $discount_label = 'Discount Code'; @@ -990,7 +990,7 @@ $payment_status_101 = 'Pending'; $payment_status_102 = 'Failed'; $payment_status_103 = 'Expired'; $payment_status_999 = 'Cancelled'; -$payment_method_3 = 'Debit/Credit'; +$payment_method_1 = 'Debit/Credit'; $contract_billing4 = ' Onetime upfront'; $message_build = ' Please login again'; $username_enter = 'Please enter username'; @@ -1049,4 +1049,8 @@ $focus_offering_1 = 'Premium - Highend'; $dealer_type_0 = 'Local'; $dealer_type_1 = 'Professional'; $dealer_type_2 = 'Corporate'; +$user_information_required = 'User Information Required'; +$user_information_description = 'Please provide your information to continue with software updates'; +$general_continue = 'Continue'; +$payment_method_3 = 'PayPal'; ?> \ No newline at end of file diff --git a/shipping.php b/shipping.php index 16982bc..e140d2d 100644 --- a/shipping.php +++ b/shipping.php @@ -1,7 +1,7 @@ '', @@ -104,7 +104,7 @@ if ($delete_allowed === 1){ $view .= ''; } if ($update_allowed === 1){ - $view .= ''; + $view .= ''; } $view .= '
'; diff --git a/softwaretool.php b/softwaretool.php index c419d56..bd2951d 100644 --- a/softwaretool.php +++ b/softwaretool.php @@ -1,7 +1,7 @@ prepare($sql); + $stmt->execute([$payment_return]); + + if (debug) { + debuglog("Payment cancelled - Order ID: {$payment_return} marked as cancelled (999)"); + } + } catch (Exception $e) { + if (debug) { + debuglog("Error marking order as cancelled: " . $e->getMessage()); + } + } +} + +// Handle PayPal return - capture the order directly +if ($paypal_token && $payment_return) { + try { + // Get PayPal access token + $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); + curl_close($ch); + $token_data = json_decode($response, true); + $access_token = $token_data['access_token'] ?? ''; + + if ($access_token) { + // Capture the PayPal order + $capture_url = PAYPAL_URL . "/v2/checkout/orders/{$paypal_token}/capture"; + $ch = curl_init($capture_url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + 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 (debug) { + debuglog("PayPal Capture: HTTP $http_code - $response"); + } + + // Update transaction status based on capture result + if ($http_code == 200 || $http_code == 201) { + $capture_result = json_decode($response, true); + $capture_status = $capture_result['status'] ?? ''; + + $payment_status = null; + if ($capture_status === 'COMPLETED') { + $payment_status = 1; // Paid + } elseif ($capture_status === 'PENDING') { + $payment_status = 101; // Pending + } + + if ($payment_status !== null) { + $pdo = dbConnect($dbname); + $sql = 'UPDATE transactions SET payment_status = ? WHERE txn_id = ?'; + $stmt = $pdo->prepare($sql); + $stmt->execute([$payment_status, $payment_return]); + } + } + } + + // Redirect to clean URL + header("Location: ?page=softwaretool&payment_return=1&order_id={$payment_return}"); + exit; + + } catch (Exception $e) { + if (debug) { + debuglog("PayPal Capture Error: " . $e->getMessage()); + } + } +} template_header('Softwaretool', 'softwaretool','view'); @@ -40,16 +124,19 @@ if ($payment_return && $payment_return_status) { $payment_modal = ' '; +
+ '; } else if ($transaction['payment_status'] == 0 || $transaction['payment_status'] == 101) { // Payment pending $payment_modal = ' @@ -68,8 +155,23 @@ if ($payment_return && $payment_return_status) { // Auto-refresh every 3 seconds to check payment status setTimeout(function() { location.reload(); }, 3000); '; + } else if ($transaction['payment_status'] == 999) { + // Payment cancelled + $payment_modal = ' + '; } else { - // Payment failed/cancelled + // Payment failed/expired $payment_modal = '
-
- - -
-
diff --git a/orders.php b/orders.php index 64019d3..f130384 100644 --- a/orders.php +++ b/orders.php @@ -1,7 +1,7 @@ '; } if ($update_allowed === 1){ - $view .= ''; + $view .= ''; } $view .= ''; @@ -130,10 +130,15 @@ $view .= '
+ + + + + + + + + + + + + + +
'; + $view .= ''; diff --git a/partners.php b/partners.php index b348705..1ff7852 100644 --- a/partners.php +++ b/partners.php @@ -1,7 +1,7 @@ '; } if ($update_allowed === 1){ - $view .= ''; + $view .= ''; } $view .= ''; diff --git a/product.php b/product.php index 6ad6dfe..538e6ca 100644 --- a/product.php +++ b/product.php @@ -1,7 +1,7 @@ ←':''; //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,12 +27,12 @@ $pagination_page = $_SESSION['p'] = isset($_GET['p']) ? $_GET['p'] : 1; //PAGE Security $page_manage = 'product_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'); -$media_update = isAllowed('products_media' ,$_SESSION['profile'],$_SESSION['permission'],'U'); -$software_update = isAllowed('products_software_assignment' ,$_SESSION['profile'],$_SESSION['permission'],'U'); +$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'); +$media_update = isAllowed('products_media' ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U'); +$software_update = isAllowed('products_software_assignment' ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U'); //GET Details from URL $GET_VALUES = urlGETdetails($_GET) ?? ''; @@ -183,11 +183,11 @@ $view .= '

'.$product_category.'

-

'.$$product_category_text.'

+

'.$$product_category_text.'

'.$product_parttype.'

-

'.$$parttype_text.'

+

'.$$parttype_text.'

'.$product_code.'

@@ -268,7 +268,7 @@ $view .= '
$view .= '
'.$version->rowID.''.(($version->status == 1)? ''.$prod_status_1:''.$prod_status_0).''.(($version->status == 1)? $prod_status_1:''.$prod_status_0).' '.$version->version.' '.(!empty($version->config) ? ($general_yes ?? 'Y') : ($general_no ?? 'N')).'
+ + + + + + + + + + + + +
'.($User_permission ?? 'Permission Level').' + '; + +// Display permission level text +switch($user->view){ + case 1: $view .= ($permission1 ?? 'View'); break; + case 2: $view .= ($permission2 ?? 'Edit'); break; + case 3: $view .= ($permission3 ?? 'Admin'); break; + case 4: $view .= ($permission4 ?? 'Super Admin'); break; + case 5: $view .= ($permission5 ?? 'System'); break; + default: $view .= '-'; +} + +$view .= ' + +
'.($User_profile ?? 'Profile').' + '.($user->settings ?? '-').''; + +if ($_SESSION['authorization']['permission'] == 3 || $_SESSION['authorization']['permission'] == 4){ + $view .= ''; +} else { + $view .= ''; +} + +$view .= '
'.($User_service ?? 'Service Access').' + '.(($service_active == 1) ? ($enabled ?? 'Enabled') : ($disabled ?? 'Disabled')).' + +
+
'; -$view .= '
- '.$tab2.' -
'; +// Partner Hierarchy Block +$partner_data = json_decode($user->partnerhierarchy) ?? json_decode($_SESSION['authorization']['partnerhierarchy']); -//GET PARTNERDATA -$partner_data = json_decode($user['partnerhierarchy'])?? json_decode($_SESSION['partnerhierarchy']) ; -//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,''); +$view .= '
+
+ '.($view_user_partners ?? 'Partner Hierarchy').' +
+
+ '; -//DISPLAY -$view .= '
-
-'; -if ($_SESSION['permission'] == 3 || $_SESSION['permission'] == 4){ - $view .= ''; - $view .= $salesid_dropdown; - $view .= ''; - $view .= $soldto_dropdown; +if ($_SESSION['authorization']['permission'] == 3 || $_SESSION['authorization']['permission'] == 4){ + $salesid_dropdown = listPartner('salesid', $_SESSION['authorization']['permission'], $partner_data->salesid ?? '', ''); + $soldto_dropdown = listPartner('soldto', $_SESSION['authorization']['permission'], $partner_data->soldto ?? '', ''); + + $view .= '
+ + + + + + + '; } -$view .= ''; -$view .= $shipto_dropdown; -$view .= ''; -$view .= $location_dropdown; -$view .= ' - + +$shipto_dropdown = listPartner('shipto', $_SESSION['authorization']['permission'], $partner_data->shipto ?? '', ''); +$location_dropdown = listPartner('location', $_SESSION['authorization']['permission'], $partner_data->location ?? '', ''); + +$view .= ' + + + + + + + +
'.($general_salesid ?? 'Sales ID').' + '.($partner_data->salesid ?? '-').' + '.$salesid_dropdown.' +
'.($general_soldto ?? 'Sold To').' + '.($partner_data->soldto ?? '-').' + '.$soldto_dropdown.' +
'.($general_shipto ?? 'Ship To').' + '.($partner_data->shipto ?? '-').' + '.$shipto_dropdown.' +
'.($general_location ?? 'Location').' + '.($partner_data->location ?? '-').' + '.$location_dropdown.' +
+
'; -$view .= '
- '.$tab3.' -
'; +// Metadata Block (hide for new users) +if (!$is_new_user) { + $view .= '
+
+ '.($tab3 ?? 'Details').' +
+
+ + + + + + + + + + + + + + + + + + + + + +
'.($general_created ?? 'Created').''.getRelativeTime($user->created).'
'.($User_lastlogin ?? 'Last Login').''.($user->lastlogin ? getRelativeTime($user->lastlogin) : '-').'
'.($general_updated ?? 'Updated').''.($user->updated ? getRelativeTime($user->updated) : '-').'
'.($general_updatedby ?? 'Updated By').''.($user->updatedby ?? '-').'
'.($User_pw_login_count ?? 'Login Attempts').' + '.$user->login_count.''; + if ($_SESSION['authorization']['permission'] == 3 || $_SESSION['authorization']['permission'] == 4){ + $view .= ''; + } else { + $view .= ''; + } -//SUPERUSERS AND ADMINS CAN RESET BLOCKED USERS -if ($_SESSION['permission'] == 3 || $_SESSION['permission'] == 4){ - $login_count = ''; -} else { - $login_count = ''; -} - -$view .= '
-
- - - - - - - - - - '.$login_count.' + $view .= '
'; +} +$view .= ' +'; -if ($update_allowed === 1 && $user_ID !=''){ -$view .= ''; -$view .= '
-
- - - - +// Actions Block (outside form for separate actions, hide for new users) +if ($update_allowed === 1 && !$is_new_user){ +$view .= '
+
+ '.($general_actions ?? 'Actions').' +
+
+ + + + + '; + +if ($is_blocked){ +$view .= ' + + + '; +} + +if ($delete_allowed === 1){ +$view .= ' + + + '; +} + +$view .= '
'.($User_pw_reset ?? 'Reset Password').' +
+ + +
+
'.($User_unblock ?? 'Unblock User').' +
+ + +
+
'.($general_delete ?? 'Delete User').' +
+ + +
+
'; } -$view .= ''; - -//Output +//OUTPUT echo $view; -template_footer() -?> \ No newline at end of file + +$js = 'var userEditMode = false; + +function toggleUserEdit() { + userEditMode = !userEditMode; + var editBtn = document.getElementById("editBtn"); + var saveBtn = document.getElementById("saveBtn"); + var viewElements = document.querySelectorAll(".view-mode"); + var editElements = document.querySelectorAll(".edit-mode"); + var viewRolesElements = document.querySelectorAll(".view-mode-roles"); + var editRolesElements = document.querySelectorAll(".edit-mode-roles"); + var i; + if (userEditMode) { + // Enter edit mode for user info AND roles + for (i = 0; i < viewElements.length; i++) { viewElements[i].style.display = "none"; } + for (i = 0; i < editElements.length; i++) { editElements[i].style.display = "inline"; } + for (i = 0; i < viewRolesElements.length; i++) { viewRolesElements[i].style.display = "none"; } + for (i = 0; i < editRolesElements.length; i++) { editRolesElements[i].style.display = "block"; } + editBtn.style.display = "none"; + saveBtn.style.display = "inline-block"; + } else { + // Exit edit mode + for (i = 0; i < viewElements.length; i++) { viewElements[i].style.display = "inline"; } + for (i = 0; i < editElements.length; i++) { editElements[i].style.display = "none"; } + for (i = 0; i < viewRolesElements.length; i++) { viewRolesElements[i].style.display = "block"; } + for (i = 0; i < editRolesElements.length; i++) { editRolesElements[i].style.display = "none"; } + editBtn.style.display = "inline-block"; + saveBtn.style.display = "none"; + } +}'; + +template_footer($js); diff --git a/user_role.php b/user_role.php new file mode 100644 index 0000000..736f342 --- /dev/null +++ b/user_role.php @@ -0,0 +1,538 @@ +rowID; + +//CALL TO API FOR Role Permissions +$api_url = '/v2/role_access_permissions/role_id='.$role_id; +$permissions = ioServer($api_url,''); +//Decode Payload +if (!empty($permissions)){$permissions = json_decode($permissions);}else{$permissions = null;} + +//CALL TO API FOR All Access Elements (no paging) +$api_url = '/v2/access_elements/all='; +$all_access_elements = ioServer($api_url,''); +//Decode Payload +if (!empty($all_access_elements)){$all_access_elements = json_decode($all_access_elements);}else{$all_access_elements = null;} + +// Create lookup array for existing permissions +$permission_lookup = []; +if (!empty($permissions)){ + foreach ($permissions as $perm){ + $permission_lookup[$perm->access_id] = $perm; + } +} + +//CALL TO API FOR User Role Assignments +$api_url = '/v2/user_role_assignments/role_id='.$role_id; +$assignments = ioServer($api_url,''); +//Decode Payload +if (!empty($assignments)){$assignments = json_decode($assignments);}else{$assignments = null;} + +//CALL TO API FOR All User Roles (for copy dropdown) +$api_url = '/v2/user_roles/status=1&all='; +$all_roles = ioServer($api_url,''); +//Decode Payload +if (!empty($all_roles)){$all_roles = json_decode($all_roles);}else{$all_roles = null;} + +//------------------------------ +// Handle POST for inline edit +//------------------------------ +if (isset($_POST['save_permissions']) && $update_allowed_edit === 1) { + // Update role info (name, description, status, system role) + $role_data_array = [ + 'rowID' => $role_id, + 'name' => $_POST['name'] ?? '', + 'description' => $_POST['description'] ?? '', + 'is_active' => $_POST['is_active'] ?? 1 + ]; + // Only allow is_system to be changed if user has delete permission on user_roles + if ($system_role_allowed === 1) { + $role_data_array['is_system'] = isset($_POST['is_system']) ? 1 : 0; + } + $role_data = json_encode($role_data_array, JSON_UNESCAPED_UNICODE); + ioServer('/v2/user_roles', $role_data); + + // Process permission updates + $posted_permissions = $_POST['permissions'] ?? []; + + // For each access element, update or create permission + foreach ($all_access_elements as $element) { + $access_id = $element->rowID; + $has_permission = isset($posted_permissions[$access_id]); + $existing_permission = $permission_lookup[$access_id] ?? null; + + if ($has_permission) { + // Get CRUD values + $can_create = isset($posted_permissions[$access_id]['C']) ? 1 : 0; + $can_read = isset($posted_permissions[$access_id]['R']) ? 1 : 0; + $can_update = isset($posted_permissions[$access_id]['U']) ? 1 : 0; + $can_delete = isset($posted_permissions[$access_id]['D']) ? 1 : 0; + + if ($existing_permission) { + // Update existing permission + $data = json_encode([ + 'rowID' => $existing_permission->rowID, + 'role_id' => $role_id, + 'access_id' => $access_id, + 'can_create' => $can_create, + 'can_read' => $can_read, + 'can_update' => $can_update, + 'can_delete' => $can_delete + ], JSON_UNESCAPED_UNICODE); + } else { + // Insert new permission + $data = json_encode([ + 'role_id' => $role_id, + 'access_id' => $access_id, + 'can_create' => $can_create, + 'can_read' => $can_read, + 'can_update' => $can_update, + 'can_delete' => $can_delete + ], JSON_UNESCAPED_UNICODE); + } + ioServer('/v2/role_access_permissions', $data); + } else { + // If no permission checkboxes selected but had existing permission, delete it + if ($existing_permission) { + $data = json_encode([ + 'rowID' => $existing_permission->rowID, + 'delete' => 'delete' + ], JSON_UNESCAPED_UNICODE); + ioServer('/v2/role_access_permissions', $data); + } + } + } + + // Redirect to refresh + header('Location: index.php?page=user_role&rowID='.$role_id.'&success_msg=2'); + exit; +} + +//------------------------------ +// Handle POST for delete +//------------------------------ +if (isset($_POST['delete_role']) && $delete_allowed === 1) { + $role_data = json_encode([ + 'rowID' => $role_id, + 'delete' => 'delete' + ], JSON_UNESCAPED_UNICODE); + ioServer('/v2/user_roles', $role_data); + + // Redirect to roles list with success message + header('Location: index.php?page='.$_SESSION['origin'].'&success_msg=3'); + exit; +} + + +//------------------------------ +//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_role_1 ?? 'Role created successfully'); + } + if ($_GET['success_msg'] == 2) { + $success_msg = ($message_role_2 ?? 'Role updated successfully'); + } + if ($_GET['success_msg'] == 3) { + $success_msg = ($message_role_3 ?? 'Role deleted successfully'); + } +} + +template_header(($user_role_title ?? 'User Role'), 'user_role', 'view'); +$view = ' +
+

'.($view_role_h2 ?? 'User Role').' - '.$responses->name.'

+ +'; + +if ($update_allowed_edit === 1){ + $view .= '✏️'; + $view .= ''; +} + +if ($delete_allowed === 1){ + $view .= '🗑️'; +} + +$view .= '
'; + +if (isset($success_msg)){ + $view .= '
+ +

'.$success_msg.'

+ +
'; +} + +// Delete form (hidden) +if ($delete_allowed === 1){ + $view .= ''; + + // Delete confirmation modal + $view .= ''; +} + +// Start form wrapper for edit mode +$view .= '
+ + '; + +$view .= '
'; + +// Role Information Block +$view .= '
+
+ '.($view_role_information ?? 'Role Information').' +
+
+

'.($general_status ?? 'Status').'

+

+ '.$status_text.' + +

+
+
+

'.($role_name ?? 'Role Name').'

+

+ '.$responses->name.' + +

+
+
+

'.($role_description ?? 'Description').'

+

+ '.($responses->description ?? '-').' + +

+
+
+

'.($role_system ?? 'System Role').'

+

+ '.($responses->is_system == 1 ? ' '.($yes ?? 'Yes') : ' '.($no ?? 'No')).' + '.($system_role_allowed === 1 ? '' : '').' +

+
+ +
+'; + +// Role Assignments Block +$view .='
+
+ '.($view_role_assignments ?? 'Assigned Users').' +
'; + +if (!empty($assignments)){ + foreach ($assignments as $assignment){ + $assignment_status = ($assignment->is_active == 1) ? ($enabled ?? 'Active') : ($disabled ?? 'Inactive'); + $view .= '
+

'.$assignment->username.'

+

'.$assignment_status.((!empty($assignment->expires_at))? ' - '.($expires ?? 'Expires').': '.$assignment->expires_at : '').'

+
'; + } +} else { + $view .= '
+

-

+

'.($no_users_assigned ?? 'No users assigned to this role').'

+
'; +} + +$view .= '
'; + +$view .= '
'; // Close content-block-wrapper + +// Permissions Table Block +$view .= '
+
+ '.($view_role_permissions ?? 'Role Permissions').' +
+
+ + + + + + + + + + + + + '; + +if (!empty($all_access_elements)){ + foreach ($all_access_elements as $element){ + $access_id = $element->rowID; + $existing_perm = $permission_lookup[$access_id] ?? null; + + // Determine current permission values + $has_create = ($existing_perm && $existing_perm->can_create == 1); + $has_read = ($existing_perm && $existing_perm->can_read == 1); + $has_update = ($existing_perm && $existing_perm->can_update == 1); + $has_delete = ($existing_perm && $existing_perm->can_delete == 1); + $has_any_permission = ($has_create || $has_read || $has_update || $has_delete); + + // Row class - hide unassigned rows in view mode + $row_class = $has_any_permission ? '' : ' class="edit-only-row" style="display:none;"'; + + // View mode icons + $icon_create = $has_create ? '' : ''; + $icon_read = $has_read ? '' : ''; + $icon_update = $has_update ? '' : ''; + $icon_delete = $has_delete ? '' : ''; + + // Edit mode checkboxes + $cb_create_checked = $has_create ? ' checked' : ''; + $cb_read_checked = $has_read ? ' checked' : ''; + $cb_update_checked = $has_update ? ' checked' : ''; + $cb_delete_checked = $has_delete ? ' checked' : ''; + + $view .= ' + + + + + + + + '; + } +} else { + $view .= ' + + '; +} + +$view .= ' +
'.($access_element_name ?? 'Access Element').''.($access_element_path ?? 'Path').''.($access_element_group ?? 'Group').''.($permission_create ?? 'C').''.($permission_read ?? 'R').''.($permission_update ?? 'U').''.($permission_delete ?? 'D').'
'.$element->access_name.''.$element->access_path.''.($element->access_group ?? '-').' + '.$icon_create.' + + + '.$icon_read.' + + + '.$icon_update.' + + + '.$icon_delete.' + +
'.($no_access_elements ?? 'No access elements found').'
+
+
+ '; + +// Metadata Block +$view .= '
+
+ '.($tab3 ?? 'Details').' +
+
+ + + + + + + + + +
'.($general_created ?? 'Created').''.getRelativeTime($responses->created).'
'.($general_updated ?? 'Updated').''.getRelativeTime($responses->updated).'
+
+
+
+'; + +//OUTPUT +echo $view; + +$js = 'var permissionsEditMode = false; +function togglePermissionsEdit() { + permissionsEditMode = !permissionsEditMode; + var editBtn = document.getElementById("editBtn"); + var saveBtn = document.getElementById("saveBtn"); + var viewElements = document.querySelectorAll(".view-mode"); + var editElements = document.querySelectorAll(".edit-mode"); + var editBlockElements = document.querySelectorAll(".edit-mode-block"); + var editOnlyRows = document.querySelectorAll(".edit-only-row"); + var i; + if (permissionsEditMode) { + for (i = 0; i < viewElements.length; i++) { viewElements[i].style.display = "none"; } + for (i = 0; i < editElements.length; i++) { editElements[i].style.display = "inline"; } + for (i = 0; i < editBlockElements.length; i++) { editBlockElements[i].style.display = "block"; } + for (i = 0; i < editOnlyRows.length; i++) { editOnlyRows[i].style.display = "table-row"; } + editBtn.style.display = "none"; + saveBtn.style.display = "inline-block"; + } else { + for (i = 0; i < viewElements.length; i++) { viewElements[i].style.display = "inline"; } + for (i = 0; i < editElements.length; i++) { editElements[i].style.display = "none"; } + for (i = 0; i < editBlockElements.length; i++) { editBlockElements[i].style.display = "none"; } + for (i = 0; i < editOnlyRows.length; i++) { editOnlyRows[i].style.display = "none"; } + editBtn.style.display = "inline-block"; + saveBtn.style.display = "none"; + } +} +function toggleColumn(type) { + var checkboxes = document.querySelectorAll("input[name$=\\"[" + type + "]\\"]"); + var allChecked = true; + for (var i = 0; i < checkboxes.length; i++) { + if (!checkboxes[i].checked) { + allChecked = false; + break; + } + } + for (var i = 0; i < checkboxes.length; i++) { + checkboxes[i].checked = !allChecked; + } +} +async function copyPermissionsFromRole(roleId) { + if (!roleId) return; + + if (!confirm("'.($confirm_copy_permissions ?? 'This will override all current permission settings. Continue?').'")) { + document.getElementById("copyFromRole").value = ""; + return; + } + + try { + // Call PHP page which will use ioServer to get permissions + const response = await fetch("index.php?page=user_role&action=get_role_permissions&source_role_id=" + roleId); + if (!response.ok) throw new Error("Failed to fetch"); + const permissions = await response.json(); + + // Create a lookup map of permissions by access_id + var permMap = {}; + if (permissions && permissions.length > 0) { + for (var i = 0; i < permissions.length; i++) { + permMap[permissions[i].access_id] = permissions[i]; + } + } + + // Get all permission checkboxes and reset them + var allCheckboxes = document.querySelectorAll("input[type=checkbox][name^=\\"permissions[\\"]"); + for (var i = 0; i < allCheckboxes.length; i++) { + allCheckboxes[i].checked = false; + } + + // Apply copied permissions + for (var accessId in permMap) { + var perm = permMap[accessId]; + var cbCreate = document.querySelector("input[name=\\"permissions[" + accessId + "][C]\\"]"); + var cbRead = document.querySelector("input[name=\\"permissions[" + accessId + "][R]\\"]"); + var cbUpdate = document.querySelector("input[name=\\"permissions[" + accessId + "][U]\\"]"); + var cbDelete = document.querySelector("input[name=\\"permissions[" + accessId + "][D]\\"]"); + + if (cbCreate && perm.can_create == 1) cbCreate.checked = true; + if (cbRead && perm.can_read == 1) cbRead.checked = true; + if (cbUpdate && perm.can_update == 1) cbUpdate.checked = true; + if (cbDelete && perm.can_delete == 1) cbDelete.checked = true; + } + + // Reset dropdown + document.getElementById("copyFromRole").value = ""; + + } catch (error) { + console.error("Error copying permissions:", error); + alert("'.($error_copy_permissions ?? 'Failed to copy permissions. Please try again.').'"); + document.getElementById("copyFromRole").value = ""; + } +} +function confirmDeleteRole() { + document.getElementById("deleteModal").style.display = "flex"; +} +function closeDeleteModal() { + document.getElementById("deleteModal").style.display = "none"; +} +function executeDeleteRole() { + document.getElementById("deleteRoleForm").submit(); +}'; + +template_footer($js); diff --git a/user_role_manage.php b/user_role_manage.php new file mode 100644 index 0000000..4bee75b --- /dev/null +++ b/user_role_manage.php @@ -0,0 +1,227 @@ + '', + 'name' => '', + 'description' => '', + 'is_active' => 1, + 'created' => '', + 'createdby' => $_SESSION['authorization']['clientID'], + 'updated' => '', + 'updatedby' => '' +]; + +$role_ID = $_GET['rowID'] ?? ''; + +if ($role_ID !=''){ + $url = 'index.php?page=user_role&rowID='.$role_ID.''; +} else { + $url = 'index.php?page=user_roles'; +} + +//GET ALL ACCESS ELEMENTS +$api_url = '/v2/access_elements/status=1'; +$access_elements = ioServer($api_url,''); +//Decode Payload +if (!empty($access_elements)){$access_elements = json_decode($access_elements);}else{$access_elements = null;} + +//GET ROLE PERMISSIONS (if editing) +$role_permissions = []; +if ($role_ID != ''){ + $api_url = '/v2/role_access_permissions/role_id='.$role_ID; + $role_permissions_response = ioServer($api_url,''); + if (!empty($role_permissions_response)){ + $role_permissions_data = json_decode($role_permissions_response); + foreach ($role_permissions_data as $perm){ + $role_permissions[$perm->access_id] = [ + 'can_create' => $perm->can_create, + 'can_read' => $perm->can_read, + 'can_update' => $perm->can_update, + 'can_delete' => $perm->can_delete + ]; + } + } +} + +if (isset($_GET['rowID'])) { + // ID param exists, edit an existing role + //CALL TO API + $api_url = '/v2/user_roles/rowID='.$role_ID; + $responses = ioServer($api_url,''); + //Decode Payload + if (!empty($responses)){$responses = json_decode($responses,true);}else{$responses = null;} + + $role = $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/user_roles', $data); + + if ($responses === 'NOK'){ + + } else { + header('Location: index.php?page=user_role&rowID='.$role_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/user_roles', $data); + // Redirect and delete role + if ($responses === 'NOK'){ + + } else { + header('Location: index.php?page=user_roles&success_msg=3'); + exit; + } + } + } + +} else { + // Create a new role + if (isset($_POST['submit']) && $create_allowed === 1) { + //GET ALL POST DATA + $data = json_encode($_POST, JSON_UNESCAPED_UNICODE); + //API call + $responses = ioServer('/v2/user_roles', $data); + if ($responses === 'NOK'){ + + } else { + header('Location: index.php?page=user_roles&success_msg=1'); + exit; + } + } +} + +template_header(($user_role_title ?? 'User Role'), 'user_role', 'manage'); + +$label_h2 = (($role_ID !='')? ($manage_role_h2 ?? 'Edit Role') : ($button_create_role ?? 'Create Role')); +$view =' +
+
+

'.$label_h2.'

+ +'; + +if ($delete_allowed === 1 && $role_ID != ''){ + $view .= ''; +} +if ($update_allowed === 1 || ($create_allowed === 1 && $role_ID == '')){ + $view .= ''; +} + +$view .= '
'; + +$view .= ' +
+
+ + + + + + + +
+
'; + +// Permissions Tab +$view .= ' +
+
+ + + + + + + + + + + '; + +if (!empty($access_elements)){ + foreach ($access_elements as $element){ + $perm = $role_permissions[$element->rowID] ?? ['can_create' => 0, 'can_read' => 0, 'can_update' => 0, 'can_delete' => 0]; + + $view .= ' + + + + + + '; + } +} else { + $view .= ' + + '; +} + +$view .= ' +
'.($access_element_name ?? 'Access Element').''.($permission_create ?? 'Create').''.($permission_read ?? 'Read').''.($permission_update ?? 'Update').''.($permission_delete ?? 'Delete').'
'.$element->access_name.'
'.$element->access_path.'
'.($no_access_elements ?? 'No access elements found').'
+
+
'; + +//DISPLAY TAB 3 - Metadata +if ($role_ID != ''){ +$view .= ' +
+
+ + + + + + + + +
+
'; +} + +$view .= '
'; + +//Output +echo $view; +template_footer()?> diff --git a/user_roles.php b/user_roles.php new file mode 100644 index 0000000..2b52d82 --- /dev/null +++ b/user_roles.php @@ -0,0 +1,202 @@ +←':''; + +//Check if allowed +if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){ + header('location: index.php'); + exit; +} +//PAGE Security +$page_manage = 'user_role_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'); + +//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=user_roles'.$status.$search.$sort; +//GET Details from URL +$GET_VALUES = urlGETdetails($_GET) ?? ''; +//CALL TO API +$api_url = '/v2/user_roles/'.$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/user_roles/'.$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_role_1 ?? 'Role created successfully'); + } + if ($_GET['success_msg'] == 2) { + $success_msg = ($message_role_2 ?? 'Role updated successfully'); + } + if ($_GET['success_msg'] == 3) { + $success_msg = ($message_role_3 ?? 'Role deleted successfully'); + } +} + +template_header(($user_roles_title ?? 'User Roles'), 'user_roles','view'); +$view = ' +
+
+ +
+

'.($user_roles_h2 ?? 'User Roles').' ('.$query_total.')

+

'.($user_roles_p ?? 'Manage user roles and permissions').'

+
+
+
+ '.$back_btn_orgin; + +if ($create_allowed === 1){ + $view .= '+'; +} + +$view .= ' +
+
'; + +if (isset($success_msg)){ +$view .= '
+ +

'.$success_msg.'

+ +
'; +} + +$view .= ' + +'; +$view .= ' +
+
+ + + + + + + + + + + + '; + + if (empty($responses)){ + + $view .= ' + + + '; + } + +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 .= ' + + + + + + + '; + } + $view .= ' + +
'.($role_name ?? 'Role Name').''.($role_description ?? 'Description').''.($general_status ?? 'Status').''.($role_permissions_count ?? 'Permissions').''.($general_created ?? 'Created').'
'.($message_no_roles ?? 'No roles found').'
'.$response->name.''.($response->description ?? '-').''.$status_text.''.($response->permission_count ?? '0').''.getRelativeTime($response->created).'
+
+
+'; + +$page_rows = $page_rows_equipment ?? 20; +$view.=''; +//OUTPUT +echo $view; + +template_footer(); +?> diff --git a/users.php b/users.php index 025e91c..5cff75a 100644 --- a/users.php +++ b/users.php @@ -1,7 +1,7 @@ ←':''; //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'); +$page = 'user'; +$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 @@ -38,16 +39,16 @@ $url = 'index.php?page=users'.$status.$search; //GET Details from URL $GET_VALUES = urlGETdetails($_GET) ?? ''; //CALL TO API -$api_url = '/v1/users/'.$GET_VALUES; +$api_url = '/v2/users/'.$GET_VALUES; $responses = ioServer($api_url,''); //Decode Payload -if (!empty($responses)){$responses = decode_payload($responses);}else{$responses = null;} +if (!empty($responses)){$responses = json_decode($responses);}else{$responses = null;} //Return QueryTotal from API -$api_url = '/v1/users/'.$GET_VALUES.'&totals='; +$api_url = '/v2/users/'.$GET_VALUES.'&totals='; $query_total = ioServer($api_url,''); //Decode Payload -if (!empty($query_total)){$query_total = decode_payload($query_total);}else{$query_total = null;} +if (!empty($query_total)){$query_total = json_decode($query_total);}else{$query_total = null;} // Handle success messages if (isset($_GET['success_msg'])) { diff --git a/webhook_mollie.php b/webhook_mollie.php index 8ab0333..cac5356 100644 --- a/webhook_mollie.php +++ b/webhook_mollie.php @@ -157,8 +157,8 @@ try { // Create license $sql = 'INSERT INTO products_software_licenses - (version_id, license_type, license_key, status, starts_at, expires_at, transaction_id, created, createdby) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)'; + (version_id, license_type, license_key, status, starts_at, expires_at, transaction_id,accounthierarchy, created, createdby) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; $stmt = $pdo->prepare($sql); $stmt->execute([ $item['item_id'], // version_id @@ -168,6 +168,7 @@ try { date('Y-m-d H:i:s'), '2099-12-31 23:59:59', // effectively permanent $orderId, + '{"salesid":"21-Total Safety Solutions B.V.","soldto":""}', date('Y-m-d H:i:s'), 'webhook' // created by webhook ]); @@ -216,10 +217,8 @@ try { date('Y-m-d H:i:s') ]); $invoice_id = $pdo->lastInsertId(); - debuglog("WEBHOOK: Invoice created with ID: $invoice_id"); } else { $invoice_id = $existing_invoice['id']; - debuglog("WEBHOOK: Invoice already exists with ID: $invoice_id"); } // Fetch full invoice data with customer details for email @@ -238,13 +237,9 @@ try { $stmt->execute([$invoice_id]); $invoice_data = $stmt->fetchAll(PDO::FETCH_ASSOC); - debuglog("WEBHOOK: Invoice data fetched: " . print_r($invoice_data, true)); - if (!empty($invoice_data)) { - debuglog("WEBHOOK: Transforming invoice data..."); // Transform the data (group items like the API does) $invoice_cust = transformOrderData($invoice_data); - debuglog("WEBHOOK: Transformed invoice data: " . print_r($invoice_cust, true)); // Determine invoice language if (!empty($invoice_cust['customer']['language'])) { @@ -256,40 +251,27 @@ try { } // Generate invoice HTML (using custom template for software upgrades) - debuglog("WEBHOOK: Calling generateSoftwareInvoice with language: $invoice_language"); - list($data,$customer_email,$order_id) = generateSoftwareInvoice($invoice_cust,$orderId,$invoice_language); - debuglog("WEBHOOK: Invoice generated - Customer email: $customer_email, Order ID: $order_id"); - debuglog("WEBHOOK: Invoice HTML length: " . strlen($data)); - + list($message,$pdf,$customer_email,$order_id) = generateSoftwareInvoice($invoice_cust,$orderId,$invoice_language); + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ //CREATE PDF using DomPDF //+++++++++++++++++++++++++++++++++++++++++++++++++++++ - debuglog("WEBHOOK: Creating PDF..."); - $dompdf->loadHtml($data); + $dompdf->loadHtml($pdf); $dompdf->setPaper('A4', 'portrait'); $dompdf->render(); $subject = 'Software Upgrade - Invoice: '.$order_id; $attachment = $dompdf->output(); - debuglog("WEBHOOK: PDF created, size: " . strlen($attachment) . " bytes"); //+++++++++++++++++++++++++++++++++++++++++++++++++++++ //Send email via PHPMailer //+++++++++++++++++++++++++++++++++++++++++++++++++++++ - debuglog("WEBHOOK: Attempting to send email to: $customer_email"); - debuglog("WEBHOOK: Email subject: $subject"); - debuglog("WEBHOOK: Email config - Host: " . (defined('email_host_name') ? email_host_name : 'NOT DEFINED')); - debuglog("WEBHOOK: Email config - Port: " . (defined('email_outgoing_port') ? email_outgoing_port : 'NOT DEFINED')); - debuglog("WEBHOOK: Email config - Security: " . (defined('email_outgoing_security') ? email_outgoing_security : 'NOT DEFINED')); - debuglog("WEBHOOK: Email config - Username: " . (defined('email') ? email : 'NOT DEFINED')); - // The send_mail function will exit on error and debuglog the error - $mail_result = send_mail($customer_email, $subject, $data, $attachment, $subject); - debuglog("WEBHOOK: Email sent successfully to: $customer_email"); + $mail_result = send_mail($customer_email, $subject, $message, $attachment, $subject); // Send to bookkeeping if configured if(invoice_bookkeeping){ debuglog("WEBHOOK: Sending to bookkeeping: " . email_bookkeeping); - send_mail(email_bookkeeping, $subject, $data, $attachment, $subject); + send_mail(email_bookkeeping, $subject, $message, $attachment, $subject); } } else { debuglog("WEBHOOK: No invoice data found for invoice_id: $invoice_id"); diff --git a/webhook_paypal.php b/webhook_paypal.php new file mode 100644 index 0000000..f25f56f --- /dev/null +++ b/webhook_paypal.php @@ -0,0 +1,428 @@ + interface_user, "clientsecret" => interface_pw), JSON_UNESCAPED_UNICODE); +$responses = ioAPIv2('/v2/authorization', $data,''); +debuglog("WEBHOOK: Authorization response: " . $responses); +if (!empty($responses)){$responses = json_decode($responses,true);}else{$responses = '400';} +$clientsecret = $responses['token']; +debuglog("WEBHOOK: Token obtained: " . ($clientsecret ? 'YES' : 'NO')); + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++ +// BASEURL is required for invoice template +//+++++++++++++++++++++++++++++++++++++++++++++++++++++ +$base_url = 'https://'.$_SERVER['SERVER_NAME'].'/'; +define('base_url', $base_url); + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++ +// Initialize DomPDF for invoice generation +//+++++++++++++++++++++++++++++++++++++++++++++++++++++ +use Dompdf\Dompdf; +use Dompdf\Options; +$options = new Options(); +$options->set('isRemoteEnabled', true); +$dompdf = new Dompdf($options); + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++ +// Language mapping for invoices +//+++++++++++++++++++++++++++++++++++++++++++++++++++++ +$available_languages = [ + 'NL' => 'NL', + 'BE' => 'NL', + 'US' => 'US', + 'GB' => 'US', + 'DE' => 'DE', + 'FR' => 'FR', + 'ES' => 'ES' +]; + +try { + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + // Get the webhook event data from PayPal + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + $raw_input = file_get_contents('php://input'); + $webhook_event = json_decode($raw_input, true); + + debuglog("PAYPAL WEBHOOK: Decoded event: " . print_r($webhook_event, true)); + + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + // Verify webhook signature (recommended for production) + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + if (!debug && PAYPAL_WEBHOOK_ID) { + $verified = verifyPayPalWebhookSignature($raw_input, $_SERVER); + if (!$verified) { + debuglog("PAYPAL WEBHOOK ERROR: Signature verification failed"); + http_response_code(401); + exit; + } + debuglog("PAYPAL WEBHOOK: Signature verified successfully"); + } + + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + // Extract event type and resource + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + $event_type = $webhook_event['event_type'] ?? ''; + $resource = $webhook_event['resource'] ?? []; + + debuglog("PAYPAL WEBHOOK: Event type: {$event_type}"); + + // Get order ID from custom_id in the payment resource + $orderId = $resource['custom_id'] ?? $resource['purchase_units'][0]['custom_id'] ?? null; + + if (!$orderId) { + debuglog("PAYPAL WEBHOOK ERROR: No custom_id (order_id) in webhook event"); + http_response_code(400); + exit; + } + + debuglog("PAYPAL WEBHOOK: Order ID: {$orderId}"); + + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + // Map PayPal event types to payment status + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + $payment_status = null; + + switch ($event_type) { + case 'PAYMENT.CAPTURE.COMPLETED': + case 'CHECKOUT.ORDER.APPROVED': + $payment_status = 1; // Paid + debuglog("PAYPAL WEBHOOK: Payment completed/approved"); + break; + + case 'PAYMENT.CAPTURE.PENDING': + case 'CHECKOUT.ORDER.PROCESSED': + $payment_status = 101; // Pending + debuglog("PAYPAL WEBHOOK: Payment pending"); + break; + + case 'PAYMENT.CAPTURE.DECLINED': + case 'PAYMENT.CAPTURE.FAILED': + $payment_status = 102; // Failed + debuglog("PAYPAL WEBHOOK: Payment failed/declined"); + break; + + case 'PAYMENT.CAPTURE.REFUNDED': + $payment_status = 104; // Refunded + debuglog("PAYPAL WEBHOOK: Payment refunded"); + break; + + case 'CHECKOUT.ORDER.VOIDED': + $payment_status = 999; // Canceled + debuglog("PAYPAL WEBHOOK: Payment voided/canceled"); + break; + + default: + debuglog("PAYPAL WEBHOOK: Unhandled event type: {$event_type}"); + http_response_code(200); + echo "OK - Event type not handled"; + exit; + } + + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + // Update transaction status directly in database + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + if ($payment_status !== null) { + debuglog("PAYPAL WEBHOOK: Order ID: $orderId, Payment Status: $payment_status"); + + $pdo = dbConnect($dbname); + + // Update transaction status + $sql = 'UPDATE transactions SET payment_status = ? WHERE txn_id = ?'; + $stmt = $pdo->prepare($sql); + $stmt->execute([$payment_status, $orderId]); + + debuglog("PAYPAL WEBHOOK: Transaction status updated in database"); + + // Fetch transaction data for license creation + $sql = 'SELECT * FROM transactions WHERE txn_id = ?'; + $stmt = $pdo->prepare($sql); + $stmt->execute([$orderId]); + $transaction = $stmt->fetch(PDO::FETCH_ASSOC); + + debuglog("PAYPAL WEBHOOK: Transaction data: " . print_r($transaction, true)); + + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + // Only create license and invoice if payment is PAID + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + if ($payment_status == 1 && $transaction !== null && !empty($transaction)) { + debuglog("PAYPAL WEBHOOK: Payment is PAID, processing license..."); + debuglog("PAYPAL WEBHOOK: Transaction ID (auto-increment): " . $transaction['id']); + debuglog("PAYPAL WEBHOOK: Transaction txn_id: " . $transaction['txn_id']); + + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + // 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([$transaction['id']]); + $items = $stmt->fetchAll(PDO::FETCH_ASSOC); + + debuglog("PAYPAL WEBHOOK: Found " . count($items) . " transaction items"); + debuglog("PAYPAL WEBHOOK: Items data: " . print_r($items, true)); + + foreach ($items as $item) { + debuglog("PAYPAL WEBHOOK: Processing item: " . print_r($item, true)); + + if (!empty($item['item_options'])) { + $options = json_decode($item['item_options'], true); + debuglog("PAYPAL WEBHOOK: Item options: " . print_r($options, true)); + + // Check if this is a software upgrade (has serial_number and equipment_id) + if (isset($options['serial_number']) && isset($options['equipment_id'])) { + debuglog("PAYPAL WEBHOOK: This is a software upgrade item"); + + // 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) { + debuglog("PAYPAL WEBHOOK: No existing license, creating new one..."); + + // Generate unique license key + $license_key = generateUniqueLicenseKey(); + + // 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([ + $item['item_id'], // version_id + 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 + $orderId, + '{"salesid":"21-Total Safety Solutions B.V.","soldto":""}', + date('Y-m-d H:i:s'), + 'webhook_paypal' // created by PayPal webhook + ]); + + debuglog("PAYPAL WEBHOOK: License created: $license_key"); + + // 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']]); + + debuglog("PAYPAL WEBHOOK: Equipment updated with license: {$options['equipment_id']}"); + } else { + debuglog("PAYPAL WEBHOOK: License already exists for order: $orderId"); + } + } else { + debuglog("PAYPAL WEBHOOK: Not a software upgrade item (no serial_number/equipment_id)"); + } + } else { + debuglog("PAYPAL WEBHOOK: No item_options found"); + } + } + + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + // Generate INVOICE directly in database + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + + // Check if invoice already exists for this transaction + $sql = 'SELECT id FROM invoice WHERE txn_id = ?'; + $stmt = $pdo->prepare($sql); + $stmt->execute([$transaction['txn_id']]); + $existing_invoice = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$existing_invoice) { + // Create invoice + $sql = 'INSERT INTO invoice (txn_id, payment_status, payment_amount, shipping_amount, discount_amount, tax_amount, created) + VALUES (?, ?, ?, ?, ?, ?, ?)'; + $stmt = $pdo->prepare($sql); + $stmt->execute([ + $transaction['txn_id'], + $transaction['payment_status'], + $transaction['payment_amount'], + $transaction['shipping_amount'] ?? 0.00, + $transaction['discount_amount'] ?? 0.00, + $transaction['tax_amount'] ?? 0.00, + date('Y-m-d H:i:s') + ]); + $invoice_id = $pdo->lastInsertId(); + } else { + $invoice_id = $existing_invoice['id']; + } + + // Fetch full invoice data with customer details for email + $sql = 'SELECT tx.*, txi.item_id, txi.item_price, txi.item_quantity, txi.item_options, + p.productcode, p.productname, inv.id as invoice, inv.created as invoice_created, + i.language as user_language + FROM invoice inv + LEFT JOIN transactions tx ON tx.txn_id = inv.txn_id + LEFT JOIN transactions_items txi ON txi.txn_id = tx.id + LEFT JOIN products p ON p.rowID = txi.item_id + LEFT JOIN identity i ON i.userkey = tx.account_id + WHERE inv.id = ?'; + $stmt = $pdo->prepare($sql); + $stmt->execute([$invoice_id]); + $invoice_data = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if (!empty($invoice_data)) { + // Transform the data (group items like the API does) + $invoice_cust = transformOrderData($invoice_data); + + // 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($message,$pdf,$customer_email,$order_id) = generateSoftwareInvoice($invoice_cust,$orderId,$invoice_language); + + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + //CREATE PDF using DomPDF + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + $dompdf->loadHtml($pdf); + $dompdf->setPaper('A4', 'portrait'); + $dompdf->render(); + $subject = 'Software Upgrade - Invoice: '.$order_id; + $attachment = $dompdf->output(); + + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + //Send email via PHPMailer + //+++++++++++++++++++++++++++++++++++++++++++++++++++++ + $mail_result = send_mail($customer_email, $subject, $message, $attachment, $subject); + + // Send to bookkeeping if configured + if(invoice_bookkeeping){ + debuglog("PAYPAL WEBHOOK: Sending to bookkeeping: " . email_bookkeeping); + send_mail(email_bookkeeping, $subject, $message, $attachment, $subject); + } + } else { + debuglog("PAYPAL WEBHOOK: No invoice data found for invoice_id: $invoice_id"); + } + } + } + + // Return 200 OK to PayPal + http_response_code(200); + echo "OK"; + +} catch (Exception $e) { + debuglog("PAYPAL WEBHOOK ERROR: " . $e->getMessage()); + debuglog("PAYPAL WEBHOOK ERROR TRACE: " . $e->getTraceAsString()); + error_log("PayPal webhook error: " . htmlspecialchars($e->getMessage())); + http_response_code(500); +} + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++ +// Helper function to verify PayPal webhook signature +//+++++++++++++++++++++++++++++++++++++++++++++++++++++ +function verifyPayPalWebhookSignature($raw_body, $headers) { + if (!PAYPAL_WEBHOOK_ID || !PAYPAL_CLIENT_ID || !PAYPAL_CLIENT_SECRET) { + debuglog("PAYPAL WEBHOOK: Signature verification skipped - credentials not configured"); + return true; // Skip verification if not configured + } + + // Get required headers + $transmission_id = $headers['HTTP_PAYPAL_TRANSMISSION_ID'] ?? ''; + $transmission_time = $headers['HTTP_PAYPAL_TRANSMISSION_TIME'] ?? ''; + $cert_url = $headers['HTTP_PAYPAL_CERT_URL'] ?? ''; + $auth_algo = $headers['HTTP_PAYPAL_AUTH_ALGO'] ?? ''; + $transmission_sig = $headers['HTTP_PAYPAL_TRANSMISSION_SIG'] ?? ''; + + debuglog("PAYPAL WEBHOOK: Headers - transmission_id: $transmission_id, cert_url: $cert_url"); + + if (!$transmission_id || !$transmission_sig) { + debuglog("PAYPAL WEBHOOK: Missing signature headers"); + return false; + } + + try { + // Get PayPal access token + $access_token = getPayPalAccessToken(); + + // Prepare verification request + $verify_url = PAYPAL_URL . '/v1/notifications/verify-webhook-signature'; + $verify_data = [ + 'transmission_id' => $transmission_id, + 'transmission_time' => $transmission_time, + 'cert_url' => $cert_url, + 'auth_algo' => $auth_algo, + 'transmission_sig' => $transmission_sig, + 'webhook_id' => PAYPAL_WEBHOOK_ID, + 'webhook_event' => json_decode($raw_body, true) + ]; + + debuglog("PAYPAL WEBHOOK: Verification request - webhook_id: " . PAYPAL_WEBHOOK_ID); + debuglog("PAYPAL WEBHOOK: Verification URL: " . $verify_url); + + $ch = curl_init($verify_url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($verify_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); + + debuglog("PAYPAL WEBHOOK: Verification response (HTTP $http_code): " . $response); + + $result = json_decode($response, true); + $verified = ($http_code == 200 && isset($result['verification_status']) && $result['verification_status'] === 'SUCCESS'); + + debuglog("PAYPAL WEBHOOK: Signature verification result: " . ($verified ? 'SUCCESS' : 'FAILED')); + return $verified; + + } catch (Exception $e) { + debuglog("PAYPAL WEBHOOK: Signature verification error: " . $e->getMessage()); + return false; + } +} + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++ +// 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'] ?? ''; +} + +?>