30 'Apache' => [
'type' =>
'binary'],
31 'Bruno' => [
'type' =>
'tools'],
32 'Composer' => [
'type' =>
'tools'],
33 'Ghostscript' => [
'type' =>
'tools'],
34 'Git' => [
'type' =>
'tools'],
35 'Mailpit' => [
'type' =>
'binary'],
36 'MariaDB' => [
'type' =>
'binary'],
37 'Memcached' => [
'type' =>
'binary'],
38 'MySQL' => [
'type' =>
'binary'],
39 'Ngrok' => [
'type' =>
'tools'],
40 'NodeJS' => [
'type' =>
'binary'],
41 'Perl' => [
'type' =>
'tools'],
42 'PHP' => [
'type' =>
'binary'],
43 'PhpMyAdmin' => [
'type' =>
'application'],
44 'PhpPgAdmin' => [
'type' =>
'application'],
45 'PostgreSQL' => [
'type' =>
'binary'],
46 'PowerShell' => [
'type' =>
'tools'],
47 'Python' => [
'type' =>
'tools'],
48 'Ruby' => [
'type' =>
'tools'],
49 'Xlight' => [
'type' =>
'binary']
72 $this->jsonFilePath =
$bearsamppCore->getResourcesPath() .
'/quickpick-releases.json';
86 if ($isPrerelease && $includePr == 1) {
87 return '<span class="text-danger">' . htmlspecialchars($version) .
' PR</span>';
90 return htmlspecialchars($version);
103 $moduleName = str_replace(
'module-',
'', $moduleName);
107 foreach ($this->modules as $key => $moduleInfo) {
108 if (strtolower($key) === strtolower($moduleName)) {
123 return array_keys( $this->modules );
141 if (!$validation[
'valid']) {
182 $remoteFileCreationTime = strtotime(isset($headers[
'Date']) ? $headers[
'Date'] :
'');
196 if (!file_exists($this->jsonFilePath)) {
201 return filectime($this->jsonFilePath);
213 if ($headers ===
false || !isset($headers[
'Date'])) {
229 Log::debug(
'Headers: ' . print_r($headers,
true));
240 $content = @file_get_contents( $this->jsonFilePath );
241 if ( $content ===
false ) {
242 Log::error(
'Error fetching content from JSON file: ' . $this->jsonFilePath );
244 return [
'error' =>
'Error fetching JSON file'];
247 $data = json_decode( $content,
true );
248 if ( json_last_error() !== JSON_ERROR_NONE ) {
249 Log::error(
'Error decoding JSON content: ' . json_last_error_msg() );
251 return [
'error' =>
'Error decoding JSON content'];
265 Log::debug(
'Fetching JSON file: ' . $this->jsonFilePath );
270 if ( $jsonContent ===
false ) {
272 throw new Exception(
'Failed to fetch JSON content from the URL.' );
276 $result = file_put_contents( $this->jsonFilePath, $jsonContent );
280 throw new Exception(
'Failed to save JSON content to the specified path.' );
284 return [
'success' =>
'JSON content fetched and saved successfully'];
303 foreach ( $jsonData as $entry ) {
304 if ( is_array( $entry ) ) {
305 if ( isset( $entry[
'module'] ) && is_string( $entry[
'module'] ) ) {
306 if ( isset( $entry[
'versions'] ) && is_array( $entry[
'versions'] ) ) {
307 $versions[$entry[
'module']] = array_column( $entry[
'versions'],
null,
'version' );
312 Log::error(
'Invalid entry format in JSON data' );
319 return [
'error' =>
'No versions found'];
343 Log::debug(
'getModuleUrl called for module: ' . $module .
' version: ' . $version );
344 $url = trim( $this->versions[
'module-' . strtolower( $module )][$version][
'url'] );
346 Log::debug(
'Found URL for version: ' . $version .
' URL: ' . $url );
351 Log::error(
'Version not found: ' . $version );
353 return [
'error' =>
'Version not found'];
376 Log::debug(
'checkDownloadId method called.' );
380 Log::error(
'Global configuration is not set.' );
386 Log::debug(
'DownloadId is: ' . $DownloadId );
389 if ( empty( $DownloadId ) ) {
405 Log::error(
'Failed to validate QuickPick license - API server unavailable' );
414 if ( json_last_error() !== JSON_ERROR_NONE ) {
415 Log::error(
'Error decoding JSON response: ' . json_last_error_msg() );
421 if ( isset( $data[
'success'] ) && $data[
'success'] ===
true && isset( $data[
'data'] ) && is_array( $data[
'data'] ) && count( $data[
'data'] ) > 0 ) {
422 Log::debug(
'License key valid: ' . $DownloadId );
427 Log::error(
'Invalid license key: ' . $DownloadId );
451 if ( is_array( $moduleUrl ) && isset( $moduleUrl[
'error'] ) ) {
452 Log::error(
'Module URL not found for module: ' . $module .
' version: ' . $version );
454 return [
'error' =>
'Module URL not found'];
457 if ( empty( $moduleUrl ) ) {
458 Log::error(
'Module URL not found for module: ' . $module .
' version: ' . $version );
460 return [
'error' =>
'Module URL not found'];
472 Log::debug(
'Enhanced mode: ' . ($enhancedMode ?
'enabled' :
'disabled'));
475 if (isset(
$response[
'success']) && $enhancedMode == 1) {
477 Log::debug(
'Enhanced mode enabled - Updating config for module: ' . $module .
' version: ' . $version);
480 if ($configUpdated) {
482 Log::debug(
'Config updated successfully, triggering reload to apply changes...');
485 $obLevel = ob_get_level();
486 while (ob_get_level() > 0) {
490 echo json_encode([
'phase' =>
'updating',
'message' =>
'Updating system configuration...']);
494 for ($i = 0; $i < $obLevel; $i++) {
499 Log::debug(
'Installation complete - user must manually reload from tray menu');
502 Log::error(
'Config update failed for module: ' . $module);
505 }
else if (isset(
$response[
'success']) && $enhancedMode == 0) {
506 Log::debug(
'Enhanced mode disabled - skipping config update');
512 Log::error(
'No internet connection available.' );
514 return [
'error' =>
'No internet connection'];
532 Log::debug(
'Temporary Directory: ' . $tmpDir);
534 $fileName = basename($moduleUrl);
537 $tmpFilePath = $tmpDir .
'/' . $fileName;
540 $moduleName = str_replace(
'module-',
'', $module);
546 foreach ($this->modules as $key => $moduleInfo) {
547 if (strtolower($key) === strtolower($moduleName)) {
554 Log::error(
"Module not found in modules array: $moduleName");
555 return [
'error' =>
'Module configuration not found'];
558 $moduleType = $this->modules[$moduleKey][
'type'];
571 Log::error(
'Failed to retrieve file from URL: ' . $moduleUrl);
572 return [
'error' =>
'Failed to retrieve file from URL'];
576 $fileExtension = pathinfo($tmpFilePath, PATHINFO_EXTENSION);
577 Log::debug(
'File extension: ' . $fileExtension);
579 if ($fileExtension ===
'7z' || $fileExtension ===
'zip') {
581 echo json_encode([
'phase' =>
'extracting']);
582 if (ob_get_length()) {
587 $unzipResult =
$bearsamppCore->unzipFile($tmpFilePath, $destination,
function ($currentPercentage) {
588 echo json_encode([
'progress' =>
"$currentPercentage%"]);
589 if (ob_get_length()) {
595 if ($unzipResult ===
false) {
596 return [
'error' =>
'Failed to unzip file. File: ' . $tmpFilePath .
' could not be unzipped',
'Destination: ' . $destination];
599 Log::error(
'Unsupported file extension: ' . $fileExtension);
600 return [
'error' =>
'Unsupported file extension'];
603 return [
'success' =>
'Module installed successfully'];
621 if ( $moduleType ===
'application' ) {
622 $destination =
$bearsamppRoot->getAppsPath() .
'/' . strtolower( $moduleName ) .
'/';
624 elseif ( $moduleType ===
'binary' ) {
625 $destination =
$bearsamppRoot->getBinPath() .
'/' . strtolower( $moduleName ) .
'/';
627 elseif ( $moduleType ===
'tools' ) {
628 $destination =
$bearsamppRoot->getToolsPath() .
'/' . strtolower( $moduleName ) .
'/';
645 Log::debug(
'Regenerating menu (AJAX-safe mode)...');
648 $oldErrorReporting = error_reporting();
649 error_reporting($oldErrorReporting & ~E_WARNING);
656 error_reporting($oldErrorReporting);
661 }
catch (Exception $e) {
663 error_reporting($oldErrorReporting);
665 Log::warning(
'Error during menu regeneration: ' . $e->getMessage());
685 $moduleName = str_replace(
'module-',
'', $module);
690 foreach ($this->modules as $key => $moduleInfo) {
691 if (strtolower($key) === strtolower($moduleName)) {
698 Log::error(
"Module not found in modules array: $moduleName");
702 $moduleType = $this->modules[$moduleKey][
'type'];
706 $configSection = strtolower($moduleKey);
708 Log::debug(
"Updating config for module: $module (key: $moduleKey, type: $moduleType) to version: $version");
713 $configKey = $configSection .
'Version';
716 Log::info(
"Successfully updated $configSection version to $version in bearsampp.conf");
720 }
catch (Exception $e) {
721 Log::error(
"Failed to update module config: " . $e->getMessage());
737 <div
id=
"configErrorContainer" class=
"text-center mt-3 pe-3">
738 <div
class=
"alert alert-danger d-inline-block" role=
"alert" style=
"max-width: 500px;">
739 <h4
class=
"alert-heading">
740 <i
class=
"fas fa-exclamation-circle"></i> Configuration Error
744 <?php echo htmlspecialchars($errorMessage); ?>
747 <small
class=
"text-muted">
748 Please add the missing parameter to the <code>bearsampp.conf</code> file in the
Bearsampp root directory.
753 return ob_get_clean();
781 <div
class = "enhanced-mode-toggle
">
782 <label class = "form-check-label me-2
" for = "enhancedQuickPickSwitch
">
785 <div class = "form-check form-switch mb-0
">
786 <input class = "form-check-input
" type = "checkbox
" role = "switch
" id = "enhancedQuickPickSwitch
"
787 <?php echo $enhancedMode == 1 ? 'checked' : ''; ?>
788 data-bs-toggle = "tooltip
" data-bs-placement = "bottom
"
789 title = "Toggle between enhanced (auto-config update) and standard
QuickPick mode
">
792 <div id = 'quickPickContainer'>
793 <div class = 'quickpick'>
795 <div class = "custom-select
">
796 <button class = "select-button
" role = "combobox
"
797 aria-label = "select button
"
798 aria-haspopup = "listbox
"
799 aria-expanded = "false
"
800 aria-controls = "select-dropdown
">
801 <span class = "selected-value
">Select a module and version</span>
802 <span class = "arrow
"></span>
804 <ul class = "select-dropdown
" role = "listbox
" id = "select-dropdown
">
807 foreach ( $modules as $module ): ?>
808 <?php if ( is_string( $module ) ): ?>
809 <li role = "option
" class = "moduleheader
">
810 <?php echo htmlspecialchars( $module ); ?>
814 foreach ( $versions['module-' . strtolower( $module )] as $version_array ):
815 // Skip prerelease versions if includePr is not enabled
816 if (isset($version_array['prerelease']) && $version_array['prerelease'] === true && $includePr != 1) {
820 <li role = "option
" class = "moduleoption
"
821 id = "<?php echo htmlspecialchars( $module ); ?>-version-<?php echo htmlspecialchars( $version_array[
'version'] ); ?>-li
">
822 <input type = "radio
"
823 id = "<?php echo htmlspecialchars( $module ); ?>-version-<?php echo htmlspecialchars( $version_array[
'version'] ); ?>
"
824 name = "module
" data-module = "<?php echo htmlspecialchars( $module ); ?>
"
825 data-value = "<?php echo htmlspecialchars( $version_array[
'version'] ); ?>
">
827 for = "<?php echo htmlspecialchars( $module ); ?>-version-<?php echo htmlspecialchars( $version_array[
'version'] ); ?>
"><?php echo $this->formatVersionLabel( $version_array['version'], isset($version_array['prerelease']) && $version_array['prerelease'] === true ); ?></label>
835 <div class = "progress
" id = "progress
" tabindex = "-1
" style = "width:260px;display:none
"
836 aria-labelledby = "progressbar
" aria-hidden = "true">
837 <div class = "progress-bar progress-bar-striped progress-bar-animated
" id = "progress-bar
" role = "progressbar
" aria-valuenow = "0
" aria-valuemin = "0
"
838 aria-valuemax = "100
" data-module = "Module"
839 data-version = "0.0.0
">0%
841 <div id = "download-module
" style = "display: none
">ModuleName</div>
842 <div id = "download-version
" style = "display: none
">Version</div>
846 <div id = "subscribeContainer
" class = "text-center
">
847 <a href = "<?php echo
Util::getWebsiteUrl(
'subscribe' ); ?>
" class = "btn btn-dark d-
inline-flex align-items-center
">
848 <img src = "<?php echo
$imagesPath .
'subscribe.svg'; ?>
" alt = "Subscribe Icon
" class = "me-2
">
849 Subscribe to QuickPick now
856 <div id = "InternetState
" class = "text-center
">
857 <img src = "<?php echo
$imagesPath .
'no-wifi-icon.svg'; ?>
" alt = "No Wifi Icon
" class = "me-2
">
858 <span>No internet present</span>
863 return ob_get_clean();
static checkInternetState()
static info($data, $file=null)
static debug($data, $file=null)
static warning($data, $file=null)
static error($data, $file=null)
isValidHeaderResponse($headers)
fetchAndUnzipModule(string $moduleUrl, string $module)
getModuleUrl(string $module, string $version)
getLocalFileCreationTime()
normalizeModuleName(string $moduleName)
getErrorModal(string $errorMessage)
installModule(string $module, string $version)
updateModuleConfig(string $module, string $version)
getModuleDestinationPath(string $moduleType, string $moduleName)
getQuickpickMenu(array $modules, array $versions, string $imagesPath)
formatVersionLabel($version, $isPrerelease=false)
logHeaders(array $headers)
loadQuickpick(string $imagesPath)
static getWebsiteUrl($path='', $fragment='', $utmSource=true)