addOption(name: 'skipDbBackup', mode: InputOption::VALUE_NONE); } /** * Executes the update process. * * @param InputInterface $input The input interface object. * @param OutputInterface $output The output interface object. * @return int 0 if everything went fine, or an exit code. * * @throws BindingResolutionException|\Throwable */ protected function execute(InputInterface $input, OutputInterface $output): int { $appSettings = app()->make(AppSettings::class); $io = new SymfonyStyle($input, $output); $currentVersion = $appSettings->appVersion; $io->text('Starting the updater'); // Check Versions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + */ $io->section('Version Check'); $io->text('Your current version is: v'.$currentVersion); $url = 'https://github.com/leantime/leantime/releases/latest'; $io->text('Checking latest version on Github...'); // Create stream context to follow redirects $context = stream_context_create( [ 'http' => [ 'method' => 'GET', 'header' => 'Accept: application/json', 'follow_location' => true, ], ], ); // Use file_get_contents() with HTTP context to fetch url $result = file_get_contents($url, false, $context); $jsonResponse = json_decode($result, true); $latestVersion = $jsonResponse['tag_name'] ?? null; $io->text('The latest Leantime version is: '.$latestVersion); // Build download URL if (version_compare($currentVersion, ltrim($latestVersion, 'v'), '>=')) { $io->success('You are on the most up to date version'); return self::SUCCESS; } // Backup DB + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + */ $io->section('Database Backup'); $skipBackup = $input->getOption('skipDbBackup'); if ($skipBackup === false) { $backUp = new ArrayInput([ // The command name is passed as first argument 'command' => 'db:backup', ]); $this->getApplication()->doRun($backUp, $output); } // Download and extract + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + */ $io->section('Download & Extract'); $io->text('Downloading latest version...'); $downloadUrl = 'https://github.com/leantime/leantime/releases/download/'.$latestVersion.'/Leantime-'.$latestVersion.'.zip'; $zipFile = storage_path('/framework/cache/latest.zip'); Http::sink($zipFile)->get($downloadUrl); $io->text('Extracting Archive...'); if (! class_exists(\ZipArchive::class)) { $io->text('ZipArchive not found. Cannot auto-update until the php zip extension is installed. On linux, \'sudo apt install php-zip\''); return self::FAILURE; } $zip = new \ZipArchive; if ($zip->open($zipFile) === true) { $zip->extractTo(storage_path('/framework/cache/leantime')); $zip->close(); $io->success('New update zip file successfully extracted to '.storage_path('/framework/cache/leantime')); } else { $io->text('Error opening downloaded zip file!'); return self::FAILURE; } // Disable Plugins + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + */ // If we got here everything is ready to go and we just need to move the files. // Let's disable plugins $io->section('Disabling Plugins'); /** @var Plugins $plugins */ $plugins = app()->make(Plugins::class); $enabledPlugins = $plugins->getAllPlugins(enabledOnly: true); foreach ($enabledPlugins as $plugin) { if ($plugin->type != 'system' && isset($plugin->id)) { $plugins->disablePlugin($plugin->id); $io->text($plugin->name.': Disabled'); } } $io->success('Plugins disabled successfully'); // Applying Update + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + */ $io->section('Applying Update'); $cp_retval = 0; $cp_output = []; exec('cp -r '.storage_path('/framework/cache/leantime').'/* '.APP_ROOT.'/', $cp_output, $cp_retval); $io->text('Returned with status '.$cp_retval.' and output:'); $io->text($cp_output); if ($cp_retval == 0) { $io->success('Files were updated'); } else { $io->error('Could not apply update. Please check the output above for more information.'); return self::FAILURE; } // Clear Cache + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + */ $io->section('Clearing Cache'); // Deliberately NOT a shell glob: inside the double quotes the shell never expanded // `*.php`, so this delete silently did nothing for years. A stale // bootstrap/cache/packages.php then survives updates and Laravel trusts it as-is — // any provider added to composer.json since (e.g. laravel/mcp) never loads. foreach (glob(APP_ROOT.'/bootstrap/cache/*.php') ?: [] as $cachedFile) { unlink($cachedFile); } exec('rm -rf "'.APP_ROOT.'/storage/framework/composerPaths.php"'); exec('rm -rf "'.APP_ROOT.'/storage/framework/viewPaths.php"'); exec('rm -rf "'.APP_ROOT.'/storage/framework/cache/leantime"'); exec('rm -rf "'.APP_ROOT.'/storage/framework/cache/latest.zip"'); exec('find "'.APP_ROOT.'/storage/framework/cache" -type f ! -name ".gitignore" -delete'); exec('find "'.APP_ROOT.'/storage/framework/sessions" -type f ! -name ".gitignore" -delete'); exec('find "'.APP_ROOT.'/storage/framework/views" -type f ! -name ".gitignore" -delete'); $io->success('Clearing Cache Complete'); // Enable Plugins + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + */ $io->section('Re-enabling Plugins'); foreach ($enabledPlugins as $plugin) { if ($plugin->type != 'system' && isset($plugin->id)) { $plugins->enablePlugin($plugin->id); $io->text($plugin->name.': Enabled'); } } $io->success('Plugins were enabled'); $io->section('Summary'); $io->success('Update applied Successfully'); return Command::SUCCESS; } }