diff --git a/api/v2/get/software_available.php b/api/v2/get/software_available.php new file mode 100644 index 0000000..118a914 --- /dev/null +++ b/api/v2/get/software_available.php @@ -0,0 +1,196 @@ +prepare($sql); + $stmt->execute([$criterias['version'],$username,$criterias['sn']]); + } + + //check if current hw_version is send and update the equipment record + if(isset($criterias['hw_version']) && $criterias['hw_version'] !=''){ + $sql = 'UPDATE equipment SET hw_version = ?, updatedby = ? WHERE serialnumber = ? '; + $stmt = $pdo->prepare($sql); + $stmt->execute([$criterias['hw_version'],$username,$criterias['sn']]); + } + + //GET EQUIPMENT AND PRODUCT DATA BASED ON SERIAL NUMBER + $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 + FROM equipment e + JOIN products p ON e.productrowid = p.rowID + WHERE e.serialnumber = ?'; + $stmt = $pdo->prepare($sql); + $stmt->execute([$criterias['sn']]); + $equipment_data = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$equipment_data) { + $messages = ["error" => "No equipment found for serialnumber"]; + } else { + $product_rowid = $equipment_data['product_rowid']; + $productcode = $equipment_data['productcode']; + $current_sw_version = $equipment_data['current_sw_version']; + $hw_version = $equipment_data['hw_version']; + $sw_version_license = $equipment_data['sw_version_license']; + $equipment_rowid = $equipment_data['equipment_rowid']; + + //GET ALL DATA: active assignments, version details, and upgrade paths + //Filter on active status, hw_version compatibility, and exclude current version + $sql = 'SELECT + psv.rowID as version_id, + psv.version, + psv.name, + psv.description, + psv.mandatory, + psv.latest, + psv.hw_version, + psv.file_path, + pup.price, + pup.currency, + pup.from_version_id, + from_ver.version as from_version + FROM products_software_assignment psa + JOIN products_software_versions psv ON psa.software_version_id = psv.rowID + LEFT JOIN products_software_upgrade_paths pup ON pup.to_version_id = psv.rowID AND pup.is_active = 1 + LEFT JOIN products_software_versions from_ver ON pup.from_version_id = from_ver.rowID + WHERE psa.product_id = ? + AND psa.status = 1 + AND (psv.hw_version = ? OR psv.hw_version IS NULL OR psv.hw_version = "") + AND psv.version != ?'; + + $stmt = $pdo->prepare($sql); + $stmt->execute([$product_rowid, $hw_version, $current_sw_version ?? '']); + $versions = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if (empty($versions)) { + // No versions available + $software_available = "no"; + } else { + $has_priced_options = false; + $has_latest_version_different = false; + + foreach ($versions as $version) { + //Check if this version should be shown (same logic as software_update) + $show_version = false; + if (!$current_sw_version || $current_sw_version == '') { + //No current version - show all + $show_version = true; + } elseif ($version['from_version'] == $current_sw_version) { + //Upgrade path exists from current version + $show_version = true; + } else { + //Check if any upgrade paths exist for this version + $sql = 'SELECT COUNT(*) as path_count + FROM products_software_upgrade_paths + WHERE to_version_id = ? AND is_active = 1'; + $stmt = $pdo->prepare($sql); + $stmt->execute([$version['version_id']]); + $path_check = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($path_check['path_count'] == 0) { + //No paths exist at all - show as free upgrade + $show_version = true; + } + } + + if ($show_version) { + //Check if there's a valid license for this upgrade + $final_price = $version['price'] ?? '0.00'; + + if ($final_price > 0 && $sw_version_license) { + //Check if the license is valid + $sql = 'SELECT status, start_at, expires_at + FROM products_software_licenses + WHERE license_key = ? AND equipment_id = ?'; + $stmt = $pdo->prepare($sql); + $stmt->execute([$sw_version_license, $equipment_rowid]); + $license = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($license && $license['status'] == 1) { + $now = date('Y-m-d H:i:s'); + $start_at = $license['start_at']; + $expires_at = $license['expires_at']; + + //Check if license is within valid date range + if ((!$start_at || $start_at <= $now) && (!$expires_at || $expires_at >= $now)) { + $final_price = '0.00'; + } + } + } + + // Check for priced options + if ($final_price > 0) { + $has_priced_options = true; + } + + // Check if there's a "latest" flagged version that's different from current + if ($version['latest'] == 1 && $version['version'] != $current_sw_version) { + $has_latest_version_different = true; + } + } + } + + // 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) { + $software_available = "yes"; + } elseif ($has_latest_version_different) { + $software_available = "yes"; + } else { + $software_available = "no"; + } + } + + $messages = ["software_available" => $software_available]; + } +} else { + $messages = ["error" => "No serialnumber found"]; +} + +//Encrypt results +$messages = json_encode($messages, JSON_UNESCAPED_UNICODE); + +//Send results +echo $messages; + +?> \ No newline at end of file diff --git a/assets/functions.php b/assets/functions.php index 32c5d57..90964dd 100644 --- a/assets/functions.php +++ b/assets/functions.php @@ -1490,6 +1490,7 @@ function getProfile($profile, $permission){ 'com_log' => 'U', 'software_update' => 'R', 'software_download' => 'R', + 'software_available' => 'R' ]; // Group permissions: [granting_page => [collection => allowed_actions_string]] diff --git a/logfile.php b/logfile.php index 5d11d38..39786a1 100644 --- a/logfile.php +++ b/logfile.php @@ -7,12 +7,14 @@ if (isAllowed('logfile',$_SESSION['profile'],$_SESSION['permission'],'R') === 0) exit; } +// Define logs directory path +$logs_dir = __DIR__ . '/log/'; + //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // POST HANDLER - Delete all logs //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $delete_message = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_all_logs'])) { - $logs_dir = __DIR__ . '/log/'; $deleted_count = 0; if (is_dir($logs_dir)) { @@ -37,6 +39,28 @@ if (isset($_GET['deleted'])) { $delete_message = "Successfully deleted " . intval($_GET['deleted']) . " log file(s)."; } +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// DOWNLOAD HANDLER +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +if (isset($_GET['download']) && $_GET['download']) { + $filename = $_GET['download']; + $filepath = $logs_dir . $filename; + + // Security check: ensure file exists and is within logs directory + if (file_exists($filepath) && strpos(realpath($filepath), realpath($logs_dir)) === 0) { + // Set headers for file download + header('Content-Type: application/octet-stream'); + header('Content-Disposition: attachment; filename="' . $filename . '"'); + header('Content-Length: ' . filesize($filepath)); + header('Cache-Control: must-revalidate'); + header('Pragma: public'); + + // Output file content + readfile($filepath); + exit; + } +} + //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // GET HANDLER //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -45,7 +69,6 @@ if (isset($_GET['deleted'])) { $selected_log = $_GET['log'] ?? ''; // Scan logs directory for all log files -$logs_dir = __DIR__ . '/log/'; $log_files = []; if (is_dir($logs_dir)) { @@ -113,6 +136,9 @@ if (file_exists($filelocation_webserver)){ + @@ -165,6 +191,20 @@ function loadLogFile(filename) { } } +// Download current log file +function downloadLogFile() { + const selector = document.getElementById('log-file-selector'); + const selectedFile = selector.value; + + if (selectedFile) { + const url = new URL(window.location.href); + url.searchParams.set('download', selectedFile); + window.location.href = url.toString(); + } else { + alert('No log file selected to download.'); + } +} + // Refresh log functionality function refreshLog() { const button = event.target.closest('button'); diff --git a/products_software_versions.php b/products_software_versions.php index 012e28c..2cd45e0 100644 --- a/products_software_versions.php +++ b/products_software_versions.php @@ -113,6 +113,9 @@ $view .= ' +
+ +
@@ -121,16 +124,6 @@ $view .= '
- - - - - '; @@ -146,7 +139,6 @@ $view .= ' Mandatory Latest Status - Actions @@ -163,14 +155,13 @@ $view .= ' foreach ($responses as $response){ $view .= ' - + '.$response->name.' '.$response->version.' '.$response->hw_version.' '.($response->mandatory ? 'Yes' : 'No').' '.($response->latest ? 'Yes' : 'No').' '.($response->status ? 'Active' : 'Inactive').' - View '; } diff --git a/profiles.php b/profiles.php index e2ddf85..335f575 100644 --- a/profiles.php +++ b/profiles.php @@ -7,6 +7,9 @@ defined(page_security_key) or exit; $domain = getDomainName($_SERVER['SERVER_NAME']); $file = ((file_exists(dirname(__FILE__).'/custom/'.$domain.'/settings/settingsprofiles.php')) ? dirname(__FILE__).'/custom/'.$domain.'/settings/settingsprofiles.php' : dirname(__FILE__).'/settings/settingsprofiles.php'); +// Include settings views globally +include dirname(__FILE__).'/settings/settingsviews.php'; + $page = 'profiles'; //Check if allowed if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){ @@ -20,6 +23,71 @@ $contents = file_get_contents($file); //empty view $view = ''; +// Function to scan for new views and update settingsviews.php +function scan_and_update_views() { + global $all_views; + $new_views = []; + + // Scan root PHP files + $root_files = glob(dirname(__FILE__) . '/*.php'); + foreach ($root_files as $file) { + $filename = basename($file, '.php'); + if ($filename !== 'index' && $filename !== 'login' && $filename !== 'logout') { + $new_views[] = $filename; + } + } + + // Scan API v2 get folder + $get_files = glob(dirname(__FILE__) . '/api/v2/get/*.php'); + foreach ($get_files as $file) { + $filename = basename($file, '.php'); + $new_views[] = $filename; + } + + // Scan API v2 post folder + $post_files = glob(dirname(__FILE__) . '/api/v2/post/*.php'); + foreach ($post_files as $file) { + $filename = basename($file, '.php'); + $new_views[] = $filename; + } + + // Remove duplicates and filter out existing views + $new_views = array_unique($new_views); + $views_to_add = array_diff($new_views, $all_views); + + if (!empty($views_to_add)) { + // Update the all_views array + $all_views = array_merge($all_views, $views_to_add); + sort($all_views); + + // Update settingsviews.php file + $views_file = dirname(__FILE__) . '/settings/settingsviews.php'; + $new_content = ""; + + file_put_contents($views_file, $new_content); + + return count($views_to_add); + } + + return 0; +} + +// Handle view scanning +if (isset($_POST['scan_views'])) { + $added_count = scan_and_update_views(); + header('Location: index.php?page=profiles&views_added=' . $added_count); + exit; +} + // Format key function function format_key($key) { $key = str_replace( @@ -31,57 +99,138 @@ function format_key($key) { } // Format HTML output function function format_var_html($key, $value) { - - include dirname(__FILE__).'/settings/settingsviews.php'; + global $all_views; + + // Ensure all_views is loaded + if (!isset($all_views) || !is_array($all_views)) { + include dirname(__FILE__).'/settings/settingsviews.php'; + } $html = ''; $value = htmlspecialchars(trim($value, '\''), ENT_QUOTES); $profile_contents = explode(',',$value); - - foreach ($all_views as $view){ - $html .= '
'; - if (in_array($view, $profile_contents)){ - $html .= ' '.$view; - } else { - $html .= ' '.$view; - } - $html .= '
'; - } - return $html; + + // Add profile header with bulk select options + $checked_count = count(array_intersect($profile_contents, $all_views)); + $total_count = count($all_views); + + $html .= '
'; + $html .= '

Profile: ' . ucwords(str_replace('_', ' ', $key)) . ' (' . $checked_count . '/' . $total_count . ' selected)

'; + $html .= '
'; + $html .= ' '; + $html .= ''; + $html .= '
'; + + // Create a container for the checkboxes with data attributes for filtering + $html .= '
'; + + // Group views by category for better organization + $grouped_views = []; + foreach ($all_views as $view) { + $category = 'Other'; + if (strpos($view, 'equipment') === 0) $category = 'Equipment'; + elseif (strpos($view, 'product') === 0) $category = 'Products'; + elseif (strpos($view, 'account') === 0) $category = 'Accounts'; + elseif (strpos($view, 'user') === 0) $category = 'Users'; + elseif (strpos($view, 'report') === 0) $category = 'Reports'; + elseif (strpos($view, 'contract') === 0) $category = 'Contracts'; + elseif (strpos($view, 'dealer') === 0) $category = 'Dealers'; + elseif (strpos($view, 'rma') === 0) $category = 'RMA'; + elseif (in_array($view, ['dashboard', 'profile', 'admin', 'settings', 'config'])) $category = 'Core'; + + $grouped_views[$category][] = $view; + } + + // Output grouped checkboxes + foreach ($grouped_views as $category => $views) { + $html .= '
'; + $html .= '
' . $category . '
'; + + foreach ($views as $view) { + $checked = in_array($view, $profile_contents) ? 'checked' : ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + } + + $html .= '
'; + } + + $html .= '
'; + + return $html; } -// Format tabs -function format_tabs($contents) { +// Format tabs and content together (interleaved for collapsible functionality) +function format_tabs_and_content($contents) { + global $all_views; + + // Ensure all_views is loaded + if (!isset($all_views) || !is_array($all_views)) { + include dirname(__FILE__).'/settings/settingsviews.php'; + } + $rows = explode("\n", $contents); - $tab = '
'; - $tab .= 'General'; - for ($i = 0; $i < count($rows); $i++) { - preg_match('/\/\*(.*?)\*\//', $rows[$i], $match); - if ($match) { - $tab .= '' . $match[1] . ''; + $output = ''; + $profile_count = 0; + + // Count profiles for performance warning + foreach ($rows as $row) { + if (preg_match('/\/\*(.*?)\*\//', $row)) { + $profile_count++; } } - $tab .= '
'; - return $tab; -} -// Format form -function format_form($contents) { - $rows = explode("\n", $contents); - $form = '
Each tab represents a profile. Each element in a profile represents a view and or API access.'; + + // Add performance notice for large numbers of profiles + if ($profile_count > 10) { + $output .= '
'; + $output .= ''; + $output .= '

Managing ' . $profile_count . ' profiles.

'; + $output .= ''; + $output .= '
'; + } + + // Start with General tab and its content + $output .= '
General
'; + $output .= ''; + $current_profile = strtolower(str_replace(' ', '_', $tab_match[1])); + $output .= '
' . $tab_match[1] . '
'; + $output .= '
'; + + return $output; } if (isset($_POST['submit']) && !empty($_POST)) { //remove submit from POST @@ -115,6 +264,16 @@ if (isset($_GET['success_msg'])) { } } +// Handle views added message +if (isset($_GET['views_added'])) { + $added_count = (int)$_GET['views_added']; + if ($added_count > 0) { + $success_msg = $added_count . ' new views added!'; + } else { + $success_msg = 'No new views found. All views are up to date.'; + } +} + template_header('Profiles', 'profiles'); $view .= ' @@ -123,28 +282,77 @@ $view .= '

Profiles

-
-'; + '; if (isset($success_msg)){ - $view .= '
+ $view .= '

'.$success_msg.'

- +
'; } -$view .= format_tabs($contents); -$view .= '
-
- '; -$view .= format_form($contents); +$view .= format_tabs_and_content($contents); +$view .= ''; +// Add basic JavaScript functionality $view .= ' -
-
- -'; + +'; //Output echo $view; diff --git a/style/admin.css b/style/admin.css index bebe95e..7db8594 100644 --- a/style/admin.css +++ b/style/admin.css @@ -522,7 +522,6 @@ main .content-block { box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.1), 0px 1px 2px 0px rgba(0, 0, 0, 0.06); overflow: hidden; border-radius: 4px; - border: 1px solid #e2e8f0; } main .content-block:has(.sortable) { @@ -556,7 +555,7 @@ main .content-block .block-header i { main .content-block-wrapper { display: flex; width: 100%; - padding-top: 25px; + padding-top: 5px; } main .content-block-wrapper .content-block { @@ -624,7 +623,6 @@ main .tabs ~ .content-block { main .tab-content { display: none; - border: 1px solid #ddd; border-top: none; overflow: hidden; transition: max-height 0.3s ease;