installerDir = __DIR__; $this->rootDir = dirname($this->installerDir); $this->configDir = sprintf('%s/config', $this->rootDir); $this->tempDir = sprintf('%s/temp', $this->installerDir); $this->vendorDir = sprintf('%s/vendor', $this->rootDir); $this->viewsDir = sprintf('%s/views', $this->installerDir); $appConfig = require_once sprintf('%s/app.php', $this->configDir); $this->versionNumber = $appConfig['version']; } public function handleRequest() { if (!isset($_GET['act'])) { $this->checkInstallation(); } else { switch (trim($_GET['act'])) { case 'download': $this->download(); return; case 'extract': $this->extract(); return; case 'finalise': $this->finalise(); return; default: throw new \Exception(sprintf('Action \'%s\' was not recognised.', $_GET['act'])); } } } /** * @param bool $isUpgrade */ public function setIsUpgrade(bool $isUpgrade): void { $this->isUpgrade = $isUpgrade; } protected function checkInstallation() { $requirements = [ 'core:php72OrLater', 'php:hasCurlLibrary', 'php:hasMySqlClientLibrary', 'php:hasGdLibrary', 'config:maxUploadSize', 'config:maxPostRequestSize' ]; $requirementsGrouped = []; $canInstall = true; foreach ($requirements as $requirement) { $requirementSplit = explode(':', $requirement); $groupName = $requirementSplit[0]; $functionName = $requirementSplit[1]; $status = null; $result = call_user_func_array([AppRequirements::class, $functionName], [&$status]); if ($result == AppRequirements::STATUS_NOT_MET) { $canInstall = false; } if (!array_key_exists($groupName, $requirementsGrouped)) { $requirementsGrouped[$groupName] = []; } if (!array_key_exists($functionName, $requirementsGrouped[$groupName])) { $requirementsGrouped[$groupName][$functionName] = [ 'result' => $result, 'status' => $status ]; } } $requirementGroupNames = [ 'config' => 'PHP configuration', 'core' => 'Core requirements', 'php' => 'Required PHP modules' ]; $requirementNames = [ 'hasCurlLibrary' => 'cURL Web Requests Library', 'hasGdLibrary' => 'GD Graphics Procesisng Library', 'hasMySqlClientLibrary' => 'MySQL PDO Data Access Library', 'maxPostRequestSize' => 'Maximum POST request size (recommended: 4 MB)', 'maxUploadSize' => 'Maximum file size allowed to upload (recommended: 4 MB)', 'php72OrLater' => 'Requires PHP 7.2.0 minimum', ]; $this->view('index', [ 'appName' => 'Blue Twilight Installer', 'canInstall' => $canInstall, 'isUpgrade' => $this->isUpgrade, 'requirementGroupNames' => $requirementGroupNames, 'requirementNames' => $requirementNames, 'statusNotMet' => AppRequirements::STATUS_NOT_MET, 'statusOK' => AppRequirements::STATUS_OK, 'statusWarning' => AppRequirements::STATUS_WARNING, 'systemRequirements' => $requirementsGrouped ]); } protected function download() { $servicesConfig = require_once sprintf('%s/services.php', $this->configDir); $versionNumber = sprintf('v%s', $this->versionNumber); $gitea = new GiteaService($servicesConfig['gitea'], $versionNumber); $releaseInfo = $gitea->getSpecificRelease($versionNumber); if (is_null($releaseInfo)) { throw new \Exception(sprintf('No release info found in Gitea for Blue Twilight version \'%s\'', $versionNumber)); } else if (!isset($releaseInfo->assets)) { throw new \Exception(sprintf('No assets found in Gitea for Blue Twilight version \'%s\'', $versionNumber)); } $vendorsPrefix = 'vendors'; $vendorsSuffix = '.tar.gz'; $selectedAsset = null; foreach ($releaseInfo->assets as $asset) { /* Ignore anything that is not "vendors.tar.gz" were the is also optional - e.g. vendors_2.1.2.tar.gz vendors.tar.gz but NOT 2.1.2_vendors.zip */ if (!starts_with($asset->name, $vendorsPrefix) || !ends_with($asset->name, $vendorsSuffix)) { continue; } $selectedAsset = $asset; break; } if (is_null($selectedAsset)) { throw new \Exception(sprintf('No vendors.tar.gz found in Gitea for Blue Twilight version \'%s\'', $versionNumber)); } $targetFileName = $this->getVendorsTempFileName(); $this->downloadFile($selectedAsset->browser_download_url, $targetFileName); $this->json([ 'result' => true, 'fileName' => $targetFileName ]); } protected function extract() { $targetFileName = $this->getVendorsTempFileName(); if (!file_exists($targetFileName) || !is_readable($targetFileName)) { throw new \Exception(sprintf('The file \'%s\' does not exist or is not readable', $targetFileName)); } $phar = new \PharData($targetFileName); $phar->extractTo($this->rootDir, null, true); // We should always have a vendor/autoload.php $vendorsTestFile = $this->getVendorsAutoloadFileName(); if (file_exists($vendorsTestFile)) { $this->writeVersionFile(); $this->json([ 'result' => true, 'testFile' => $vendorsTestFile ]); } else { throw new \Exception('The extraction failed'); } } protected function finalise() { $result = [ 'cacheFilesRemoved' => 0, 'envFileCreated' => false ]; $result['cacheFilesRemoved'] = $this->clearCacheIfExists(); $result['envFileCreated'] = !$this->isUpgrade && $this->createEnvFileIfNotExist(); require sprintf('%s/bootstrap/autoload.php', $this->rootDir); $app = require_once sprintf('%s/bootstrap/app.php', $this->rootDir); $kernel = $app->make(\Illuminate\Contracts\Console\Kernel::class); $kernel->bootstrap(); if ($result['envFileCreated']) { Artisan::call('key:generate'); } $kernel->terminate(null, null); $this->json($result); } private function downloadFile($sourceURL, $targetFilename) { $urlHandle = @fopen($sourceURL, 'r'); $tempFilename = @fopen($targetFilename, 'w'); if ($urlHandle === false) { throw new \Exception(sprintf('Failed downloading the file from %s', $sourceURL)); } else if ($tempFilename === false) { throw new \Exception(sprintf('Failed opening the file \'%s\' for writing', $targetFilename)); } while (!feof($urlHandle)) { $buffer = fread($urlHandle, 8192); fwrite($tempFilename, $buffer); } @fclose($urlHandle); @fclose($tempFilename); } private function clearCacheIfExists() { $filesDeleted = 0; $cacheDir = sprintf('%s/bootstrap/cache', $this->rootDir); $di = new \DirectoryIterator($cacheDir); foreach ($di as $fileInfo) { if (@unlink($fileInfo->getRealPath())) { $filesDeleted++; } } return $filesDeleted; } private function createEnvFileIfNotExist() { $envFile = $this->getEnvFileName(); if (!file_exists($envFile)) { copy($this->getEnvExampleFileName(), $envFile); return true; } return false; } private function getEnvExampleFileName() { return sprintf('%s/.env.example', $this->rootDir); } private function getEnvFileName() { return sprintf('%s/.env', $this->rootDir); } private function getVendorsAutoloadFileName() { return sprintf('%s/autoload.php', $this->vendorDir); } private function getVendorsTempFileName() { return sprintf('%s/vendors.tar.gz', $this->tempDir); } private function getVendorsVersionFileName() { return sprintf('%s/version.txt', $this->vendorDir); } private function json($data) { echo json_encode($data); } private function view($name, array $viewData = []) { $viewFile = sprintf('%s/%s.php', $this->viewsDir, $name); if (!file_exists($viewFile) || !is_readable($viewFile)) { throw new \Exception(sprintf('ERROR: View file \'%s\' does not exist.', $viewFile)); } // Provide keys as variables - e.g. $viewData['something'] becomes accessible via $something extract($viewData); require_once $viewFile; } private function writeVersionFile() { file_put_contents($this->getVendorsVersionFileName(), $this->versionNumber . PHP_EOL); } }