mirror of
https://github.com/flarum/framework.git
synced 2025-02-21 09:53:27 +08:00
Merge pull request #1617 from flarum/fl/installer-cleanup
Split up the installer logic
This commit is contained in:
commit
6484dc4982
@ -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']);
|
||||
});
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
58
src/Install/AdminUser.php
Normal file
58
src/Install/AdminUser.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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.');
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -1,111 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
}
|
@ -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'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
'<error>Please fix the following errors before we can continue with the installation.</error>'
|
||||
);
|
||||
$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(),
|
||||
'<?php return '.var_export($config, true).';'
|
||||
);
|
||||
$pipeline
|
||||
->on('start', function (Step $step) {
|
||||
$this->output->write($step->getMessage().'...');
|
||||
})->on('end', function () {
|
||||
$this->output->write("<info>done</info>\n");
|
||||
})->on('fail', function () {
|
||||
$this->output->write("<error>failed</error>\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(
|
||||
'<error>Please fix the following problems before we can continue with the installation.</error>'
|
||||
);
|
||||
|
||||
$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('<comment>'.$error['detail'].'</comment>');
|
||||
if (isset($problem['detail'])) {
|
||||
$this->output->writeln('<comment>'.$problem['detail'].'</comment>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>$question</question> ", $default);
|
||||
|
||||
return $this->questionHelper->ask($this->input, $this->output, $question);
|
||||
}
|
||||
|
||||
protected function secret($question)
|
||||
private function secret($question)
|
||||
{
|
||||
$question = new Question("<question>$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("<error>$message</error>");
|
||||
$this->output->writeln('Please try again.');
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
100
src/Install/DatabaseConfig.php
Normal file
100
src/Install/DatabaseConfig.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
164
src/Install/Installation.php
Normal file
164
src/Install/Installation.php
Normal file
@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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';
|
||||
}
|
||||
}
|
@ -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))
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
108
src/Install/Pipeline.php
Normal file
108
src/Install/Pipeline.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
}
|
@ -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', []);
|
||||
}
|
||||
}
|
||||
|
@ -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.",
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
17
src/Install/ReversibleStep.php
Normal file
17
src/Install/ReversibleStep.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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();
|
||||
}
|
33
src/Install/Step.php
Normal file
33
src/Install/Step.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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();
|
||||
}
|
18
src/Install/StepFailed.php
Normal file
18
src/Install/StepFailed.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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
|
||||
{
|
||||
}
|
62
src/Install/Steps/ConnectToDatabase.php
Normal file
62
src/Install/Steps/ConnectToDatabase.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
53
src/Install/Steps/CreateAdminUser.php
Normal file
53
src/Install/Steps/CreateAdminUser.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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,
|
||||
]);
|
||||
}
|
||||
}
|
122
src/Install/Steps/EnableBundledExtensions.php
Normal file
122
src/Install/Steps/EnableBundledExtensions.php
Normal file
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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
|
||||
);
|
||||
}
|
||||
}
|
58
src/Install/Steps/PublishAssets.php
Normal file
58
src/Install/Steps/PublishAssets.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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";
|
||||
}
|
||||
}
|
60
src/Install/Steps/RunMigrations.php
Normal file
60
src/Install/Steps/RunMigrations.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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);
|
||||
}
|
||||
}
|
72
src/Install/Steps/StoreConfig.php
Normal file
72
src/Install/Steps/StoreConfig.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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,
|
||||
'<?php return '.var_export($this->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',
|
||||
];
|
||||
}
|
||||
}
|
80
src/Install/Steps/WriteSettings.php
Normal file
80
src/Install/Steps/WriteSettings.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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',
|
||||
];
|
||||
}
|
||||
}
|
18
src/Install/ValidationFailed.php
Normal file
18
src/Install/ValidationFailed.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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
|
||||
{
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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());
|
||||
}
|
||||
}
|
75
tests/Install/DefaultInstallationTest.php
Normal file
75
tests/Install/DefaultInstallationTest.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* 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',
|
||||
];
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -1,14 +0,0 @@
|
||||
<h2>Hold Up!</h2>
|
||||
|
||||
<p>These errors must be resolved before you can continue the installation. If you're having trouble, get help on the <a href="https://flarum.org/docs/install.html" target="_blank">Flarum website</a>.</p>
|
||||
|
||||
<div class="Errors">
|
||||
<?php foreach ($errors as $error): ?>
|
||||
<div class="Error">
|
||||
<h3 class="Error-message"><?php echo $error['message']; ?></h3>
|
||||
<?php if (! empty($error['detail'])): ?>
|
||||
<p class="Error-detail"><?php echo $error['detail']; ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
14
views/install/problems.php
Normal file
14
views/install/problems.php
Normal file
@ -0,0 +1,14 @@
|
||||
<h2>Hold Up!</h2>
|
||||
|
||||
<p>These problems must be resolved before you can continue the installation. If you're having trouble, get help on the <a href="https://flarum.org/docs/install.html" target="_blank">Flarum website</a>.</p>
|
||||
|
||||
<div class="Problems">
|
||||
<?php foreach ($problems as $problem): ?>
|
||||
<div class="Problem">
|
||||
<h3 class="Problem-message"><?php echo $problem['message']; ?></h3>
|
||||
<?php if (! empty($problem['detail'])): ?>
|
||||
<p class="Problem-detail"><?php echo $problem['detail']; ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
Loading…
x
Reference in New Issue
Block a user