File: /home/accemeff/vendor/craftcms/cms/src/console/controllers/SetupController.php
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/
namespace craft\console\controllers;
use Composer\Util\Platform;
use Craft;
use craft\config\DbConfig;
use craft\db\Connection;
use craft\errors\DbConnectException;
use craft\helpers\App;
use craft\helpers\Console;
use craft\helpers\FileHelper;
use craft\helpers\StringHelper;
use Seld\CliPrompt\CliPrompt;
use yii\base\InvalidConfigException;
use yii\console\Controller;
use yii\console\ExitCode;
/**
* Craft CMS setup installer.
*
* @author Pixel & Tonic, Inc. <support@pixelandtonic.com>
* @since 3.0
*/
class SetupController extends Controller
{
// Properties
// =========================================================================
/**
* @var string|null The database driver to use. Either 'mysql' for MySQL or 'pgsql' for PostgreSQL.
*/
public $driver;
/**
* @var string|null The database server name or IP address. Usually 'localhost' or '127.0.0.1'.
*/
public $server;
/**
* @var int|null The database server port. Defaults to 3306 for MySQL and 5432 for PostgreSQL.
*/
public $port = 0;
/**
* @var string|null The database username to connect with.
*/
public $user;
/**
* @var string|null The database password to connect with.
*/
public $password;
/**
* @var string|null The name of the database to select.
*/
public $database;
/**
* @var string|null The database schema to use (PostgreSQL only).
* @see https://www.postgresql.org/docs/8.2/static/ddl-schemas.html
*/
public $schema;
/**
* @var string|null The table prefix to add to all database tables. This can
* be no more than 5 characters, and must be all lowercase.
*/
public $tablePrefix;
// Public Methods
// =========================================================================
/**
* @inheritdoc
*/
public function options($actionID)
{
$options = parent::options($actionID);
if ($actionID === 'db-creds' || $actionID === 'db') {
$options[] = 'driver';
$options[] = 'server';
$options[] = 'port';
$options[] = 'user';
$options[] = 'password';
$options[] = 'database';
$options[] = 'schema';
$options[] = 'tablePrefix';
}
return $options;
}
/**
* Sets up all the things.
*
* @return int
*/
public function actionIndex(): int
{
if (!Craft::$app->getConfig()->getGeneral()->securityKey) {
$this->run('security-key');
$this->stdout(PHP_EOL);
}
if (!$this->interactive) {
return ExitCode::OK;
}
$this->run('db-creds');
if (Craft::$app->getIsInstalled()) {
$this->stdout("It looks like Craft is already installed, so we're done here." . PHP_EOL, Console::FG_YELLOW);
return ExitCode::OK;
}
if (!$this->confirm(PHP_EOL . 'Install Craft now?', true)) {
$this->stdout("You can install Craft from a browser once you've set up a web server, or by running this command:" . PHP_EOL, Console::FG_YELLOW);
$this->_outputCommand('install');
return ExitCode::OK;
}
$this->stdout(PHP_EOL);
return $this->module->runAction('install');
}
/**
* Called from the post-create-project-cmd Composer hook.
*
* @return int
*/
public function actionWelcome(): int
{
$craft = <<<EOD
______ .______ ___ _______ .___________.
/ || _ \ / \ | ____|| |
| ,----'| |_) | / ^ \ | |__ `---| |----`
| | | / / /_\ \ | __| | |
| `----.| |\ \----./ _____ \ | | | |
\______|| _| `._____/__/ \__\ |__| |__|
A N E W I N S T A L L
______ .___ ___. _______.
/ || \/ | / |
| ,----'| \ / | | (----`
| | | |\/| | \ \
| `----.| | | | .----) |
\______||__| |__| |_______/
EOD;
$this->stdout(str_replace("\n", PHP_EOL, $craft), Console::FG_YELLOW);
// Can't do anything interactive here (https://github.com/composer/composer/issues/3299)
$this->run('security-key');
$this->stdout(PHP_EOL . 'Welcome to Craft CMS! Run the following command if you want to setup Craft from your terminal:' . PHP_EOL);
$this->_outputCommand('setup');
return ExitCode::OK;
}
/**
* Generates a new security key and saves it in the .env file.
*
* @return int
*/
public function actionSecurityKey(): int
{
$this->stdout(PHP_EOL . 'Generating a security key ... ', Console::FG_YELLOW);
$key = Craft::$app->getSecurity()->generateRandomString();
if (!$this->_setEnvVar('SECURITY_KEY', $key)) {
return ExitCode::UNSPECIFIED_ERROR;
}
Craft::$app->getConfig()->getGeneral()->securityKey = $key;
$this->stdout("done ({$key})" . PHP_EOL, Console::FG_YELLOW);
return ExitCode::OK;
}
/**
* Stores new DB connection settings to the .env file.
*
* @return int
*/
public function actionDbCreds(): int
{
try {
$dbConfig = Craft::$app->getConfig()->getDb();
} catch (InvalidConfigException $e) {
$dbConfig = new DbConfig();
}
$firstTime = true;
$badUserCredentials = false;
top:
// driver
if ($this->driver) {
if (!in_array($this->driver, [DbConfig::DRIVER_MYSQL, DbConfig::DRIVER_PGSQL], true)) {
$this->stderr('--driver must be either "' . DbConfig::DRIVER_MYSQL . '" or "' . DbConfig::DRIVER_PGSQL . '".' . PHP_EOL, Console::FG_RED);
return ExitCode::USAGE;
}
$dbConfig->driver = $this->driver;
} else if ($this->interactive) {
$dbConfig->driver = $this->select('Which database driver are you using?', [
DbConfig::DRIVER_MYSQL => 'MySQL',
DbConfig::DRIVER_PGSQL => 'PostgreSQL',
]);
}
// server
if ($this->server) {
$server = $this->server;
} else {
$server = $this->prompt('Database server name or IP address:', [
'required' => true,
'default' => $dbConfig->server ?: '127.0.0.1',
]);
}
$dbConfig->server = strtolower($server);
// port
if ($this->port) {
$dbConfig->port = (int)$this->port;
} else {
if ($firstTime) {
$defaultPort = $dbConfig->driver === DbConfig::DRIVER_MYSQL ? 3306 : 5432;
} else {
$defaultPort = $dbConfig->port;
}
$dbConfig->port = (int)$this->prompt('Database port:', [
'required' => true,
'default' => $defaultPort,
'validator' => function(string $input): bool {
return is_numeric($input);
}
]);
}
userCredentials:
// user
if ($this->user) {
$dbConfig->user = $this->user;
} else {
$dbConfig->user = $this->prompt('Database username:', [
'default' => $dbConfig->user ?: null,
]);
}
// password
if ($this->password) {
$dbConfig->password = $this->password;
} else if ($this->interactive) {
$this->stdout('Database password: ');
$dbConfig->password = CliPrompt::hiddenPrompt(true);
}
if ($badUserCredentials) {
$badUserCredentials = false;
goto test;
}
// database
if ($this->database) {
$dbConfig->database = $this->database;
} else if ($this->interactive || $dbConfig->database) {
$dbConfig->database = $this->prompt('Database name:', [
'required' => true,
'default' => $dbConfig->database ?: null,
]);
} else {
$this->stderr('The --database option must be set.' . PHP_EOL, Console::FG_RED);
return ExitCode::USAGE;
}
// schema
if ($dbConfig->driver === DbConfig::DRIVER_PGSQL) {
if ($this->schema) {
$dbConfig->schema = $this->schema;
} else {
$dbConfig->schema = $this->prompt('Database schema:', [
'required' => true,
'default' => $dbConfig->schema ?: 'public',
]);
}
}
// tablePrefix
if ($this->tablePrefix) {
$tablePrefix = $this->tablePrefix;
} else {
$tablePrefix = $this->prompt('Database table prefix' . ($dbConfig->tablePrefix ? ' (type "none" for none)' : '') . ':', [
'default' => $dbConfig->tablePrefix ?: null,
'validator' => function(string $input): bool {
if (strlen(StringHelper::ensureRight($input, '_')) > 6) {
Console::stderr($this->ansiFormat('The table prefix must be 5 or less characters long.' . PHP_EOL, Console::FG_RED));
return false;
}
return true;
}
]);
}
if ($tablePrefix && $tablePrefix !== 'none') {
$dbConfig->tablePrefix = StringHelper::ensureRight($tablePrefix, '_');
} else {
$tablePrefix = $dbConfig->tablePrefix = '';
}
// Test the DB connection
$this->stdout('Testing database credentials ... ', Console::FG_YELLOW);
$originalServer = $dbConfig->server;
$originalPort = $dbConfig->port;
test:
$dbConfig->updateDsn();
/** @var Connection $db */
$db = Craft::createObject(App::dbConfig($dbConfig));
try {
$db->open();
} catch (DbConnectException $e) {
// Error codes:
// 7: Name or service not known (server)
// 7: could not connect to server (port)
// 7: password authentication failed (username)
// 7: no password supplied (password)
// 1045: Access denied for user (username, password)
// 1049: Unknown database (database)
// 2002: Connection timed out (server)
/** @var \PDOException $pdoException */
$pdoException = $e->getPrevious()->getPrevious() ?? $e->getPrevious() ?? $e;
$this->stderr('failed: ' . $pdoException->getMessage() . PHP_EOL, Console::FG_RED);
// Test some common issues
$message = $pdoException->getMessage();
if ($dbConfig->server === 'localhost' && $message === 'SQLSTATE[HY000] [2002] No such file or directory') {
// means the Unix socket doesn't exist - https://stackoverflow.com/a/22927341/1688568
// try 127.0.0.1 instead...
$this->stdout('Trying with 127.0.0.1 instead of localhost ... ', Console::FG_YELLOW);
$dbConfig->server = '127.0.0.1';
goto test;
}
if ($dbConfig->port === 3306 && $message === 'SQLSTATE[HY000] [2002] Connection refused') {
// try 8889 instead (default MAMP port)...
$this->stdout('Trying with port 8889 instead of 3306 ... ', Console::FG_YELLOW);
$dbConfig->port = 8889;
goto test;
}
if (
strpos($message, 'Access denied for user') !== false ||
strpos($message, 'no password supplied') !== false ||
strpos($message, 'password authentication failed for user') !== false
) {
$this->stdout('Try with a different username and/or password.' . PHP_EOL, Console::FG_YELLOW);
$badUserCredentials = true;
goto userCredentials;
}
if (!$this->interactive) {
return ExitCode::UNSPECIFIED_ERROR;
}
// Restore the original server/port values
$dbConfig->server = $originalServer;
$dbConfig->port = $originalPort;
$firstTime = false;
goto top;
}
Craft::$app->set('db', $db);
Craft::$app->setIsInstalled(null);
$this->stdout('success!' . PHP_EOL, Console::FG_GREEN);
$this->stdout('Saving database credentials to your .env file ... ', Console::FG_YELLOW);
if (
!$this->_setEnvVar('DB_DRIVER', $dbConfig->driver) ||
!$this->_setEnvVar('DB_SERVER', $dbConfig->server) ||
!$this->_setEnvVar('DB_PORT', $dbConfig->port) ||
!$this->_setEnvVar('DB_USER', $dbConfig->user) ||
!$this->_setEnvVar('DB_PASSWORD', $dbConfig->password) ||
!$this->_setEnvVar('DB_DATABASE', $dbConfig->database) ||
!$this->_setEnvVar('DB_SCHEMA', $dbConfig->schema) ||
!$this->_setEnvVar('DB_TABLE_PREFIX', $tablePrefix)
) {
return ExitCode::UNSPECIFIED_ERROR;
}
$this->stdout('done' . PHP_EOL, Console::FG_YELLOW);
return ExitCode::OK;
}
/**
* Alias for setup/db-creds.
*
* @return int
*/
public function actionDb(): int
{
return $this->actionDbCreds();
}
// Private Methods
// =========================================================================
/**
* Outputs a terminal command.
*
* @param string $command
*/
private function _outputCommand(string $command)
{
$script = FileHelper::normalizePath(Craft::$app->getRequest()->getScriptFile());
if (!Platform::isWindows() && ($home = getenv('HOME')) !== false) {
$home = FileHelper::normalizePath($home);
if (strpos($script, $home . DIRECTORY_SEPARATOR) === 0) {
$script = '~' . substr($script, strlen($home));
}
}
$this->stdout(PHP_EOL . ' ' . $script . ' ' . $command . PHP_EOL . PHP_EOL);
}
/**
* Sets an environment variable value in the project's .env file.
*
* @param $name
* @param $value
* @return bool
*/
private function _setEnvVar($name, $value): bool
{
$configService = Craft::$app->getConfig();
$path = $configService->getDotEnvPath();
if (!file_exists($path)) {
if ($this->confirm(PHP_EOL . "A .env file doesn't exist at {$path}. Would you like to create one?", true)) {
try {
FileHelper::writeToFile($path, '');
} catch (\Throwable $e) {
$this->stderr("Unable to create {$path}: {$e->getMessage()}" . PHP_EOL, Console::FG_RED);
return false;
}
$this->stdout("{$path} created. Note you still need to set up PHP dotenv for its values to take effect." . PHP_EOL, Console::FG_YELLOW);
} else {
$this->stdout(PHP_EOL . 'Action aborted.' . PHP_EOL, Console::FG_YELLOW);
return false;
}
}
try {
$configService->setDotEnvVar($name, $value);
} catch (\Throwable $e) {
$this->stderr("Unable to set {$name} on {$path}: {$e->getMessage()}" . PHP_EOL, Console::FG_RED);
return false;
}
return true;
}
}