diff --git a/framework/core/migrations/2018_01_18_135100_change_posts_add_foreign_keys.php b/framework/core/migrations/2018_01_18_135100_change_posts_add_foreign_keys.php
index 00ef23223..ffdaad0df 100644
--- a/framework/core/migrations/2018_01_18_135100_change_posts_add_foreign_keys.php
+++ b/framework/core/migrations/2018_01_18_135100_change_posts_add_foreign_keys.php
@@ -41,7 +41,6 @@ return [
'down' => function (Builder $schema) {
$schema->table('posts', function (Blueprint $table) {
$table->dropForeign(['user_id']);
- $table->dropForeign(['discussion_id']);
$table->dropForeign(['edited_user_id']);
$table->dropForeign(['hidden_user_id']);
});
diff --git a/framework/core/migrations/2018_09_22_004100_change_registration_tokens_columns.php b/framework/core/migrations/2018_09_22_004100_change_registration_tokens_columns.php
index 526899c84..6fc797fea 100644
--- a/framework/core/migrations/2018_09_22_004100_change_registration_tokens_columns.php
+++ b/framework/core/migrations/2018_09_22_004100_change_registration_tokens_columns.php
@@ -24,7 +24,7 @@ return [
},
'down' => function (Builder $schema) {
- $schema->table('auth_tokens', function (Blueprint $table) {
+ $schema->table('registration_tokens', function (Blueprint $table) {
$table->dropColumn('provider', 'identifier', 'user_attributes');
$table->string('payload', 150)->change();
diff --git a/framework/core/src/Database/Migrator.php b/framework/core/src/Database/Migrator.php
index 980abe2f6..ac61af7d7 100644
--- a/framework/core/src/Database/Migrator.php
+++ b/framework/core/src/Database/Migrator.php
@@ -146,7 +146,9 @@ class Migrator
*/
public function reset($path, Extension $extension = null)
{
- $migrations = array_reverse($this->repository->getRan($extension->getId()));
+ $migrations = array_reverse($this->repository->getRan(
+ $extension ? $extension->getId() : null
+ ));
$count = count($migrations);
diff --git a/framework/core/src/Extension/Extension.php b/framework/core/src/Extension/Extension.php
index a85792961..a657c4e95 100644
--- a/framework/core/src/Extension/Extension.php
+++ b/framework/core/src/Extension/Extension.php
@@ -11,12 +11,18 @@
namespace Flarum\Extension;
+use Flarum\Database\Migrator;
use Flarum\Extend\Compat;
use Flarum\Extend\LifecycleInterface;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
+use League\Flysystem\Adapter\Local;
+use League\Flysystem\Filesystem;
+use League\Flysystem\FilesystemInterface;
+use League\Flysystem\MountManager;
+use League\Flysystem\Plugin\ListFiles;
/**
* @property string $name
@@ -307,6 +313,25 @@ class Extension implements Arrayable
return realpath($this->path.'/assets/') !== false;
}
+ public function copyAssetsTo(FilesystemInterface $target)
+ {
+ if (! $this->hasAssets()) {
+ return;
+ }
+
+ $mount = new MountManager([
+ 'source' => $source = new Filesystem(new Local($this->getPath().'/assets')),
+ 'target' => $target,
+ ]);
+
+ $source->addPlugin(new ListFiles);
+ $assetFiles = $source->listFiles('/', true);
+
+ foreach ($assetFiles as $file) {
+ $mount->copy("source://$file[path]", "target://extensions/$this->id/$file[path]");
+ }
+ }
+
/**
* Tests whether the extension has migrations.
*
@@ -317,6 +342,19 @@ class Extension implements Arrayable
return realpath($this->path.'/migrations/') !== false;
}
+ public function migrate(Migrator $migrator, $direction = 'up')
+ {
+ if (! $this->hasMigrations()) {
+ return;
+ }
+
+ if ($direction == 'up') {
+ return $migrator->run($this->getPath().'/migrations', $this);
+ } else {
+ return $migrator->reset($this->getPath().'/migrations', $this);
+ }
+ }
+
/**
* Generates an array result for the object.
*
diff --git a/framework/core/src/Extension/ExtensionManager.php b/framework/core/src/Extension/ExtensionManager.php
index 45decbe94..221418367 100644
--- a/framework/core/src/Extension/ExtensionManager.php
+++ b/framework/core/src/Extension/ExtensionManager.php
@@ -222,26 +222,16 @@ class ExtensionManager
* Runs the database migrations for the extension.
*
* @param Extension $extension
- * @param bool|true $up
+ * @param string $direction
* @return void
*/
- public function migrate(Extension $extension, $up = true)
+ public function migrate(Extension $extension, $direction = 'up')
{
- if (! $extension->hasMigrations()) {
- return;
- }
-
- $migrationDir = $extension->getPath().'/migrations';
-
$this->app->bind('Illuminate\Database\Schema\Builder', function ($container) {
return $container->make('Illuminate\Database\ConnectionInterface')->getSchemaBuilder();
});
- if ($up) {
- $this->migrator->run($migrationDir, $extension);
- } else {
- $this->migrator->reset($migrationDir, $extension);
- }
+ $extension->migrate($this->migrator, $direction);
}
/**
@@ -252,7 +242,7 @@ class ExtensionManager
*/
public function migrateDown(Extension $extension)
{
- return $this->migrate($extension, false);
+ return $this->migrate($extension, 'down');
}
/**
diff --git a/framework/core/src/Install/AdminUser.php b/framework/core/src/Install/AdminUser.php
new file mode 100644
index 000000000..943191516
--- /dev/null
+++ b/framework/core/src/Install/AdminUser.php
@@ -0,0 +1,58 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Install;
+
+use Carbon\Carbon;
+use Illuminate\Hashing\BcryptHasher;
+
+class AdminUser
+{
+ private $username;
+ private $password;
+ private $email;
+
+ public function __construct($username, $password, $email)
+ {
+ $this->username = $username;
+ $this->password = $password;
+ $this->email = $email;
+
+ $this->validate();
+ }
+
+ public function getUsername()
+ {
+ return $this->username;
+ }
+
+ public function getAttributes(): array
+ {
+ return [
+ 'username' => $this->username,
+ 'email' => $this->email,
+ 'password' => (new BcryptHasher)->make($this->password),
+ 'joined_at' => Carbon::now(),
+ 'is_email_confirmed' => 1,
+ ];
+ }
+
+ private function validate()
+ {
+ if (! filter_var($this->email, FILTER_VALIDATE_EMAIL)) {
+ throw new ValidationFailed('You must enter a valid email.');
+ }
+
+ if (! $this->username || preg_match('/[^a-z0-9_-]/i', $this->username)) {
+ throw new ValidationFailed('Username can only contain letters, numbers, underscores, and dashes.');
+ }
+ }
+}
diff --git a/framework/core/src/Install/Console/DataProviderInterface.php b/framework/core/src/Install/Console/DataProviderInterface.php
index 9976598e2..94816bd7b 100644
--- a/framework/core/src/Install/Console/DataProviderInterface.php
+++ b/framework/core/src/Install/Console/DataProviderInterface.php
@@ -11,15 +11,9 @@
namespace Flarum\Install\Console;
+use Flarum\Install\Installation;
+
interface DataProviderInterface
{
- public function getDatabaseConfiguration();
-
- public function getBaseUrl();
-
- public function getAdminUser();
-
- public function getSettings();
-
- public function isDebugMode(): bool;
+ public function configure(Installation $installation): Installation;
}
diff --git a/framework/core/src/Install/Console/DefaultsDataProvider.php b/framework/core/src/Install/Console/DefaultsDataProvider.php
deleted file mode 100644
index 70f300071..000000000
--- a/framework/core/src/Install/Console/DefaultsDataProvider.php
+++ /dev/null
@@ -1,111 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Flarum\Install\Console;
-
-class DefaultsDataProvider implements DataProviderInterface
-{
- protected $databaseConfiguration = [
- 'driver' => 'mysql',
- 'host' => 'localhost',
- 'database' => 'flarum',
- 'username' => 'root',
- 'password' => '',
- 'prefix' => '',
- 'port' => '3306',
- ];
-
- protected $debug = false;
-
- protected $baseUrl = 'http://flarum.local';
-
- protected $adminUser = [
- 'username' => 'admin',
- 'password' => 'password',
- 'password_confirmation' => 'password',
- 'email' => 'admin@example.com',
- ];
-
- protected $settings = [
- 'allow_post_editing' => 'reply',
- 'allow_renaming' => '10',
- 'allow_sign_up' => '1',
- 'custom_less' => '',
- 'default_locale' => 'en',
- 'default_route' => '/all',
- 'extensions_enabled' => '[]',
- 'forum_title' => 'Development Forum',
- 'forum_description' => '',
- 'mail_driver' => 'mail',
- 'mail_from' => 'noreply@flarum.dev',
- 'theme_colored_header' => '0',
- 'theme_dark_mode' => '0',
- 'theme_primary_color' => '#4D698E',
- 'theme_secondary_color' => '#4D698E',
- 'welcome_message' => 'This is beta software and you should not use it in production.',
- 'welcome_title' => 'Welcome to Development Forum',
- ];
-
- public function getDatabaseConfiguration()
- {
- return $this->databaseConfiguration;
- }
-
- public function setDatabaseConfiguration(array $databaseConfiguration)
- {
- $this->databaseConfiguration = $databaseConfiguration;
- }
-
- public function getBaseUrl()
- {
- return $this->baseUrl;
- }
-
- public function setBaseUrl($baseUrl)
- {
- $this->baseUrl = $baseUrl;
- }
-
- public function getAdminUser()
- {
- return $this->adminUser;
- }
-
- public function setAdminUser(array $adminUser)
- {
- $this->adminUser = $adminUser;
- }
-
- public function getSettings()
- {
- return $this->settings;
- }
-
- public function setSettings(array $settings)
- {
- $this->settings = $settings;
- }
-
- public function setSetting($key, $value)
- {
- $this->settings[$key] = $value;
- }
-
- public function isDebugMode(): bool
- {
- return $this->debug;
- }
-
- public function setDebugMode(bool $debug = true)
- {
- $this->debug = $debug;
- }
-}
diff --git a/framework/core/src/Install/Console/FileDataProvider.php b/framework/core/src/Install/Console/FileDataProvider.php
index 0eb8886f0..07626bfa4 100644
--- a/framework/core/src/Install/Console/FileDataProvider.php
+++ b/framework/core/src/Install/Console/FileDataProvider.php
@@ -12,12 +12,14 @@
namespace Flarum\Install\Console;
use Exception;
+use Flarum\Install\AdminUser;
+use Flarum\Install\DatabaseConfig;
+use Flarum\Install\Installation;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Yaml\Yaml;
class FileDataProvider implements DataProviderInterface
{
- protected $default;
protected $debug = false;
protected $baseUrl = null;
protected $databaseConfiguration = [];
@@ -26,9 +28,6 @@ class FileDataProvider implements DataProviderInterface
public function __construct(InputInterface $input)
{
- // Get default configuration
- $this->default = new DefaultsDataProvider();
-
// Get configuration file path
$configurationFile = $input->getOption('file');
@@ -55,28 +54,35 @@ class FileDataProvider implements DataProviderInterface
}
}
- public function getDatabaseConfiguration()
+ public function configure(Installation $installation): Installation
{
- return $this->databaseConfiguration + $this->default->getDatabaseConfiguration();
+ return $installation
+ ->debugMode($this->debug)
+ ->baseUrl($this->baseUrl ?? 'http://flarum.local')
+ ->databaseConfig($this->getDatabaseConfiguration())
+ ->adminUser($this->getAdminUser())
+ ->settings($this->settings);
}
- public function getBaseUrl()
+ private function getDatabaseConfiguration(): DatabaseConfig
{
- return (! is_null($this->baseUrl)) ? $this->baseUrl : $this->default->getBaseUrl();
+ return new DatabaseConfig(
+ $this->databaseConfiguration['driver'] ?? 'mysql',
+ $this->databaseConfiguration['host'] ?? 'localhost',
+ $this->databaseConfiguration['port'] ?? 3306,
+ $this->databaseConfiguration['database'] ?? 'flarum',
+ $this->databaseConfiguration['username'] ?? 'root',
+ $this->databaseConfiguration['password'] ?? '',
+ $this->databaseConfiguration['prefix'] ?? ''
+ );
}
- public function getAdminUser()
+ private function getAdminUser(): AdminUser
{
- return $this->adminUser + $this->default->getAdminUser();
- }
-
- public function getSettings()
- {
- return $this->settings + $this->default->getSettings();
- }
-
- public function isDebugMode(): bool
- {
- return $this->debug;
+ return new AdminUser(
+ $this->adminUser['username'] ?? 'admin',
+ $this->adminUser['password'] ?? 'password',
+ $this->adminUser['email'] ?? 'admin@example.com'
+ );
}
}
diff --git a/framework/core/src/Install/Console/InstallCommand.php b/framework/core/src/Install/Console/InstallCommand.php
index 37cd34655..6356cf620 100644
--- a/framework/core/src/Install/Console/InstallCommand.php
+++ b/framework/core/src/Install/Console/InstallCommand.php
@@ -11,65 +11,32 @@
namespace Flarum\Install\Console;
-use Carbon\Carbon;
-use Exception;
use Flarum\Console\AbstractCommand;
-use Flarum\Database\DatabaseMigrationRepository;
-use Flarum\Database\Migrator;
-use Flarum\Extension\ExtensionManager;
-use Flarum\Foundation\Application as FlarumApplication;
-use Flarum\Foundation\Site;
-use Flarum\Group\Group;
-use Flarum\Install\Prerequisite\PrerequisiteInterface;
-use Flarum\Settings\DatabaseSettingsRepository;
-use Illuminate\Contracts\Events\Dispatcher;
-use Illuminate\Contracts\Foundation\Application;
-use Illuminate\Contracts\Translation\Translator;
-use Illuminate\Database\ConnectionInterface;
-use Illuminate\Database\Connectors\ConnectionFactory;
-use Illuminate\Filesystem\Filesystem;
-use Illuminate\Hashing\BcryptHasher;
-use Illuminate\Validation\Factory;
-use PDO;
+use Flarum\Install\Installation;
+use Flarum\Install\Pipeline;
+use Flarum\Install\Step;
use Symfony\Component\Console\Input\InputOption;
class InstallCommand extends AbstractCommand
{
+ /**
+ * @var Installation
+ */
+ protected $installation;
+
/**
* @var DataProviderInterface
*/
protected $dataSource;
/**
- * @var Application
+ * @param Installation $installation
*/
- protected $application;
-
- /**
- * @var Filesystem
- */
- protected $filesystem;
-
- /**
- * @var ConnectionInterface
- */
- protected $db;
-
- /**
- * @var Migrator
- */
- protected $migrator;
-
- /**
- * @param Application $application
- * @param Filesystem $filesystem
- */
- public function __construct(Application $application, Filesystem $filesystem)
+ public function __construct(Installation $installation)
{
- $this->application = $application;
+ $this->installation = $installation;
parent::__construct();
- $this->filesystem = $filesystem;
}
protected function configure()
@@ -77,12 +44,6 @@ class InstallCommand extends AbstractCommand
$this
->setName('install')
->setDescription("Run Flarum's installation migration and seeds")
- ->addOption(
- 'defaults',
- 'd',
- InputOption::VALUE_NONE,
- 'Create default settings and user'
- )
->addOption(
'file',
'f',
@@ -104,274 +65,64 @@ class InstallCommand extends AbstractCommand
{
$this->init();
- $prerequisites = $this->getPrerequisites();
- $prerequisites->check();
- $errors = $prerequisites->getErrors();
+ $problems = $this->installation->prerequisites()->problems();
- if (empty($errors)) {
+ if ($problems->isEmpty()) {
$this->info('Installing Flarum...');
$this->install();
$this->info('DONE.');
} else {
- $this->output->writeln(
- 'Please fix the following errors before we can continue with the installation.'
- );
- $this->showErrors($errors);
+ $this->showProblems($problems);
}
}
protected function init()
{
- if ($this->dataSource === null) {
- if ($this->input->getOption('defaults')) {
- $this->dataSource = new DefaultsDataProvider();
- } elseif ($this->input->getOption('file')) {
- $this->dataSource = new FileDataProvider($this->input);
- } else {
- $this->dataSource = new UserDataProvider($this->input, $this->output, $this->getHelperSet()->get('question'));
- }
+ if ($this->input->getOption('file')) {
+ $this->dataSource = new FileDataProvider($this->input);
+ } else {
+ $this->dataSource = new UserDataProvider($this->input, $this->output, $this->getHelperSet()->get('question'));
}
}
- public function setDataSource(DataProviderInterface $dataSource)
- {
- $this->dataSource = $dataSource;
- }
-
protected function install()
{
- try {
- $this->dbConfig = $this->dataSource->getDatabaseConfiguration();
+ $pipeline = $this->dataSource->configure(
+ $this->installation->configPath($this->input->getOption('config'))
+ )->build();
- $validation = $this->getValidator()->make(
- $this->dbConfig,
- [
- 'driver' => 'required|in:mysql',
- 'host' => 'required',
- 'database' => 'required|string',
- 'username' => 'required|string',
- 'prefix' => 'nullable|alpha_dash|max:10',
- 'port' => 'nullable|integer|min:1|max:65535',
- ]
- );
-
- if ($validation->fails()) {
- throw new Exception(implode("\n", call_user_func_array('array_merge', $validation->getMessageBag()->toArray())));
- }
-
- $this->baseUrl = $this->dataSource->getBaseUrl();
- $this->settings = $this->dataSource->getSettings();
- $this->adminUser = $admin = $this->dataSource->getAdminUser();
-
- if (strlen($admin['password']) < 8) {
- throw new Exception('Password must be at least 8 characters.');
- }
-
- if ($admin['password'] !== $admin['password_confirmation']) {
- throw new Exception('The password did not match its confirmation.');
- }
-
- if (! filter_var($admin['email'], FILTER_VALIDATE_EMAIL)) {
- throw new Exception('You must enter a valid email.');
- }
-
- if (! $admin['username'] || preg_match('/[^a-z0-9_-]/i', $admin['username'])) {
- throw new Exception('Username can only contain letters, numbers, underscores, and dashes.');
- }
-
- $this->storeConfiguration($this->dataSource->isDebugMode());
-
- $this->runMigrations();
-
- $this->writeSettings();
-
- $this->createAdminUser();
-
- $this->publishAssets();
-
- // Now that the installation of core is complete, boot up a new
- // application instance before enabling extensions so that all of
- // the application services are available.
- Site::fromPaths([
- 'base' => $this->application->basePath(),
- 'public' => $this->application->publicPath(),
- 'storage' => $this->application->storagePath(),
- ])->bootApp();
-
- $this->application = FlarumApplication::getInstance();
-
- $this->enableBundledExtensions();
- } catch (Exception $e) {
- @unlink($this->getConfigFile());
-
- throw $e;
- }
+ $this->runPipeline($pipeline);
}
- protected function storeConfiguration(bool $debugMode)
+ private function runPipeline(Pipeline $pipeline)
{
- $dbConfig = $this->dbConfig;
-
- $config = [
- 'debug' => $debugMode,
- 'database' => $laravelDbConfig = [
- 'driver' => $dbConfig['driver'],
- 'host' => $dbConfig['host'],
- 'database' => $dbConfig['database'],
- 'username' => $dbConfig['username'],
- 'password' => $dbConfig['password'],
- 'charset' => 'utf8mb4',
- 'collation' => 'utf8mb4_unicode_ci',
- 'prefix' => $dbConfig['prefix'],
- 'port' => $dbConfig['port'],
- 'strict' => false
- ],
- 'url' => $this->baseUrl,
- 'paths' => [
- 'api' => 'api',
- 'admin' => 'admin',
- ],
- ];
-
- $this->info('Testing config');
-
- $factory = new ConnectionFactory($this->application);
-
- $laravelDbConfig['engine'] = 'InnoDB';
-
- $this->db = $factory->make($laravelDbConfig);
- $version = $this->db->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION);
-
- if (version_compare($version, '5.5.0', '<')) {
- throw new Exception('MySQL version too low. You need at least MySQL 5.5.');
- }
-
- $repository = new DatabaseMigrationRepository(
- $this->db, 'migrations'
- );
- $files = $this->application->make('files');
-
- $this->migrator = new Migrator($repository, $this->db, $files);
-
- $this->info('Writing config');
-
- file_put_contents(
- $this->getConfigFile(),
- 'on('start', function (Step $step) {
+ $this->output->write($step->getMessage().'...');
+ })->on('end', function () {
+ $this->output->write("done\n");
+ })->on('fail', function () {
+ $this->output->write("failed\n");
+ $this->output->writeln('Rolling back...');
+ })->on('rollback', function (Step $step) {
+ $this->output->writeln($step->getMessage().' (rollback)');
+ })
+ ->run();
}
- protected function runMigrations()
+ protected function showProblems($problems)
{
- $this->migrator->setOutput($this->output);
- $this->migrator->getRepository()->createRepository();
- $this->migrator->run(__DIR__.'/../../../migrations');
- }
-
- protected function writeSettings()
- {
- $settings = new DatabaseSettingsRepository($this->db);
-
- $this->info('Writing default settings');
-
- $settings->set('version', $this->application->version());
-
- foreach ($this->settings as $k => $v) {
- $settings->set($k, $v);
- }
- }
-
- protected function createAdminUser()
- {
- $admin = $this->adminUser;
-
- if ($admin['password'] !== $admin['password_confirmation']) {
- throw new Exception('The password did not match its confirmation.');
- }
-
- $this->info('Creating admin user '.$admin['username']);
-
- $uid = $this->db->table('users')->insertGetId([
- 'username' => $admin['username'],
- 'email' => $admin['email'],
- 'password' => (new BcryptHasher)->make($admin['password']),
- 'joined_at' => Carbon::now(),
- 'is_email_confirmed' => 1,
- ]);
-
- $this->db->table('group_user')->insert([
- 'user_id' => $uid,
- 'group_id' => Group::ADMINISTRATOR_ID,
- ]);
- }
-
- protected function enableBundledExtensions()
- {
- $extensions = new ExtensionManager(
- new DatabaseSettingsRepository($this->db),
- $this->application,
- $this->migrator,
- $this->application->make(Dispatcher::class),
- $this->application->make('files')
+ $this->output->writeln(
+ 'Please fix the following problems before we can continue with the installation.'
);
- $disabled = [
- 'flarum-akismet',
- 'flarum-auth-facebook',
- 'flarum-auth-github',
- 'flarum-auth-twitter',
- 'flarum-pusher',
- ];
+ foreach ($problems as $problem) {
+ $this->info($problem['message']);
- foreach ($extensions->getExtensions() as $name => $extension) {
- if (in_array($name, $disabled)) {
- continue;
- }
-
- $this->info('Enabling extension: '.$name);
-
- $extensions->enable($name);
- }
- }
-
- protected function publishAssets()
- {
- $this->filesystem->copyDirectory(
- $this->application->basePath().'/vendor/components/font-awesome/webfonts',
- $this->application->publicPath().'/assets/fonts'
- );
- }
-
- protected function getConfigFile()
- {
- return $this->input->getOption('config') ?: base_path('config.php');
- }
-
- /**
- * @return \Flarum\Install\Prerequisite\PrerequisiteInterface
- */
- protected function getPrerequisites()
- {
- return $this->application->make(PrerequisiteInterface::class);
- }
-
- /**
- * @return \Illuminate\Contracts\Validation\Factory
- */
- protected function getValidator()
- {
- return new Factory($this->application->make(Translator::class));
- }
-
- protected function showErrors($errors)
- {
- foreach ($errors as $error) {
- $this->info($error['message']);
-
- if (isset($error['detail'])) {
- $this->output->writeln(''.$error['detail'].'');
+ if (isset($problem['detail'])) {
+ $this->output->writeln(''.$problem['detail'].'');
}
}
}
diff --git a/framework/core/src/Install/Console/UserDataProvider.php b/framework/core/src/Install/Console/UserDataProvider.php
index a84df2050..d1c4caa20 100644
--- a/framework/core/src/Install/Console/UserDataProvider.php
+++ b/framework/core/src/Install/Console/UserDataProvider.php
@@ -11,6 +11,9 @@
namespace Flarum\Install\Console;
+use Flarum\Install\AdminUser;
+use Flarum\Install\DatabaseConfig;
+use Flarum\Install\Installation;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -33,75 +36,91 @@ class UserDataProvider implements DataProviderInterface
$this->questionHelper = $questionHelper;
}
- public function getDatabaseConfiguration()
+ public function configure(Installation $installation): Installation
+ {
+ return $installation
+ ->debugMode(false)
+ ->baseUrl($this->getBaseUrl())
+ ->databaseConfig($this->getDatabaseConfiguration())
+ ->adminUser($this->getAdminUser())
+ ->settings($this->getSettings());
+ }
+
+ private function getDatabaseConfiguration(): DatabaseConfig
{
$host = $this->ask('Database host:');
- $port = '3306';
+ $port = 3306;
if (str_contains($host, ':')) {
list($host, $port) = explode(':', $host, 2);
}
- return [
- 'driver' => 'mysql',
- 'host' => $host,
- 'port' => $port,
- 'database' => $this->ask('Database name:'),
- 'username' => $this->ask('Database user:'),
- 'password' => $this->secret('Database password:'),
- 'prefix' => $this->ask('Prefix:'),
- ];
+ return new DatabaseConfig(
+ 'mysql',
+ $host,
+ intval($port),
+ $this->ask('Database name:'),
+ $this->ask('Database user:'),
+ $this->secret('Database password:'),
+ $this->ask('Prefix:')
+ );
}
- public function getBaseUrl()
+ private function getBaseUrl()
{
return $this->baseUrl = rtrim($this->ask('Base URL:'), '/');
}
- public function getAdminUser()
+ private function getAdminUser(): AdminUser
{
- return [
- 'username' => $this->ask('Admin username:'),
- 'password' => $this->secret('Admin password:'),
- 'password_confirmation' => $this->secret('Admin password (confirmation):'),
- 'email' => $this->ask('Admin email address:'),
- ];
+ return new AdminUser(
+ $this->ask('Admin username:'),
+ $this->askForAdminPassword(),
+ $this->ask('Admin email address:')
+ );
}
- public function getSettings()
+ private function askForAdminPassword()
+ {
+ while (true) {
+ $password = $this->secret('Admin password:');
+
+ if (strlen($password) < 8) {
+ $this->validationError('Password must be at least 8 characters.');
+ continue;
+ }
+
+ $confirmation = $this->secret('Admin password (confirmation):');
+
+ if ($password !== $confirmation) {
+ $this->validationError('The password did not match its confirmation.');
+ continue;
+ }
+
+ return $password;
+ }
+ }
+
+ private function getSettings()
{
$title = $this->ask('Forum title:');
$baseUrl = $this->baseUrl ?: 'http://localhost';
return [
- 'allow_post_editing' => 'reply',
- 'allow_renaming' => '10',
- 'allow_sign_up' => '1',
- 'custom_less' => '',
- 'default_locale' => 'en',
- 'default_route' => '/all',
- 'extensions_enabled' => '[]',
'forum_title' => $title,
- 'forum_description' => '',
- 'mail_driver' => 'mail',
'mail_from' => 'noreply@'.preg_replace('/^www\./i', '', parse_url($baseUrl, PHP_URL_HOST)),
- 'theme_colored_header' => '0',
- 'theme_dark_mode' => '0',
- 'theme_primary_color' => '#4D698E',
- 'theme_secondary_color' => '#4D698E',
- 'welcome_message' => 'This is beta software and you should not use it in production.',
'welcome_title' => 'Welcome to '.$title,
];
}
- protected function ask($question, $default = null)
+ private function ask($question, $default = null)
{
$question = new Question("$question ", $default);
return $this->questionHelper->ask($this->input, $this->output, $question);
}
- protected function secret($question)
+ private function secret($question)
{
$question = new Question("$question ");
@@ -110,8 +129,9 @@ class UserDataProvider implements DataProviderInterface
return $this->questionHelper->ask($this->input, $this->output, $question);
}
- public function isDebugMode(): bool
+ private function validationError($message)
{
- return false;
+ $this->output->writeln("$message");
+ $this->output->writeln('Please try again.');
}
}
diff --git a/framework/core/src/Install/Controller/IndexController.php b/framework/core/src/Install/Controller/IndexController.php
index e96110969..3d66789d2 100644
--- a/framework/core/src/Install/Controller/IndexController.php
+++ b/framework/core/src/Install/Controller/IndexController.php
@@ -12,7 +12,7 @@
namespace Flarum\Install\Controller;
use Flarum\Http\Controller\AbstractHtmlController;
-use Flarum\Install\Prerequisite\PrerequisiteInterface;
+use Flarum\Install\Installation;
use Illuminate\Contracts\View\Factory;
use Psr\Http\Message\ServerRequestInterface as Request;
@@ -24,18 +24,18 @@ class IndexController extends AbstractHtmlController
protected $view;
/**
- * @var \Flarum\Install\Prerequisite\PrerequisiteInterface
+ * @var Installation
*/
- protected $prerequisite;
+ protected $installation;
/**
* @param Factory $view
- * @param PrerequisiteInterface $prerequisite
+ * @param Installation $installation
*/
- public function __construct(Factory $view, PrerequisiteInterface $prerequisite)
+ public function __construct(Factory $view, Installation $installation)
{
$this->view = $view;
- $this->prerequisite = $prerequisite;
+ $this->installation = $installation;
}
/**
@@ -46,13 +46,12 @@ class IndexController extends AbstractHtmlController
{
$view = $this->view->make('flarum.install::app')->with('title', 'Install Flarum');
- $this->prerequisite->check();
- $errors = $this->prerequisite->getErrors();
+ $problems = $this->installation->prerequisites()->problems();
- if (count($errors)) {
- $view->with('content', $this->view->make('flarum.install::errors')->with('errors', $errors));
- } else {
+ if ($problems->isEmpty()) {
$view->with('content', $this->view->make('flarum.install::install'));
+ } else {
+ $view->with('content', $this->view->make('flarum.install::problems')->with('problems', $problems));
}
return $view;
diff --git a/framework/core/src/Install/Controller/InstallController.php b/framework/core/src/Install/Controller/InstallController.php
index aaa13e277..cbefc3849 100644
--- a/framework/core/src/Install/Controller/InstallController.php
+++ b/framework/core/src/Install/Controller/InstallController.php
@@ -11,21 +11,23 @@
namespace Flarum\Install\Controller;
-use Exception;
use Flarum\Http\SessionAuthenticator;
-use Flarum\Install\Console\DefaultsDataProvider;
-use Flarum\Install\Console\InstallCommand;
+use Flarum\Install\AdminUser;
+use Flarum\Install\DatabaseConfig;
+use Flarum\Install\Installation;
+use Flarum\Install\StepFailed;
+use Flarum\Install\ValidationFailed;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface;
-use Symfony\Component\Console\Input\StringInput;
-use Symfony\Component\Console\Output\StreamOutput;
use Zend\Diactoros\Response;
-use Zend\Diactoros\Response\HtmlResponse;
class InstallController implements RequestHandlerInterface
{
- protected $command;
+ /**
+ * @var Installation
+ */
+ protected $installation;
/**
* @var SessionAuthenticator
@@ -34,12 +36,12 @@ class InstallController implements RequestHandlerInterface
/**
* InstallController constructor.
- * @param InstallCommand $command
+ * @param Installation $installation
* @param SessionAuthenticator $authenticator
*/
- public function __construct(InstallCommand $command, SessionAuthenticator $authenticator)
+ public function __construct(Installation $installation, SessionAuthenticator $authenticator)
{
- $this->command = $command;
+ $this->installation = $installation;
$this->authenticator = $authenticator;
}
@@ -50,55 +52,78 @@ class InstallController implements RequestHandlerInterface
public function handle(Request $request): ResponseInterface
{
$input = $request->getParsedBody();
-
- $data = new DefaultsDataProvider;
-
- $host = array_get($input, 'mysqlHost');
- $port = '3306';
-
- if (str_contains($host, ':')) {
- list($host, $port) = explode(':', $host, 2);
- }
-
- $data->setDatabaseConfiguration([
- 'driver' => 'mysql',
- 'host' => $host,
- 'database' => array_get($input, 'mysqlDatabase'),
- 'username' => array_get($input, 'mysqlUsername'),
- 'password' => array_get($input, 'mysqlPassword'),
- 'prefix' => array_get($input, 'tablePrefix'),
- 'port' => $port,
- ]);
-
- $data->setAdminUser([
- 'username' => array_get($input, 'adminUsername'),
- 'password' => array_get($input, 'adminPassword'),
- 'password_confirmation' => array_get($input, 'adminPasswordConfirmation'),
- 'email' => array_get($input, 'adminEmail'),
- ]);
-
$baseUrl = rtrim((string) $request->getUri(), '/');
- $data->setBaseUrl($baseUrl);
-
- $data->setSetting('forum_title', array_get($input, 'forumTitle'));
- $data->setSetting('mail_from', 'noreply@'.preg_replace('/^www\./i', '', parse_url($baseUrl, PHP_URL_HOST)));
- $data->setSetting('welcome_title', 'Welcome to '.array_get($input, 'forumTitle'));
-
- $body = fopen('php://temp', 'wb+');
- $input = new StringInput('');
- $output = new StreamOutput($body);
-
- $this->command->setDataSource($data);
try {
- $this->command->run($input, $output);
- } catch (Exception $e) {
- return new HtmlResponse($e->getMessage(), 500);
+ $pipeline = $this->installation
+ ->baseUrl($baseUrl)
+ ->databaseConfig($this->makeDatabaseConfig($input))
+ ->adminUser($this->makeAdminUser($input))
+ ->settings([
+ 'forum_title' => array_get($input, 'forumTitle'),
+ 'mail_from' => 'noreply@'.preg_replace('/^www\./i', '', parse_url($baseUrl, PHP_URL_HOST)),
+ 'welcome_title' => 'Welcome to '.array_get($input, 'forumTitle'),
+ ])
+ ->build();
+ } catch (ValidationFailed $e) {
+ return new Response\HtmlResponse($e->getMessage(), 500);
+ }
+
+ try {
+ $pipeline->run();
+ } catch (StepFailed $e) {
+ return new Response\HtmlResponse($e->getPrevious()->getMessage(), 500);
}
$session = $request->getAttribute('session');
$this->authenticator->logIn($session, 1);
- return new Response($body);
+ return new Response\EmptyResponse;
+ }
+
+ private function makeDatabaseConfig(array $input): DatabaseConfig
+ {
+ $host = array_get($input, 'mysqlHost');
+ $port = 3306;
+
+ if (str_contains($host, ':')) {
+ list($host, $port) = explode(':', $host, 2);
+ }
+
+ return new DatabaseConfig(
+ 'mysql',
+ $host,
+ intval($port),
+ array_get($input, 'mysqlDatabase'),
+ array_get($input, 'mysqlUsername'),
+ array_get($input, 'mysqlPassword'),
+ array_get($input, 'tablePrefix')
+ );
+ }
+
+ /**
+ * @param array $input
+ * @return AdminUser
+ * @throws ValidationFailed
+ */
+ private function makeAdminUser(array $input): AdminUser
+ {
+ return new AdminUser(
+ array_get($input, 'adminUsername'),
+ $this->getConfirmedAdminPassword($input),
+ array_get($input, 'adminEmail')
+ );
+ }
+
+ private function getConfirmedAdminPassword(array $input): string
+ {
+ $password = array_get($input, 'adminPassword');
+ $confirmation = array_get($input, 'adminPasswordConfirmation');
+
+ if ($password !== $confirmation) {
+ throw new ValidationFailed('The admin password did not match its confirmation.');
+ }
+
+ return $password;
}
}
diff --git a/framework/core/src/Install/DatabaseConfig.php b/framework/core/src/Install/DatabaseConfig.php
new file mode 100644
index 000000000..60ed3ea5d
--- /dev/null
+++ b/framework/core/src/Install/DatabaseConfig.php
@@ -0,0 +1,100 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Install;
+
+use Illuminate\Contracts\Support\Arrayable;
+
+class DatabaseConfig implements Arrayable
+{
+ private $driver;
+ private $host;
+ private $port;
+ private $database;
+ private $username;
+ private $password;
+ private $prefix;
+
+ public function __construct($driver, $host, $port, $database, $username, $password, $prefix)
+ {
+ $this->driver = $driver;
+ $this->host = $host;
+ $this->port = $port;
+ $this->database = $database;
+ $this->username = $username;
+ $this->password = $password;
+ $this->prefix = $prefix;
+
+ $this->validate();
+ }
+
+ public function toArray()
+ {
+ return [
+ 'driver' => $this->driver,
+ 'host' => $this->host,
+ 'port' => $this->port,
+ 'database' => $this->database,
+ 'username' => $this->username,
+ 'password' => $this->password,
+ 'charset' => 'utf8mb4',
+ 'collation' => 'utf8mb4_unicode_ci',
+ 'prefix' => $this->prefix,
+ 'strict' => false,
+ 'engine' => 'InnoDB',
+ ];
+ }
+
+ private function validate()
+ {
+ if (empty($this->driver)) {
+ throw new ValidationFailed('Please specify a database driver.');
+ }
+
+ if ($this->driver !== 'mysql') {
+ throw new ValidationFailed('Currently, only MySQL/MariaDB is supported.');
+ }
+
+ if (empty($this->host)) {
+ throw new ValidationFailed('Please specify the hostname of your database server.');
+ }
+
+ if (! is_int($this->port) || $this->port < 1 || $this->port > 65535) {
+ throw new ValidationFailed('Please provide a valid port number between 1 and 65535.');
+ }
+
+ if (empty($this->database)) {
+ throw new ValidationFailed('Please specify the database name.');
+ }
+
+ if (! is_string($this->database)) {
+ throw new ValidationFailed('The database name must be a non-empty string.');
+ }
+
+ if (empty($this->username)) {
+ throw new ValidationFailed('Please specify the username for accessing the database.');
+ }
+
+ if (! is_string($this->database)) {
+ throw new ValidationFailed('The username must be a non-empty string.');
+ }
+
+ if (! empty($this->prefix)) {
+ if (! preg_match('/^[\pL\pM\pN_-]+$/u', $this->prefix)) {
+ throw new ValidationFailed('The prefix may only contain characters, dashes and underscores.');
+ }
+
+ if (strlen($this->prefix) > 10) {
+ throw new ValidationFailed('The prefix should be no longer than 10 characters.');
+ }
+ }
+ }
+}
diff --git a/framework/core/src/Install/InstallServiceProvider.php b/framework/core/src/Install/InstallServiceProvider.php
index 4a0c8ae03..1ea4fa07b 100644
--- a/framework/core/src/Install/InstallServiceProvider.php
+++ b/framework/core/src/Install/InstallServiceProvider.php
@@ -14,11 +14,6 @@ namespace Flarum\Install;
use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Http\RouteCollection;
use Flarum\Http\RouteHandlerFactory;
-use Flarum\Install\Prerequisite\Composite;
-use Flarum\Install\Prerequisite\PhpExtensions;
-use Flarum\Install\Prerequisite\PhpVersion;
-use Flarum\Install\Prerequisite\PrerequisiteInterface;
-use Flarum\Install\Prerequisite\WritablePaths;
class InstallServiceProvider extends AbstractServiceProvider
{
@@ -27,32 +22,17 @@ class InstallServiceProvider extends AbstractServiceProvider
*/
public function register()
{
- $this->app->bind(
- PrerequisiteInterface::class,
- function () {
- return new Composite(
- new PhpVersion('7.1.0'),
- new PhpExtensions([
- 'dom',
- 'gd',
- 'json',
- 'mbstring',
- 'openssl',
- 'pdo_mysql',
- 'tokenizer',
- ]),
- new WritablePaths([
- base_path(),
- public_path('assets'),
- storage_path(),
- ])
- );
- }
- );
-
$this->app->singleton('flarum.install.routes', function () {
return new RouteCollection;
});
+
+ $this->app->singleton(Installation::class, function () {
+ return new Installation(
+ $this->app->basePath(),
+ $this->app->publicPath(),
+ $this->app->storagePath()
+ );
+ });
}
/**
diff --git a/framework/core/src/Install/Installation.php b/framework/core/src/Install/Installation.php
new file mode 100644
index 000000000..074d5d135
--- /dev/null
+++ b/framework/core/src/Install/Installation.php
@@ -0,0 +1,164 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Install;
+
+class Installation
+{
+ private $basePath;
+ private $publicPath;
+ private $storagePath;
+
+ private $configPath;
+ private $debug = false;
+ private $baseUrl;
+ private $customSettings = [];
+
+ /** @var DatabaseConfig */
+ private $dbConfig;
+
+ /** @var AdminUser */
+ private $adminUser;
+
+ // A few instance variables to persist objects between steps.
+ // Could also be local variables in build(), but this way
+ // access in closures is easier. :)
+
+ /** @var \Illuminate\Database\ConnectionInterface */
+ private $db;
+
+ public function __construct($basePath, $publicPath, $storagePath)
+ {
+ $this->basePath = $basePath;
+ $this->publicPath = $publicPath;
+ $this->storagePath = $storagePath;
+ }
+
+ public function configPath($path)
+ {
+ $this->configPath = $path;
+
+ return $this;
+ }
+
+ public function debugMode($flag)
+ {
+ $this->debug = $flag;
+
+ return $this;
+ }
+
+ public function databaseConfig(DatabaseConfig $dbConfig)
+ {
+ $this->dbConfig = $dbConfig;
+
+ return $this;
+ }
+
+ public function baseUrl($baseUrl)
+ {
+ $this->baseUrl = $baseUrl;
+
+ return $this;
+ }
+
+ public function settings($settings)
+ {
+ $this->customSettings = $settings;
+
+ return $this;
+ }
+
+ public function adminUser(AdminUser $admin)
+ {
+ $this->adminUser = $admin;
+
+ return $this;
+ }
+
+ public function prerequisites(): Prerequisite\PrerequisiteInterface
+ {
+ return new Prerequisite\Composite(
+ new Prerequisite\PhpVersion('7.1.0'),
+ new Prerequisite\PhpExtensions([
+ 'dom',
+ 'gd',
+ 'json',
+ 'mbstring',
+ 'openssl',
+ 'pdo_mysql',
+ 'tokenizer',
+ ]),
+ new Prerequisite\WritablePaths([
+ $this->basePath,
+ $this->getAssetPath(),
+ $this->storagePath,
+ ])
+ );
+ }
+
+ public function build(): Pipeline
+ {
+ $pipeline = new Pipeline;
+
+ $pipeline->pipe(function () {
+ return new Steps\ConnectToDatabase(
+ $this->dbConfig,
+ function ($connection) {
+ $this->db = $connection;
+ }
+ );
+ });
+
+ $pipeline->pipe(function () {
+ return new Steps\StoreConfig(
+ $this->debug, $this->dbConfig, $this->baseUrl, $this->getConfigPath()
+ );
+ });
+
+ $pipeline->pipe(function () {
+ return new Steps\RunMigrations($this->db, $this->getMigrationPath());
+ });
+
+ $pipeline->pipe(function () {
+ return new Steps\WriteSettings($this->db, $this->customSettings);
+ });
+
+ $pipeline->pipe(function () {
+ return new Steps\CreateAdminUser($this->db, $this->adminUser);
+ });
+
+ $pipeline->pipe(function () {
+ return new Steps\PublishAssets($this->basePath, $this->getAssetPath());
+ });
+
+ $pipeline->pipe(function () {
+ return new Steps\EnableBundledExtensions($this->db, $this->basePath, $this->getAssetPath());
+ });
+
+ return $pipeline;
+ }
+
+ private function getConfigPath()
+ {
+ return $this->basePath.'/'.($this->configPath ?? 'config.php');
+ }
+
+ private function getAssetPath()
+ {
+ return "$this->publicPath/assets";
+ }
+
+ private function getMigrationPath()
+ {
+ return __DIR__.'/../../migrations';
+ }
+}
diff --git a/framework/core/src/Install/Installer.php b/framework/core/src/Install/Installer.php
index 3df6a1b86..287a513fe 100644
--- a/framework/core/src/Install/Installer.php
+++ b/framework/core/src/Install/Installer.php
@@ -17,6 +17,8 @@ use Flarum\Http\Middleware\HandleErrorsWithWhoops;
use Flarum\Http\Middleware\StartSession;
use Flarum\Install\Console\InstallCommand;
use Illuminate\Contracts\Container\Container;
+use Illuminate\Contracts\Translation\Translator;
+use Illuminate\Validation\Factory;
use Zend\Stratigility\MiddlewarePipe;
class Installer implements AppInterface
@@ -52,7 +54,10 @@ class Installer implements AppInterface
public function getConsoleCommands()
{
return [
- $this->container->make(InstallCommand::class),
+ new InstallCommand(
+ $this->container->make(Installation::class),
+ new Factory($this->container->make(Translator::class))
+ ),
];
}
}
diff --git a/framework/core/src/Install/Pipeline.php b/framework/core/src/Install/Pipeline.php
new file mode 100644
index 000000000..31c8cb1c9
--- /dev/null
+++ b/framework/core/src/Install/Pipeline.php
@@ -0,0 +1,108 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Install;
+
+use Exception;
+use SplStack;
+
+class Pipeline
+{
+ /**
+ * @var callable[]
+ */
+ private $steps;
+
+ /**
+ * @var callable[]
+ */
+ private $callbacks;
+
+ /**
+ * @var SplStack
+ */
+ private $successfulSteps;
+
+ public function __construct(array $steps = [])
+ {
+ $this->steps = $steps;
+ }
+
+ public function pipe(callable $factory)
+ {
+ $this->steps[] = $factory;
+
+ return $this;
+ }
+
+ public function on($event, callable $callback)
+ {
+ $this->callbacks[$event] = $callback;
+
+ return $this;
+ }
+
+ public function run()
+ {
+ $this->successfulSteps = new SplStack;
+
+ try {
+ foreach ($this->steps as $factory) {
+ $this->runStep($factory);
+ }
+ } catch (StepFailed $failure) {
+ $this->revertReversibleSteps();
+
+ throw $failure;
+ }
+ }
+
+ /**
+ * @param callable $factory
+ * @throws StepFailed
+ */
+ private function runStep(callable $factory)
+ {
+ /** @var Step $step */
+ $step = $factory();
+
+ $this->fireCallbacks('start', $step);
+
+ try {
+ $step->run();
+ $this->successfulSteps->push($step);
+
+ $this->fireCallbacks('end', $step);
+ } catch (Exception $e) {
+ $this->fireCallbacks('fail', $step);
+
+ throw new StepFailed('Step failed', 0, $e);
+ }
+ }
+
+ private function revertReversibleSteps()
+ {
+ foreach ($this->successfulSteps as $step) {
+ if ($step instanceof ReversibleStep) {
+ $this->fireCallbacks('rollback', $step);
+
+ $step->revert();
+ }
+ }
+ }
+
+ private function fireCallbacks($event, Step $step)
+ {
+ if (isset($this->callbacks[$event])) {
+ ($this->callbacks[$event])($step);
+ }
+ }
+}
diff --git a/framework/core/src/Install/Prerequisite/AbstractPrerequisite.php b/framework/core/src/Install/Prerequisite/AbstractPrerequisite.php
deleted file mode 100644
index 06c6210b1..000000000
--- a/framework/core/src/Install/Prerequisite/AbstractPrerequisite.php
+++ /dev/null
@@ -1,24 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Flarum\Install\Prerequisite;
-
-abstract class AbstractPrerequisite implements PrerequisiteInterface
-{
- protected $errors = [];
-
- abstract public function check();
-
- public function getErrors()
- {
- return $this->errors;
- }
-}
diff --git a/framework/core/src/Install/Prerequisite/Composite.php b/framework/core/src/Install/Prerequisite/Composite.php
index ed0fb77e4..dcf380f1c 100644
--- a/framework/core/src/Install/Prerequisite/Composite.php
+++ b/framework/core/src/Install/Prerequisite/Composite.php
@@ -11,6 +11,8 @@
namespace Flarum\Install\Prerequisite;
+use Illuminate\Support\Collection;
+
class Composite implements PrerequisiteInterface
{
/**
@@ -25,21 +27,14 @@ class Composite implements PrerequisiteInterface
}
}
- public function check()
+ public function problems(): Collection
{
return array_reduce(
$this->prerequisites,
- function ($previous, PrerequisiteInterface $prerequisite) {
- return $prerequisite->check() && $previous;
+ function (Collection $errors, PrerequisiteInterface $condition) {
+ return $errors->concat($condition->problems());
},
- true
+ new Collection
);
}
-
- public function getErrors()
- {
- return collect($this->prerequisites)->map(function (PrerequisiteInterface $prerequisite) {
- return $prerequisite->getErrors();
- })->reduce('array_merge', []);
- }
}
diff --git a/framework/core/src/Install/Prerequisite/PhpExtensions.php b/framework/core/src/Install/Prerequisite/PhpExtensions.php
index 434eff927..89ed4ac7b 100644
--- a/framework/core/src/Install/Prerequisite/PhpExtensions.php
+++ b/framework/core/src/Install/Prerequisite/PhpExtensions.php
@@ -11,7 +11,9 @@
namespace Flarum\Install\Prerequisite;
-class PhpExtensions extends AbstractPrerequisite
+use Illuminate\Support\Collection;
+
+class PhpExtensions implements PrerequisiteInterface
{
protected $extensions;
@@ -20,14 +22,15 @@ class PhpExtensions extends AbstractPrerequisite
$this->extensions = $extensions;
}
- public function check()
+ public function problems(): Collection
{
- foreach ($this->extensions as $extension) {
- if (! extension_loaded($extension)) {
- $this->errors[] = [
+ return (new Collection($this->extensions))
+ ->reject(function ($extension) {
+ return extension_loaded($extension);
+ })->map(function ($extension) {
+ return [
'message' => "The PHP extension '$extension' is required.",
];
- }
- }
+ });
}
}
diff --git a/framework/core/src/Install/Prerequisite/PhpVersion.php b/framework/core/src/Install/Prerequisite/PhpVersion.php
index b2960d372..3257b27c9 100644
--- a/framework/core/src/Install/Prerequisite/PhpVersion.php
+++ b/framework/core/src/Install/Prerequisite/PhpVersion.php
@@ -11,7 +11,9 @@
namespace Flarum\Install\Prerequisite;
-class PhpVersion extends AbstractPrerequisite
+use Illuminate\Support\Collection;
+
+class PhpVersion implements PrerequisiteInterface
{
protected $minVersion;
@@ -20,13 +22,17 @@ class PhpVersion extends AbstractPrerequisite
$this->minVersion = $minVersion;
}
- public function check()
+ public function problems(): Collection
{
+ $collection = new Collection;
+
if (version_compare(PHP_VERSION, $this->minVersion, '<')) {
- $this->errors[] = [
+ $collection->push([
'message' => "PHP $this->minVersion is required.",
- 'detail' => 'You are running version '.PHP_VERSION.'. Talk to your hosting provider about upgrading to the latest PHP version.',
- ];
+ 'detail' => 'You are running version '.PHP_VERSION.'. You might want to talk to your system administrator about upgrading to the latest PHP version.',
+ ]);
}
+
+ return $collection;
}
}
diff --git a/framework/core/src/Install/Prerequisite/PrerequisiteInterface.php b/framework/core/src/Install/Prerequisite/PrerequisiteInterface.php
index 115e9f808..9b7907883 100644
--- a/framework/core/src/Install/Prerequisite/PrerequisiteInterface.php
+++ b/framework/core/src/Install/Prerequisite/PrerequisiteInterface.php
@@ -11,9 +11,18 @@
namespace Flarum\Install\Prerequisite;
+use Illuminate\Support\Collection;
+
interface PrerequisiteInterface
{
- public function check();
-
- public function getErrors();
+ /**
+ * Verify that this prerequisite is fulfilled.
+ *
+ * If everything is okay, this method should return an empty Collection
+ * instance. When problems are detected, it should return a Collection of
+ * arrays, each having at least a "message" and optionally a "detail" key.
+ *
+ * @return Collection
+ */
+ public function problems(): Collection;
}
diff --git a/framework/core/src/Install/Prerequisite/WritablePaths.php b/framework/core/src/Install/Prerequisite/WritablePaths.php
index 1f356ee6a..0d5e8e7c5 100644
--- a/framework/core/src/Install/Prerequisite/WritablePaths.php
+++ b/framework/core/src/Install/Prerequisite/WritablePaths.php
@@ -11,7 +11,9 @@
namespace Flarum\Install\Prerequisite;
-class WritablePaths extends AbstractPrerequisite
+use Illuminate\Support\Collection;
+
+class WritablePaths implements PrerequisiteInterface
{
protected $paths;
@@ -20,21 +22,36 @@ class WritablePaths extends AbstractPrerequisite
$this->paths = $paths;
}
- public function check()
+ public function problems(): Collection
{
- foreach ($this->paths as $path) {
- if (! file_exists($path)) {
- $this->errors[] = [
+ return $this->getMissingPaths()
+ ->concat($this->getNonWritablePaths());
+ }
+
+ private function getMissingPaths(): Collection
+ {
+ return (new Collection($this->paths))
+ ->reject(function ($path) {
+ return file_exists($path);
+ })->map(function ($path) {
+ return [
'message' => 'The '.$this->getAbsolutePath($path).' directory doesn\'t exist',
'detail' => 'This directory is necessary for the installation. Please create the folder.',
];
- } elseif (! is_writable($path)) {
- $this->errors[] = [
+ });
+ }
+
+ private function getNonWritablePaths(): Collection
+ {
+ return (new Collection($this->paths))
+ ->filter(function ($path) {
+ return file_exists($path) && ! is_writable($path);
+ })->map(function ($path) {
+ return [
'message' => 'The '.$this->getAbsolutePath($path).' directory is not writable.',
'detail' => 'Please chmod this directory'.($path !== public_path() ? ' and its contents' : '').' to 0775.'
];
- }
- }
+ });
}
private function getAbsolutePath($path)
diff --git a/framework/core/src/Install/ReversibleStep.php b/framework/core/src/Install/ReversibleStep.php
new file mode 100644
index 000000000..b65de4993
--- /dev/null
+++ b/framework/core/src/Install/ReversibleStep.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Install;
+
+interface ReversibleStep
+{
+ public function revert();
+}
diff --git a/framework/core/src/Install/Step.php b/framework/core/src/Install/Step.php
new file mode 100644
index 000000000..6b0de2658
--- /dev/null
+++ b/framework/core/src/Install/Step.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Install;
+
+interface Step
+{
+ /**
+ * A one-line status message summarizing what's happening in this step.
+ *
+ * @return string
+ */
+ public function getMessage();
+
+ /**
+ * Do the work that constitutes this step.
+ *
+ * This method should raise a `StepFailed` exception whenever something goes
+ * wrong that should result in the entire installation being reverted.
+ *
+ * @return void
+ * @throws StepFailed
+ */
+ public function run();
+}
diff --git a/framework/core/src/Install/StepFailed.php b/framework/core/src/Install/StepFailed.php
new file mode 100644
index 000000000..8fe1a3b7d
--- /dev/null
+++ b/framework/core/src/Install/StepFailed.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Install;
+
+use Exception;
+
+class StepFailed extends Exception
+{
+}
diff --git a/framework/core/src/Install/Steps/ConnectToDatabase.php b/framework/core/src/Install/Steps/ConnectToDatabase.php
new file mode 100644
index 000000000..51696731f
--- /dev/null
+++ b/framework/core/src/Install/Steps/ConnectToDatabase.php
@@ -0,0 +1,62 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Install\Steps;
+
+use Flarum\Install\DatabaseConfig;
+use Flarum\Install\Step;
+use Illuminate\Database\Connectors\MySqlConnector;
+use Illuminate\Database\MySqlConnection;
+use RangeException;
+
+class ConnectToDatabase implements Step
+{
+ private $dbConfig;
+ private $store;
+
+ public function __construct(DatabaseConfig $dbConfig, callable $store)
+ {
+ $this->dbConfig = $dbConfig;
+ $this->store = $store;
+ }
+
+ public function getMessage()
+ {
+ return 'Connecting to database';
+ }
+
+ public function run()
+ {
+ $config = $this->dbConfig->toArray();
+ $pdo = (new MySqlConnector)->connect($config);
+
+ $version = $pdo->query('SELECT VERSION()')->fetchColumn();
+
+ if (str_contains($version, 'MariaDB')) {
+ if (version_compare($version, '10.0.5', '<')) {
+ throw new RangeException('MariaDB version too low. You need at least MariaDB 10.0.5');
+ }
+ } else {
+ if (version_compare($version, '5.6.0', '<')) {
+ throw new RangeException('MySQL version too low. You need at least MySQL 5.6.');
+ }
+ }
+
+ ($this->store)(
+ new MySqlConnection(
+ $pdo,
+ $config['database'],
+ $config['prefix'],
+ $config
+ )
+ );
+ }
+}
diff --git a/framework/core/src/Install/Steps/CreateAdminUser.php b/framework/core/src/Install/Steps/CreateAdminUser.php
new file mode 100644
index 000000000..b058c27cf
--- /dev/null
+++ b/framework/core/src/Install/Steps/CreateAdminUser.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Install\Steps;
+
+use Flarum\Group\Group;
+use Flarum\Install\AdminUser;
+use Flarum\Install\Step;
+use Illuminate\Database\ConnectionInterface;
+
+class CreateAdminUser implements Step
+{
+ /**
+ * @var ConnectionInterface
+ */
+ private $database;
+
+ /**
+ * @var AdminUser
+ */
+ private $admin;
+
+ public function __construct(ConnectionInterface $database, AdminUser $admin)
+ {
+ $this->database = $database;
+ $this->admin = $admin;
+ }
+
+ public function getMessage()
+ {
+ return 'Creating admin user '.$this->admin->getUsername();
+ }
+
+ public function run()
+ {
+ $uid = $this->database->table('users')->insertGetId(
+ $this->admin->getAttributes()
+ );
+
+ $this->database->table('group_user')->insert([
+ 'user_id' => $uid,
+ 'group_id' => Group::ADMINISTRATOR_ID,
+ ]);
+ }
+}
diff --git a/framework/core/src/Install/Steps/EnableBundledExtensions.php b/framework/core/src/Install/Steps/EnableBundledExtensions.php
new file mode 100644
index 000000000..b887339e5
--- /dev/null
+++ b/framework/core/src/Install/Steps/EnableBundledExtensions.php
@@ -0,0 +1,122 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Install\Steps;
+
+use Flarum\Database\DatabaseMigrationRepository;
+use Flarum\Database\Migrator;
+use Flarum\Extension\Extension;
+use Flarum\Install\Step;
+use Flarum\Settings\DatabaseSettingsRepository;
+use Illuminate\Database\ConnectionInterface;
+use Illuminate\Support\Arr;
+use Illuminate\Support\Collection;
+use League\Flysystem\Adapter\Local;
+use League\Flysystem\Filesystem;
+
+class EnableBundledExtensions implements Step
+{
+ /**
+ * @var ConnectionInterface
+ */
+ private $database;
+
+ /**
+ * @var string
+ */
+ private $basePath;
+
+ /**
+ * @var string
+ */
+ private $assetPath;
+
+ public function __construct(ConnectionInterface $database, $basePath, $assetPath)
+ {
+ $this->database = $database;
+ $this->basePath = $basePath;
+ $this->assetPath = $assetPath;
+ }
+
+ public function getMessage()
+ {
+ return 'Enabling bundled extensions';
+ }
+
+ public function run()
+ {
+ $extensions = $this->loadExtensions();
+
+ foreach ($extensions as $extension) {
+ $extension->migrate($this->getMigrator());
+ $extension->copyAssetsTo(
+ new Filesystem(new Local($this->assetPath))
+ );
+ }
+
+ (new DatabaseSettingsRepository($this->database))->set(
+ 'extensions_enabled',
+ $extensions->keys()->toJson()
+ );
+ }
+
+ const EXTENSION_WHITELIST = [
+ 'flarum-approval',
+ 'flarum-bbcode',
+ 'flarum-emoji',
+ 'flarum-lang-english',
+ 'flarum-flags',
+ 'flarum-likes',
+ 'flarum-lock',
+ 'flarum-markdown',
+ 'flarum-mentions',
+ 'flarum-statistics',
+ 'flarum-sticky',
+ 'flarum-subscriptions',
+ 'flarum-suspend',
+ 'flarum-tags',
+ ];
+
+ /**
+ * @return \Illuminate\Support\Collection
+ */
+ private function loadExtensions()
+ {
+ $json = file_get_contents("$this->basePath/vendor/composer/installed.json");
+
+ return (new Collection(json_decode($json, true)))
+ ->filter(function ($package) {
+ return Arr::get($package, 'type') == 'flarum-extension';
+ })->filter(function ($package) {
+ return ! empty(Arr::get($package, 'name'));
+ })->map(function ($package) {
+ $extension = new Extension($this->basePath.'/vendor/'.Arr::get($package, 'name'), $package);
+ $extension->setVersion(Arr::get($package, 'version'));
+
+ return $extension;
+ })->filter(function (Extension $extension) {
+ return in_array($extension->getId(), self::EXTENSION_WHITELIST);
+ })->sortBy(function (Extension $extension) {
+ return $extension->composerJsonAttribute('extra.flarum-extension.title');
+ })->mapWithKeys(function (Extension $extension) {
+ return [$extension->getId() => $extension];
+ });
+ }
+
+ private function getMigrator()
+ {
+ return $this->migrator = $this->migrator ?? new Migrator(
+ new DatabaseMigrationRepository($this->database, 'migrations'),
+ $this->database,
+ new \Illuminate\Filesystem\Filesystem
+ );
+ }
+}
diff --git a/framework/core/src/Install/Steps/PublishAssets.php b/framework/core/src/Install/Steps/PublishAssets.php
new file mode 100644
index 000000000..b8f20868b
--- /dev/null
+++ b/framework/core/src/Install/Steps/PublishAssets.php
@@ -0,0 +1,58 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Install\Steps;
+
+use Flarum\Install\ReversibleStep;
+use Flarum\Install\Step;
+use Illuminate\Filesystem\Filesystem;
+
+class PublishAssets implements Step, ReversibleStep
+{
+ /**
+ * @var string
+ */
+ private $basePath;
+
+ /**
+ * @var string
+ */
+ private $assetPath;
+
+ public function __construct($basePath, $assetPath)
+ {
+ $this->basePath = $basePath;
+ $this->assetPath = $assetPath;
+ }
+
+ public function getMessage()
+ {
+ return 'Publishing all assets';
+ }
+
+ public function run()
+ {
+ (new Filesystem)->copyDirectory(
+ "$this->basePath/vendor/components/font-awesome/webfonts",
+ $this->targetPath()
+ );
+ }
+
+ public function revert()
+ {
+ (new Filesystem)->deleteDirectory($this->targetPath());
+ }
+
+ private function targetPath()
+ {
+ return "$this->assetPath/fonts";
+ }
+}
diff --git a/framework/core/src/Install/Steps/RunMigrations.php b/framework/core/src/Install/Steps/RunMigrations.php
new file mode 100644
index 000000000..3bf2049ef
--- /dev/null
+++ b/framework/core/src/Install/Steps/RunMigrations.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Install\Steps;
+
+use Flarum\Database\DatabaseMigrationRepository;
+use Flarum\Database\Migrator;
+use Flarum\Install\Step;
+use Illuminate\Database\ConnectionInterface;
+use Illuminate\Filesystem\Filesystem;
+
+class RunMigrations implements Step
+{
+ /**
+ * @var ConnectionInterface
+ */
+ private $database;
+
+ /**
+ * @var string
+ */
+ private $path;
+
+ public function __construct(ConnectionInterface $database, $path)
+ {
+ $this->database = $database;
+ $this->path = $path;
+ }
+
+ public function getMessage()
+ {
+ return 'Running migrations';
+ }
+
+ public function run()
+ {
+ $migrator = $this->getMigrator();
+
+ $migrator->getRepository()->createRepository();
+ $migrator->run($this->path);
+ }
+
+ private function getMigrator()
+ {
+ $repository = new DatabaseMigrationRepository(
+ $this->database, 'migrations'
+ );
+ $files = new Filesystem;
+
+ return new Migrator($repository, $this->database, $files);
+ }
+}
diff --git a/framework/core/src/Install/Steps/StoreConfig.php b/framework/core/src/Install/Steps/StoreConfig.php
new file mode 100644
index 000000000..757f3e376
--- /dev/null
+++ b/framework/core/src/Install/Steps/StoreConfig.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Install\Steps;
+
+use Flarum\Install\DatabaseConfig;
+use Flarum\Install\ReversibleStep;
+use Flarum\Install\Step;
+
+class StoreConfig implements Step, ReversibleStep
+{
+ private $debugMode;
+
+ private $dbConfig;
+
+ private $baseUrl;
+
+ private $configFile;
+
+ public function __construct($debugMode, DatabaseConfig $dbConfig, $baseUrl, $configFile)
+ {
+ $this->debugMode = $debugMode;
+ $this->dbConfig = $dbConfig;
+ $this->baseUrl = $baseUrl;
+
+ $this->configFile = $configFile;
+ }
+
+ public function getMessage()
+ {
+ return 'Writing config file';
+ }
+
+ public function run()
+ {
+ file_put_contents(
+ $this->configFile,
+ 'buildConfig(), true).';'
+ );
+ }
+
+ public function revert()
+ {
+ @unlink($this->configFile);
+ }
+
+ private function buildConfig()
+ {
+ return [
+ 'debug' => $this->debugMode,
+ 'database' => $this->dbConfig->toArray(),
+ 'url' => $this->baseUrl,
+ 'paths' => $this->getPathsConfig(),
+ ];
+ }
+
+ private function getPathsConfig()
+ {
+ return [
+ 'api' => 'api',
+ 'admin' => 'admin',
+ ];
+ }
+}
diff --git a/framework/core/src/Install/Steps/WriteSettings.php b/framework/core/src/Install/Steps/WriteSettings.php
new file mode 100644
index 000000000..366fc3696
--- /dev/null
+++ b/framework/core/src/Install/Steps/WriteSettings.php
@@ -0,0 +1,80 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Install\Steps;
+
+use Flarum\Foundation\Application;
+use Flarum\Install\Step;
+use Flarum\Settings\DatabaseSettingsRepository;
+use Illuminate\Database\ConnectionInterface;
+
+class WriteSettings implements Step
+{
+ /**
+ * @var ConnectionInterface
+ */
+ private $database;
+
+ /**
+ * @var array
+ */
+ private $custom;
+
+ public function __construct(ConnectionInterface $database, array $custom)
+ {
+ $this->database = $database;
+ $this->custom = $custom;
+ }
+
+ public function getMessage()
+ {
+ return 'Writing default settings';
+ }
+
+ public function run()
+ {
+ $repo = new DatabaseSettingsRepository($this->database);
+
+ $repo->set('version', Application::VERSION);
+
+ foreach ($this->getSettings() as $key => $value) {
+ $repo->set($key, $value);
+ }
+ }
+
+ private function getSettings()
+ {
+ return $this->custom + $this->getDefaults();
+ }
+
+ private function getDefaults()
+ {
+ return [
+ 'allow_post_editing' => 'reply',
+ 'allow_renaming' => '10',
+ 'allow_sign_up' => '1',
+ 'custom_less' => '',
+ 'default_locale' => 'en',
+ 'default_route' => '/all',
+ 'extensions_enabled' => '[]',
+ 'forum_title' => 'A new Flarum forum',
+ 'forum_description' => '',
+ 'mail_driver' => 'mail',
+ 'mail_from' => 'noreply@localhost',
+ 'theme_colored_header' => '0',
+ 'theme_dark_mode' => '0',
+ 'theme_primary_color' => '#4D698E',
+ 'theme_secondary_color' => '#4D698E',
+ 'welcome_message' => 'This is beta software and you should not use it in production.',
+ 'welcome_title' => 'Welcome to Flarum',
+ ];
+ }
+}
diff --git a/framework/core/src/Install/ValidationFailed.php b/framework/core/src/Install/ValidationFailed.php
new file mode 100644
index 000000000..d79206637
--- /dev/null
+++ b/framework/core/src/Install/ValidationFailed.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Install;
+
+use Exception;
+
+class ValidationFailed extends Exception
+{
+}
diff --git a/framework/core/tests/Install/DefaultInstallationCommandTest.php b/framework/core/tests/Install/DefaultInstallationCommandTest.php
deleted file mode 100644
index 0d2fee829..000000000
--- a/framework/core/tests/Install/DefaultInstallationCommandTest.php
+++ /dev/null
@@ -1,59 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Flarum\Tests\Install;
-
-use Flarum\Install\Console\InstallCommand;
-use Flarum\Tests\Test\TestCase;
-use Illuminate\Database\Connectors\ConnectionFactory;
-use Symfony\Component\Console\Input\StringInput;
-use Symfony\Component\Console\Output\StreamOutput;
-
-class DefaultInstallationCommandTest extends TestCase
-{
- protected $isInstalled = false;
-
- /**
- * @test
- */
- public function allows_forum_installation()
- {
- if (file_exists(base_path('config.php'))) {
- unlink(base_path('config.php'));
- }
-
- /** @var InstallCommand $command */
- $command = app(InstallCommand::class);
- $command->setDataSource($this->configuration);
-
- $body = fopen('php://temp', 'wb+');
- $input = new StringInput('');
- $output = new StreamOutput($body);
-
- $command->run($input, $output);
-
- $this->assertFileExists(base_path('config.php'));
-
- $admin = $this->configuration->getAdminUser();
-
- $this->assertEquals(
- $this->getDatabase()->table('users')->find(1)->username,
- $admin['username']
- );
- }
-
- private function getDatabase()
- {
- $factory = new ConnectionFactory(app());
-
- return $factory->make($this->configuration->getDatabaseConfiguration());
- }
-}
diff --git a/framework/core/tests/Install/DefaultInstallationTest.php b/framework/core/tests/Install/DefaultInstallationTest.php
new file mode 100644
index 000000000..27eac7a42
--- /dev/null
+++ b/framework/core/tests/Install/DefaultInstallationTest.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Tests\Install;
+
+use Flarum\Install\AdminUser;
+use Flarum\Install\Installation;
+use Flarum\Tests\Test\TestCase;
+use Illuminate\Database\Connectors\ConnectionFactory;
+
+class DefaultInstallationTest extends TestCase
+{
+ protected $isInstalled = false;
+
+ /**
+ * @test
+ */
+ public function allows_forum_installation()
+ {
+ if (file_exists(base_path('config.php'))) {
+ unlink(base_path('config.php'));
+ }
+
+ /** @var Installation $installation */
+ $installation = app(Installation::class);
+
+ $installation
+ ->debugMode(true)
+ ->baseUrl('http://flarum.local')
+ ->databaseConfig($this->getDatabaseConfiguration())
+ ->adminUser($this->getAdmin())
+ ->settings($this->getSettings())
+ ->build()->run();
+
+ $this->assertFileExists(base_path('config.php'));
+
+ $this->assertEquals(
+ $this->getDatabase()->table('users')->find(1)->username,
+ 'admin'
+ );
+ }
+
+ private function getDatabase()
+ {
+ $factory = new ConnectionFactory(app());
+
+ return $factory->make($this->getDatabaseConfiguration()->toArray());
+ }
+
+ private function getAdmin(): AdminUser
+ {
+ return new AdminUser(
+ 'admin',
+ 'password',
+ 'admin@example.com'
+ );
+ }
+
+ private function getSettings()
+ {
+ return [
+ 'forum_title' => 'Development Forum',
+ 'mail_driver' => 'log',
+ 'welcome_title' => 'Welcome to Development Forum',
+ ];
+ }
+}
diff --git a/framework/core/tests/Test/Concerns/CreatesForum.php b/framework/core/tests/Test/Concerns/CreatesForum.php
index a16b2269d..643afe5b1 100644
--- a/framework/core/tests/Test/Concerns/CreatesForum.php
+++ b/framework/core/tests/Test/Concerns/CreatesForum.php
@@ -19,9 +19,11 @@ use Flarum\Foundation\SiteInterface;
use Flarum\Foundation\UninstalledSite;
use Flarum\Http\Server;
use Flarum\Install\Console\DataProviderInterface;
-use Flarum\Install\Console\DefaultsDataProvider;
+use Flarum\Install\DatabaseConfig;
use Illuminate\Database\ConnectionInterface;
-use Illuminate\Database\Connectors\ConnectionFactory;
+use Illuminate\Database\Connectors\MySqlConnector;
+use Illuminate\Database\MySqlConnection;
+use Illuminate\Filesystem\Filesystem;
trait CreatesForum
{
@@ -72,26 +74,21 @@ trait CreatesForum
$this->app = $this->site->bootApp();
}
- protected function collectsConfiguration()
+ protected function getDatabaseConfiguration(): DatabaseConfig
{
- $this->configuration = new DefaultsDataProvider();
-
- $this->configuration->setDebugMode();
- $this->configuration->setSetting('mail_driver', 'log');
-
- $database = $this->configuration->getDatabaseConfiguration();
- $database['host'] = env('DB_HOST', $database['host']);
- $database['database'] = env('DB_DATABASE', $database['database']);
- $database['username'] = env('DB_USERNAME', $database['username']);
- $database['password'] = env('DB_PASSWORD', $database['password']);
- $database['prefix'] = env('DB_PREFIX', $database['prefix']);
- $this->configuration->setDatabaseConfiguration($database);
+ return new DatabaseConfig(
+ 'mysql',
+ env('DB_HOST', 'localhost'),
+ 3306,
+ env('DB_DATABASE', 'flarum'),
+ env('DB_USERNAME', 'root'),
+ env('DB_PASSWORD', ''),
+ env('DB_PREFIX', '')
+ );
}
protected function refreshApplication()
{
- $this->collectsConfiguration();
-
$this->seedsDatabase();
$this->createsSite();
@@ -108,23 +105,10 @@ trait CreatesForum
protected function getFlarumConfig()
{
- $dbConfig = $this->configuration->getDatabaseConfiguration();
-
return [
- 'debug' => $this->configuration->isDebugMode(),
- 'database' => [
- 'driver' => $dbConfig['driver'],
- 'host' => $dbConfig['host'],
- 'database' => $dbConfig['database'],
- 'username' => $dbConfig['username'],
- 'password' => $dbConfig['password'],
- 'charset' => 'utf8mb4',
- 'collation' => 'utf8mb4_unicode_ci',
- 'prefix' => $dbConfig['prefix'],
- 'port' => $dbConfig['port'],
- 'strict' => false
- ],
- 'url' => $this->configuration->getBaseUrl(),
+ 'debug' => true,
+ 'database' => $this->getDatabaseConfiguration()->toArray(),
+ 'url' => 'http://flarum.local',
'paths' => [
'api' => 'api',
'admin' => 'admin',
@@ -138,13 +122,13 @@ trait CreatesForum
return;
}
- $app = app(\Illuminate\Contracts\Foundation\Application::class);
+ $dbConfig = $this->getDatabaseConfiguration()->toArray();
- $factory = new ConnectionFactory($app);
- $db = $factory->make($this->configuration->getDatabaseConfiguration());
+ $pdo = (new MySqlConnector)->connect($dbConfig);
+ $db = new MySqlConnection($pdo, $dbConfig['database'], $dbConfig['prefix'], $dbConfig);
$repository = new DatabaseMigrationRepository($db, 'migrations');
- $migrator = new Migrator($repository, $db, app('files'));
+ $migrator = new Migrator($repository, $db, new Filesystem);
if (! $migrator->getRepository()->repositoryExists()) {
$migrator->getRepository()->createRepository();
diff --git a/framework/core/views/install/app.php b/framework/core/views/install/app.php
index 2e8601022..3e4f572de 100644
--- a/framework/core/views/install/app.php
+++ b/framework/core/views/install/app.php
@@ -129,30 +129,30 @@
animation-name: fadeIn;
}
- .Errors {
+ .Problems {
margin-top: 50px;
}
- .Errors .Error:first-child {
+ .Problems .Problem:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
- .Errors .Error:last-child {
+ .Problems .Problem:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
- .Error {
+ .Problem {
background: #EDF2F7;
margin: 0 0 1px;
padding: 20px 25px;
text-align: left;
}
- .Error-message {
+ .Problem-message {
font-size: 16px;
color: #3C5675;
font-weight: normal;
margin: 0;
}
- .Error-detail {
+ .Problem-detail {
font-size: 13px;
margin: 5px 0 0;
}
diff --git a/framework/core/views/install/errors.php b/framework/core/views/install/errors.php
deleted file mode 100644
index bd73aeb43..000000000
--- a/framework/core/views/install/errors.php
+++ /dev/null
@@ -1,14 +0,0 @@
-
Hold Up!
-
-These errors must be resolved before you can continue the installation. If you're having trouble, get help on the Flarum website.
-
-
diff --git a/framework/core/views/install/problems.php b/framework/core/views/install/problems.php
new file mode 100644
index 000000000..36b8d6252
--- /dev/null
+++ b/framework/core/views/install/problems.php
@@ -0,0 +1,14 @@
+Hold Up!
+
+These problems must be resolved before you can continue the installation. If you're having trouble, get help on the Flarum website.
+
+