From 4a63d51338da31ead4197ad0a5e383d534aad817 Mon Sep 17 00:00:00 2001 From: Daniel Klabbers Date: Thu, 21 Jan 2016 15:25:05 +0100 Subject: [PATCH] moved code to class, cleaned up, removed requirement of composer.phar in scripts, added ajaxified status updates, redirection to install path and back --- index.php | 99 +----- install/index.php | 13 + install/src/Installer.php | 297 ++++++++++++++++++ .../views/composer-installation.html | 17 +- scripts/composer.phar | Bin 1519627 -> 0 bytes 5 files changed, 329 insertions(+), 97 deletions(-) create mode 100644 install/index.php create mode 100644 install/src/Installer.php rename {storage => install}/views/composer-installation.html (77%) delete mode 100755 scripts/composer.phar diff --git a/index.php b/index.php index fab99db..fa2ac94 100644 --- a/index.php +++ b/index.php @@ -8,98 +8,13 @@ * file that was distributed with this source code. */ -// prevent all those questions concerning ::class and possible composer installation errors (5.3+) -if (PHP_VERSION_ID < 50509) { - throw new Exception('At least PHP 5.5.9 is required to make Flarum work. See the system requirements: http://flarum.org/docs/installation/#system-requirements'); -} elseif (file_exists('vendor/autoload.php')) { +if (file_exists('vendor/autoload.php')) { + require_once 'vendor/autoload.php'; - /** - * Start up the Flarum installation wizard or the Forums. - */ - require 'vendor/autoload.php'; + (new Flarum\Forum\Server(__DIR__))->listen(); - // delete /_install_tmp using composer functionality - if (is_dir('_install_tmp') && file_exists('_install_tmp/composer/vendor/autoload.php')) { - if (array_key_exists('install-done', $_GET)) { - echo 1; - exit; - } - // include the extracted composer libraries - require_once '_install_tmp/composer/vendor/autoload.php'; - - $fs = new Composer\Util\Filesystem(); - $fs->removeDirectory('_install_tmp'); - } - - $server = new Flarum\Forum\Server(__DIR__); - - $server->listen(); - -} elseif ((ini_get('allow_url_fopen') && !ini_get('phar.readonly')) || file_exists('scripts/composer.phar')) { - if (array_key_exists('install-done', $_GET)) { - echo 0; - exit; - } - // prevent time out of page, drawback of running in browser - @set_time_limit(0); - // prevent extracting composer anew if already exists - if (!is_dir('_install_tmp/composer')) { - // attempt to download the latest composer file - if (!file_exists('scripts/composer.phar') && ini_get('allow_url_fopen')) { - file_put_contents('https://getcomposer.org/installer', 'scripts/composer.phar'); - } - // use Phar to extract the composer package - $composer = new Phar('scripts/composer.phar'); - // create a temporary directory for saving the package - mkdir('_install_tmp'); - // extract composer - $composer->extractTo('_install_tmp/composer'); - } - // if extraction succeeded, let's run the update command. - if (is_dir('_install_tmp/composer') && file_exists('_install_tmp/composer/vendor/autoload.php')) { - // force memory to at least 1GB (default for composer) otherwise composer will run out of memory - if (function_exists('ini_set')) { - ini_set('memory_limit', '1G'); - } - - // show an installation html - include "storage/views/composer-installation.html"; - // force the template to the user - ob_flush(); - // sets a home directory for storing information - putenv('COMPOSER_HOME=' . getcwd() . '/_install_tmp/home'); - // prevents any interaction composer might require - putenv('COMPOSER_NO_INTERACTION=true'); - - require_once '_install_tmp/composer/vendor/autoload.php'; - - // run the composer installation command - $application = new Composer\Console\Application(); - - // disable auto exit - $application->setAutoExit(false); - - // first set the github token to prevent installation errors - $input = new \Symfony\Component\Console\Input\ArrayInput([ - 'command' => 'config', - 'github-oauth.github.com' => 'ec785da935d5535e151f7b3386190265f00e8fe2' - ]); - $application->run($input); - - // set the input for the composer install command - $input = new Symfony\Component\Console\Input\ArrayInput([ - 'command' => 'install', - '--no-dev' => true, - '--prefer-dist' => true, - '--optimize-autoloader' => true, - '-q' => true - ]); - $application->run($input); - - // application installation is now done, redirect to Flarum installer - // we cannot send a header or redirect because we're already flushed the buffer - } -} else { - // todo provide proper error message - throw new Exception('This method of installation is currently unsupported.'); +} elseif (file_exists('install/index.php')) { + $url = 'http://' . $_SERVER['HTTP_HOST'] . rtrim(dirname($_SERVER['PHP_SELF']), '/') . '/install/index.php'; + header("Location:{$url}", true, 301); + exit; } \ No newline at end of file diff --git a/install/index.php b/install/index.php new file mode 100644 index 0000000..d123ab3 --- /dev/null +++ b/install/index.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once 'src/Installer.php'; + +(new Flarum\Installer\Installer(__DIR__ . '/../'))->listen(); diff --git a/install/src/Installer.php b/install/src/Installer.php new file mode 100644 index 0000000..62cbc61 --- /dev/null +++ b/install/src/Installer.php @@ -0,0 +1,297 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Installer; + +use Composer\Console\Application; +use Phar; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Filesystem\Filesystem; + +class Installer +{ + + const GITHUB_TOKEN = 'ec785da935d5535e151f7b3386190265f00e8fe2'; + + /** + * Absolute path to Flarum installation. + * + * @var string + */ + protected $basePath; + + /** + * Absolute path to directory for storing temporary installation files. + * + * @var string + */ + protected $tmpPath; + + /** + * The installation path, temporary files needed for running the Installer. + * + * @var string + */ + protected $installPath; + + public function __construct($basePath) + { + $this->setBasePath($basePath); + $this->setTmpPath($this->getBasePath('/storage/tmp')); + $this->setInstallPath(); + + $this->setupEnvironment(); + } + + /** + * Sets up environment specifics + */ + public function setupEnvironment() + { + // prevent time out of page, drawback of running in browser + @set_time_limit(0); + + error_reporting(E_ALL); + + // sets a home directory for storing information + putenv('COMPOSER_HOME=' . $this->getTmpPath('/composer-home')); + // prevents any interaction composer might require + putenv('COMPOSER_NO_INTERACTION=true'); + + // force memory to at least 1GB (default for composer) otherwise composer will run out of memory + if (function_exists('ini_set')) { + ini_set('memory_limit', '1G'); + } + } + + /** + * @param string $basePath + * @return Installer + */ + public function setBasePath($basePath) + { + $this->basePath = realpath($basePath); + + return $this; + } + + /** + * @param string $path + * @return string + */ + public function getBasePath($path = '') + { + return $this->basePath . $path; + } + + /** + * @return Installer + */ + public function setInstallPath() + { + $this->installPath = realpath(__DIR__ . '/../'); + + return $this; + } + + /** + * @param string $path + * @return string + */ + public function getInstallPath($path = '') + { + return $this->installPath . $path; + } + + /** + * @param string $tmpPath + * @return Installer + */ + public function setTmpPath($tmpPath) + { + $this->tmpPath = realpath($tmpPath); + + return $this; + } + + /** + * @param string $path + * @return string + */ + public function getTmpPath($path = '') + { + return $this->tmpPath . $path; + } + + /** + * Reports about specific stages during installation. + * + * @param null $state + * @return bool|string + */ + protected function progressReport($state = null) + { + switch ($state) { + case 'phar-downloaded': + return file_exists($this->getTmpPath('/composer.phar')); + case 'phar-extracted': + return file_exists($this->getTmpPath('/composer-phar-extracted/vendor/autoload.php')); + case 'dependencies-loaded': + return file_exists($this->getBasePath('/vendor/autoload.php')); + case 'cleaned-up': + return $this->progressReport('dependencies-loaded') && !$this->progressReport('phar-extracted'); + } + } + + /** + * Generates a json payload identifying the current state of installing. + * + * @return json + */ + public function getCurrentState() + { + $state = null; + + if ($this->progressReport('cleaned-up')) { + $state = true; + } elseif ($this->progressReport('dependencies-loaded')) { + $state = 'Dependencies downloaded. Cleaning up ..'; + } elseif ($this->progressReport('phar-extracted')) { + $state = 'Composer extracted. Loading dependencies, this will take the longest ..'; + } elseif ($this->progressReport('phar-downloaded')) { + $state = 'Composer downloaded. Extracting ..'; + } + + return json_encode($state); + } + + /** + * Listens for HTTP connections. + */ + public function listen() + { + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + return $this->listenAjax(); + } + // show temporary installation page + ob_end_clean(); + ob_start(); + include $this->getInstallPath('/views/composer-installation.html'); + ob_end_flush(); + + $this->runInstall(); + } + + /** + * Listens for XHR calls. + */ + public function listenAjax() + { + echo $this->getCurrentState(); + } + + /** + * Runs the actual installation steps. + */ + protected function runInstall() + { + if (!$this->progressReport('phar-extracted') && !$this->progressReport('phar-downloaded')) { + $this->downloadComposerPhar(); + } + if (!$this->progressReport('phar-extracted') && $this->progressReport('phar-downloaded')) { + $this->extractComposerPhar(); + } + if ($this->progressReport('phar-extracted')) { + $this->installDependencies(); + } + if ($this->progressReport('dependencies-loaded')) { + $this->cleanUp(); + } + } + + /** + * Downloads the composer.phar. + */ + protected function downloadComposerPhar() + { + $c = curl_init('https://getcomposer.org/composer.phar'); + curl_setopt_array($c, [ + CURLOPT_RETURNTRANSFER => true + ]); + $phar = curl_exec($c); + curl_close($c); + + file_put_contents($this->getTmpPath('/composer.phar'), $phar); + + unset($phar); + } + + /** + * Extracts the composer.phar. + */ + protected function extractComposerPhar() + { + $composer = new Phar($this->getTmpPath('/composer.phar')); + $composer->extractTo($this->getTmpPath('/composer-phar-extracted'), null, true); + + unset($composer); + } + + /** + * Installs dependencies using composer phar packages. + */ + protected function installDependencies() + { + require_once $this->getTmpPath('/composer-phar-extracted/vendor/autoload.php'); + + chdir($this->getBasePath()); + + // run the composer installation command + $application = new Application(); + // disable auto exit + $application->setAutoExit(false); + + // first set the github token to prevent installation errors + $input = new ArrayInput([ + 'command' => 'config', + 'github-oauth.github.com' => self::GITHUB_TOKEN + ]); + $application->run($input); + + // set the input for the composer install command + $input = new ArrayInput([ + 'command' => 'install', + '--no-dev' => true, + '--prefer-dist' => true, + '--optimize-autoloader' => true, + '-q' => true + ]); + $application->run($input); + + unset($input, $application); + + chdir($this->getInstallPath()); + } + + /** + * Cleans up all tmp files. + */ + protected function cleanUp() + { + $fs = new Filesystem(); + + $fs->remove([ + $this->getTmpPath('/composer.phar'), + $this->getTmpPath('/composer-home'), + $this->getTmpPath('/composer-phar-extracted'), + ]); + } +} \ No newline at end of file diff --git a/storage/views/composer-installation.html b/install/views/composer-installation.html similarity index 77% rename from storage/views/composer-installation.html rename to install/views/composer-installation.html index b0e397a..56a1986 100644 --- a/storage/views/composer-installation.html +++ b/install/views/composer-installation.html @@ -42,17 +42,24 @@ You are installing Flarum without composer. We need to download some missing packages. This will take a few minutes to complete.

+

+ +