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.
+
+
+