diff options
Diffstat (limited to 'app/Http/Controllers/AdminUpgradeController.php')
| -rw-r--r-- | app/Http/Controllers/AdminUpgradeController.php | 595 |
1 files changed, 307 insertions, 288 deletions
diff --git a/app/Http/Controllers/AdminUpgradeController.php b/app/Http/Controllers/AdminUpgradeController.php index 814e3f1cc1..63a6c1adc6 100644 --- a/app/Http/Controllers/AdminUpgradeController.php +++ b/app/Http/Controllers/AdminUpgradeController.php @@ -37,348 +37,367 @@ use ZipArchive; /** * Controller for upgrading to a new version of webtrees. */ -class AdminUpgradeController extends AbstractBaseController { - // Icons for success and failure - const SUCCESS = '<i class="fas fa-check" style="color:green"></i> '; - const FAILURE = '<i class="fas fa-times" style="color:red"></i> '; +class AdminUpgradeController extends AbstractBaseController +{ + // Icons for success and failure + const SUCCESS = '<i class="fas fa-check" style="color:green"></i> '; + const FAILURE = '<i class="fas fa-times" style="color:red"></i> '; - // Options for fetching files using GuzzleHTTP - const GUZZLE_OPTIONS = [ - 'connect_timeout' => 25, - 'read_timeout' => 25, - 'timeout' => 55, - ]; + // Options for fetching files using GuzzleHTTP + const GUZZLE_OPTIONS = [ + 'connect_timeout' => 25, + 'read_timeout' => 25, + 'timeout' => 55, + ]; - const LOCK_FILE = 'data/offline.txt'; + const LOCK_FILE = 'data/offline.txt'; - protected $layout = 'layouts/administration'; + protected $layout = 'layouts/administration'; - /** - * @param Request $request - * - * @return Response - */ - public function wizard(Request $request): Response { - $continue = (bool) $request->get('continue'); + /** + * @param Request $request + * + * @return Response + */ + public function wizard(Request $request): Response + { + $continue = (bool)$request->get('continue'); - $title = I18N::translate('Upgrade wizard'); + $title = I18N::translate('Upgrade wizard'); - if ($continue) { - return $this->viewResponse('admin/upgrade/steps', [ - 'steps' => $this->wizardSteps(), - 'title' => $title, - ]); - } else { - return $this->viewResponse('admin/upgrade/wizard', [ - 'current_version' => $this->currentVersion(), - 'latest_version' => $this->latestVersion(), - 'title' => $title, - ]); - } - } + if ($continue) { + return $this->viewResponse('admin/upgrade/steps', [ + 'steps' => $this->wizardSteps(), + 'title' => $title, + ]); + } else { + return $this->viewResponse('admin/upgrade/wizard', [ + 'current_version' => $this->currentVersion(), + 'latest_version' => $this->latestVersion(), + 'title' => $title, + ]); + } + } - /** - * Perform one step of the wizard - * - * @param Request $request - * - * @return Response - */ - public function step(Request $request): Response { - $step = $request->get('step'); + /** + * Perform one step of the wizard + * + * @param Request $request + * + * @return Response + */ + public function step(Request $request): Response + { + $step = $request->get('step'); - switch ($step) { - case 'Check': - return $this->wizardStepCheck(); - case 'Pending': - return $this->wizardStepPending(); - case 'Export': - return $this->wizardStepExport($request); - case 'Download': - return $this->wizardStepDownload(); - case 'Unzip': - return $this->wizardStepUnzip(); - case 'Copy': - return $this->wizardStepCopy(); - case 'Cleanup': - return $this->wizardStepCleanup(); - default: - throw new NotFoundHttpException; - } - } + switch ($step) { + case 'Check': + return $this->wizardStepCheck(); + case 'Pending': + return $this->wizardStepPending(); + case 'Export': + return $this->wizardStepExport($request); + case 'Download': + return $this->wizardStepDownload(); + case 'Unzip': + return $this->wizardStepUnzip(); + case 'Copy': + return $this->wizardStepCopy(); + case 'Cleanup': + return $this->wizardStepCleanup(); + default: + throw new NotFoundHttpException; + } + } - /** - * @return string[] - */ - private function wizardSteps(): array { - $download_url = $this->downloadUrl(); + /** + * @return string[] + */ + private function wizardSteps(): array + { + $download_url = $this->downloadUrl(); - $export_steps = []; + $export_steps = []; - foreach (Tree::getAll() as $tree) { - $route = route('upgrade', ['step' => 'Export', 'ged' => $tree->getName()]); - $message = I18N::translate('Export all the family trees to GEDCOM files…') . ' ' . e($tree->getTitle()); - $export_steps[$route] = $message; - } + foreach (Tree::getAll() as $tree) { + $route = route('upgrade', [ + 'step' => 'Export', + 'ged' => $tree->getName(), + ]); + $message = I18N::translate('Export all the family trees to GEDCOM files…') . ' ' . e($tree->getTitle()); + $export_steps[$route] = $message; + } - return [ - route('upgrade', ['step' => 'Check']) => 'config.php', - route('upgrade', ['step' => 'Pending']) => I18N::translate('Check for pending changes…'), - ] + $export_steps + [ - route('upgrade', ['step' => 'Download']) => I18N::translate('Download %s…', e($download_url)), - route('upgrade', ['step' => 'Unzip']) => I18N::translate('Unzip %s to a temporary folder…', e(basename($download_url))), - route('upgrade', ['step' => 'Copy']) => I18N::translate('Copy files…'), - route('upgrade', ['step' => 'Cleanup']) => I18N::translate('Delete temporary files…'), - ]; - } + return [ + route('upgrade', ['step' => 'Check']) => 'config.php', + route('upgrade', ['step' => 'Pending']) => I18N::translate('Check for pending changes…'), + ] + $export_steps + [ + route('upgrade', ['step' => 'Download']) => I18N::translate('Download %s…', e($download_url)), + route('upgrade', ['step' => 'Unzip']) => I18N::translate('Unzip %s to a temporary folder…', e(basename($download_url))), + route('upgrade', ['step' => 'Copy']) => I18N::translate('Copy files…'), + route('upgrade', ['step' => 'Cleanup']) => I18N::translate('Delete temporary files…'), + ]; + } - /** - * @return Response - */ - private function wizardStepCheck(): Response { - $latest_version = $this->latestVersion(); + /** + * @return Response + */ + private function wizardStepCheck(): Response + { + $latest_version = $this->latestVersion(); - if ($latest_version === '') { - return $this->failure(I18N::translate('No upgrade information is available.')); - } + if ($latest_version === '') { + return $this->failure(I18N::translate('No upgrade information is available.')); + } - if (version_compare($this->currentVersion(), $latest_version) >= 0) { - return $this->failure(I18N::translate('This is the latest version of webtrees. No upgrade is available.')); - } + if (version_compare($this->currentVersion(), $latest_version) >= 0) { + return $this->failure(I18N::translate('This is the latest version of webtrees. No upgrade is available.')); + } - return $this->success(/* I18N: %s is a version number, such as 1.2.3 */ - I18N::translate('Upgrade to webtrees %s.', e($latest_version))); - } + return $this->success(/* I18N: %s is a version number, such as 1.2.3 */ + I18N::translate('Upgrade to webtrees %s.', e($latest_version))); + } - /** - * @return Response - */ - private function wizardStepPending(): Response { - $changes = Database::prepare("SELECT 1 FROM `##change` WHERE status='pending' LIMIT 1")->fetchOne(); + /** + * @return Response + */ + private function wizardStepPending(): Response + { + $changes = Database::prepare("SELECT 1 FROM `##change` WHERE status='pending' LIMIT 1")->fetchOne(); - if (empty($changes)) { - return $this->success(I18N::translate('There are no pending changes.')); - } else { - $route = route('show-pending'); - $message = I18N::translate('You should accept or reject all pending changes before upgrading.'); - $message .= ' <a href="' . e($route) . '">' . I18N::translate('Pending changes') . '</a>'; + if (empty($changes)) { + return $this->success(I18N::translate('There are no pending changes.')); + } else { + $route = route('show-pending'); + $message = I18N::translate('You should accept or reject all pending changes before upgrading.'); + $message .= ' <a href="' . e($route) . '">' . I18N::translate('Pending changes') . '</a>'; - return $this->failure($message); - } - } + return $this->failure($message); + } + } - /** - * @param Request $request - * - * @return Response - */ - private function wizardStepExport(Request $request): Response { - $tree = $request->attributes->get('tree'); + /** + * @param Request $request + * + * @return Response + */ + private function wizardStepExport(Request $request): Response + { + $tree = $request->attributes->get('tree'); - $filename = WT_DATA_DIR . $tree->getName() . date('-Y-m-d') . '.ged'; + $filename = WT_DATA_DIR . $tree->getName() . date('-Y-m-d') . '.ged'; - try { - $stream = fopen($filename, 'w'); - $tree->exportGedcom($stream); - fclose($stream); + try { + $stream = fopen($filename, 'w'); + $tree->exportGedcom($stream); + fclose($stream); - return $this->success(I18N::translate('The family tree has been exported to %s.', e($filename))); - } catch (Throwable $ex) { - DebugBar::addThrowable($ex); + return $this->success(I18N::translate('The family tree has been exported to %s.', e($filename))); + } catch (Throwable $ex) { + DebugBar::addThrowable($ex); - return $this->failure(I18N::translate('The file %s could not be created.', e($filename))); - } - } + return $this->failure(I18N::translate('The file %s could not be created.', e($filename))); + } + } - /** - * @return Response - */ - private function wizardStepDownload(): Response { - $download_url = $this->downloadUrl(); - $zip_file = WT_DATA_DIR . basename($download_url); - $zip_stream = fopen($zip_file, 'w'); - $start_time = microtime(true); - $client = new Client(); + /** + * @return Response + */ + private function wizardStepDownload(): Response + { + $download_url = $this->downloadUrl(); + $zip_file = WT_DATA_DIR . basename($download_url); + $zip_stream = fopen($zip_file, 'w'); + $start_time = microtime(true); + $client = new Client(); - try { - $response = $client->get($download_url, self::GUZZLE_OPTIONS); - $stream = $response->getBody(); + try { + $response = $client->get($download_url, self::GUZZLE_OPTIONS); + $stream = $response->getBody(); - while (!$stream->eof()) { - fwrite($zip_stream, $stream->read(65536)); - } + while (!$stream->eof()) { + fwrite($zip_stream, $stream->read(65536)); + } - $stream->close(); - fclose($zip_stream); - $zip_size = filesize($zip_file); - $end_time = microtime(true); + $stream->close(); + fclose($zip_stream); + $zip_size = filesize($zip_file); + $end_time = microtime(true); - if ($zip_size > 0) { - $kb = I18N::number($zip_size / 1024); - $seconds = I18N::number($end_time - $start_time, 2); + if ($zip_size > 0) { + $kb = I18N::number($zip_size / 1024); + $seconds = I18N::number($end_time - $start_time, 2); - return $this->success(/* I18N: %1$s is a number of KB, %2$s is a (fractional) number of seconds */ - I18N::translate('%1$s KB were downloaded in %2$s seconds.', $kb, $seconds)); - } elseif (preg_match('/^https:/', $download_url) && !in_array('ssl', stream_get_transports())) { - // Guess why we might have failed... - return $this->failure(I18N::translate('This server does not support secure downloads using HTTPS.')); - } else { - return $this->failure(''); - } - } catch (Exception $ex) { - return $this->failure($ex->getMessage()); - } - } + return $this->success(/* I18N: %1$s is a number of KB, %2$s is a (fractional) number of seconds */ + I18N::translate('%1$s KB were downloaded in %2$s seconds.', $kb, $seconds)); + } elseif (preg_match('/^https:/', $download_url) && !in_array('ssl', stream_get_transports())) { + // Guess why we might have failed... + return $this->failure(I18N::translate('This server does not support secure downloads using HTTPS.')); + } else { + return $this->failure(''); + } + } catch (Exception $ex) { + return $this->failure($ex->getMessage()); + } + } - /** - * @return Response - */ - private function wizardStepUnzip(): Response { - $download_url = $this->downloadUrl(); - $zip_file = WT_DATA_DIR . basename($download_url); - $tmp_folder = WT_DATA_DIR . basename($download_url, '.zip'); - $src_filesystem = new Filesystem(new ZipArchiveAdapter($zip_file, null, 'webtrees')); - $dst_filesystem = new Filesystem(new Local($tmp_folder)); - $paths = $src_filesystem->listContents('', true); - $paths = array_filter($paths, function (array $file) { - return $file['type'] === 'file'; - }); + /** + * @return Response + */ + private function wizardStepUnzip(): Response + { + $download_url = $this->downloadUrl(); + $zip_file = WT_DATA_DIR . basename($download_url); + $tmp_folder = WT_DATA_DIR . basename($download_url, '.zip'); + $src_filesystem = new Filesystem(new ZipArchiveAdapter($zip_file, null, 'webtrees')); + $dst_filesystem = new Filesystem(new Local($tmp_folder)); + $paths = $src_filesystem->listContents('', true); + $paths = array_filter($paths, function (array $file) { + return $file['type'] === 'file'; + }); - $start_time = microtime(true); + $start_time = microtime(true); - // The Flysystem/ZipArchiveAdapter is very slow, taking over a second per file. - // So we do this step using the native PHP library. + // The Flysystem/ZipArchiveAdapter is very slow, taking over a second per file. + // So we do this step using the native PHP library. - $zip = new ZipArchive; - if ($zip->open($zip_file)) { - $zip->extractTo($tmp_folder); - $zip->close(); - echo 'ok'; - } else { - echo 'failed'; - } + $zip = new ZipArchive; + if ($zip->open($zip_file)) { + $zip->extractTo($tmp_folder); + $zip->close(); + echo 'ok'; + } else { + echo 'failed'; + } - $seconds = I18N::number(microtime(true) - $start_time, 2); - $count = count($paths); + $seconds = I18N::number(microtime(true) - $start_time, 2); + $count = count($paths); - return $this->success(/* I18N: …from the .ZIP file, %2$s is a (fractional) number of seconds */ - I18N::plural('%1$s file was extracted in %2$s seconds.', '%1$s files were extracted in %2$s seconds.', $count, $count, $seconds)); - } + return $this->success(/* I18N: …from the .ZIP file, %2$s is a (fractional) number of seconds */ + I18N::plural('%1$s file was extracted in %2$s seconds.', '%1$s files were extracted in %2$s seconds.', $count, $count, $seconds)); + } - /** - * @return Response - */ - private function wizardStepCopy(): Response { - $download_url = $this->downloadUrl(); - $src_filesystem = new Filesystem(new Local(WT_DATA_DIR . basename($download_url, '.zip') . '/webtrees')); - $dst_filesystem = new Filesystem(new Local(WT_ROOT)); - $paths = $src_filesystem->listContents('', true); - $paths = array_filter($paths, function (array $file) { - return $file['type'] === 'file'; - }); + /** + * @return Response + */ + private function wizardStepCopy(): Response + { + $download_url = $this->downloadUrl(); + $src_filesystem = new Filesystem(new Local(WT_DATA_DIR . basename($download_url, '.zip') . '/webtrees')); + $dst_filesystem = new Filesystem(new Local(WT_ROOT)); + $paths = $src_filesystem->listContents('', true); + $paths = array_filter($paths, function (array $file) { + return $file['type'] === 'file'; + }); - $lock_file_text = I18N::translate('This website is being upgraded. Try again in a few minutes.') . PHP_EOL . FunctionsDate::formatTimestamp(WT_TIMESTAMP) . /* I18N: Timezone - http://en.wikipedia.org/wiki/UTC */ - I18N::translate('UTC'); - $dst_filesystem->put(self::LOCK_FILE, $lock_file_text); + $lock_file_text = I18N::translate('This website is being upgraded. Try again in a few minutes.') . PHP_EOL . FunctionsDate::formatTimestamp(WT_TIMESTAMP) . /* I18N: Timezone - http://en.wikipedia.org/wiki/UTC */ + I18N::translate('UTC'); + $dst_filesystem->put(self::LOCK_FILE, $lock_file_text); - $start_time = microtime(true); + $start_time = microtime(true); - foreach ($paths as $path) { - $dst_filesystem->put($path['path'], $src_filesystem->read($path['path'])); - // Delete files as we go, in case disk space is limited. - $src_filesystem->delete($path['path']); + foreach ($paths as $path) { + $dst_filesystem->put($path['path'], $src_filesystem->read($path['path'])); + // Delete files as we go, in case disk space is limited. + $src_filesystem->delete($path['path']); - if (microtime(true) - WT_START_TIME > ini_get('max_execution_time') - 5) { - return $this->failure(I18N::translate('The server’s time limit has been reached.')); - } - } + if (microtime(true) - WT_START_TIME > ini_get('max_execution_time') - 5) { + return $this->failure(I18N::translate('The server’s time limit has been reached.')); + } + } - $dst_filesystem->delete(self::LOCK_FILE); + $dst_filesystem->delete(self::LOCK_FILE); - $seconds = I18N::number(microtime(true) - $start_time, 2); - $count = count($paths); + $seconds = I18N::number(microtime(true) - $start_time, 2); + $count = count($paths); - return $this->success(/* I18N: …from the .ZIP file, %2$s is a (fractional) number of seconds */ - I18N::plural('%1$s file was extracted in %2$s seconds.', '%1$s files were extracted in %2$s seconds.', $count, $count, $seconds)); - } + return $this->success(/* I18N: …from the .ZIP file, %2$s is a (fractional) number of seconds */ + I18N::plural('%1$s file was extracted in %2$s seconds.', '%1$s files were extracted in %2$s seconds.', $count, $count, $seconds)); + } - /** - * @return Response - */ - private function wizardStepCleanup(): Response { - $download_url = $this->downloadUrl(); + /** + * @return Response + */ + private function wizardStepCleanup(): Response + { + $download_url = $this->downloadUrl(); - $filesystem = new Filesystem(new Local(WT_DATA_DIR)); + $filesystem = new Filesystem(new Local(WT_DATA_DIR)); - try { - $files = $filesystem->listContents(basename($download_url, '.zip'), true); + try { + $files = $filesystem->listContents(basename($download_url, '.zip'), true); - foreach ($files as $file) { - if ($file['type'] === 'file') { - $filesystem->delete($file['path']); - } else { - $filesystem->deleteDir($file['path']); - } - } + foreach ($files as $file) { + if ($file['type'] === 'file') { + $filesystem->delete($file['path']); + } else { + $filesystem->deleteDir($file['path']); + } + } - $filesystem->delete(basename($download_url)); - $filesystem->deleteDir(basename(basename($download_url, '.zip'))); - } catch (Exception $ex) { - // We don't really care if we cannot delete these temporary files. - // But show an error message, anyway. + $filesystem->delete(basename($download_url)); + $filesystem->deleteDir(basename(basename($download_url, '.zip'))); + } catch (Exception $ex) { + // We don't really care if we cannot delete these temporary files. + // But show an error message, anyway. - return $this->failure($ex->getMessage()); - } + return $this->failure($ex->getMessage()); + } - return $this->success(I18N::translate('The upgrade is complete.')); - } + return $this->success(I18N::translate('The upgrade is complete.')); + } - /** - * @param string $message - * - * @return Response - */ - private function success(string $message): Response { - return new Response(self::SUCCESS . $message); - } + /** + * @param string $message + * + * @return Response + */ + private function success(string $message): Response + { + return new Response(self::SUCCESS . $message); + } - /** - * @param string $message - * - * @return Response - */ - private function failure(string $message): Response { - return new Response(self::FAILURE . $message, Response::HTTP_INTERNAL_SERVER_ERROR); - } + /** + * @param string $message + * + * @return Response + */ + private function failure(string $message): Response + { + return new Response(self::FAILURE . $message, Response::HTTP_INTERNAL_SERVER_ERROR); + } - /** - * @return string - */ - private function currentVersion(): string { - return WT_VERSION; - } + /** + * @return string + */ + private function currentVersion(): string + { + return WT_VERSION; + } - /** - * @return string - */ - private function latestVersion(): string { - $latest_version_txt = Functions::fetchLatestVersion(); - if (preg_match('/^[0-9.]+\|[0-9.]+\|/', $latest_version_txt)) { - return explode('|', $latest_version_txt)[0]; - } else { - return ''; - } - } + /** + * @return string + */ + private function latestVersion(): string + { + $latest_version_txt = Functions::fetchLatestVersion(); + if (preg_match('/^[0-9.]+\|[0-9.]+\|/', $latest_version_txt)) { + return explode('|', $latest_version_txt)[0]; + } else { + return ''; + } + } - /** - * @return string - */ - private function downloadUrl(): string { - $latest_version_txt = Functions::fetchLatestVersion(); - if (preg_match('/^[0-9.]+\|[0-9.]+\|/', $latest_version_txt)) { - return explode('|', $latest_version_txt)[2]; - } else { - return ''; - } - } + /** + * @return string + */ + private function downloadUrl(): string + { + $latest_version_txt = Functions::fetchLatestVersion(); + if (preg_match('/^[0-9.]+\|[0-9.]+\|/', $latest_version_txt)) { + return explode('|', $latest_version_txt)[2]; + } else { + return ''; + } + } } |
