web-base/Core/Documents/Install.class.php

982 lines
32 KiB
PHP
Raw Normal View History

2020-02-09 23:02:19 +01:00
<?php
2022-11-18 18:06:46 +01:00
namespace Core\Documents {
2020-04-03 15:56:04 +02:00
use Documents\Install\InstallBody;
use Documents\Install\InstallHead;
2022-11-18 18:06:46 +01:00
use Core\Elements\HtmlDocument;
use Core\Objects\Router\Router;
2020-04-03 15:56:04 +02:00
2021-12-08 16:53:43 +01:00
class Install extends HtmlDocument {
2022-06-01 12:28:50 +02:00
public function __construct(Router $router) {
parent::__construct($router, InstallHead::class, InstallBody::class);
2020-04-02 21:39:02 +02:00
$this->databaseRequired = false;
2020-02-09 23:02:19 +01:00
}
}
}
namespace Documents\Install {
2022-11-18 18:06:46 +01:00
use Core\Configuration\Configuration;
use Core\Configuration\CreateDatabase;
use Core\Driver\SQL\SQL;
use Core\Elements\Body;
use Core\Elements\Head;
use Core\Elements\Link;
use Core\Elements\Script;
use Core\External\PHPMailer\Exception;
use Core\External\PHPMailer\PHPMailer;
use Core\Objects\ConnectionData;
2022-11-20 17:13:53 +01:00
use Core\Objects\DatabaseEntity\Group;
2024-05-04 12:23:14 +02:00
use Core\Objects\DatabaseEntity\User;
2020-04-03 15:56:04 +02:00
class InstallHead extends Head {
2020-02-09 23:02:19 +01:00
public function __construct($document) {
parent::__construct($document);
}
protected function initSources() {
$this->loadJQuery();
$this->loadBootstrap();
$this->loadFontawesome();
2020-04-03 15:56:04 +02:00
$this->addJS(Script::CORE);
$this->addCSS(Link::CORE);
$this->addJS(Script::INSTALL);
2020-02-09 23:02:19 +01:00
}
2021-04-02 21:58:06 +02:00
protected function initMetas(): array {
2024-05-04 12:23:14 +02:00
return [
['name' => 'viewport', 'content' => 'width=device-width, initial-scale=1.0'],
['name' => 'format-detection', 'content' => 'telephone=yes'],
['charset' => 'utf-8'],
["http-equiv" => 'expires', 'content' => '0'],
["name" => 'robots', 'content' => 'noarchive'],
];
2020-02-09 23:02:19 +01:00
}
2021-04-02 21:58:06 +02:00
protected function initRawFields(): array {
2024-05-04 12:23:14 +02:00
return [];
2020-02-09 23:02:19 +01:00
}
2021-04-02 21:58:06 +02:00
protected function initTitle(): string {
2020-02-09 23:02:19 +01:00
return "WebBase - Installation";
}
}
2020-04-03 15:56:04 +02:00
class InstallBody extends Body {
2020-02-09 23:02:19 +01:00
// Status enum
const NOT_STARTED = 0;
const PENDING = 1;
2020-04-03 15:56:04 +02:00
const SUCCESSFUL = 2;
2020-02-09 23:02:19 +01:00
const ERROR = 3;
// Step enum
2020-04-03 15:56:04 +02:00
const CHECKING_REQUIREMENTS = 1;
2022-02-20 23:17:17 +01:00
const INSTALL_DEPENDENCIES = 2;
const DATABASE_CONFIGURATION = 3;
const CREATE_USER = 4;
const ADD_MAIL_SERVICE = 5;
const FINISH_INSTALLATION = 6;
2020-02-09 23:02:19 +01:00
//
2020-04-03 15:56:04 +02:00
private string $errorString;
private int $currentStep;
private array $steps;
2020-02-09 23:02:19 +01:00
function __construct($document) {
parent::__construct($document);
2020-02-09 23:30:26 +01:00
$this->errorString = "";
2020-04-03 15:56:04 +02:00
$this->currentStep = InstallBody::CHECKING_REQUIREMENTS;
2024-05-04 12:23:14 +02:00
$this->steps = [];
2020-02-09 23:02:19 +01:00
}
2021-04-02 21:58:06 +02:00
private function getParameter($name): ?string {
if (isset($_REQUEST[$name]) && is_string($_REQUEST[$name])) {
2020-02-09 23:02:19 +01:00
return trim($_REQUEST[$name]);
}
return NULL;
}
private function yarnInstall(string $reactDir): array {
$fds = [
"1" => ["pipe", "w"],
"2" => ["pipe", "w"],
];
$proc = proc_open("yarn install --frozen-lockfile --non-interactive", $fds, $pipes, $reactDir);
$output = stream_get_contents($pipes[1]) . stream_get_contents($pipes[2]);
$status = proc_close($proc);
return [$status, $output];
}
private function yarnBuild(string $reactDir): array {
$fds = [
"1" => ["pipe", "w"],
"2" => ["pipe", "w"],
];
$proc = proc_open("yarn run build", $fds, $pipes, $reactDir);
$output = stream_get_contents($pipes[1]) . stream_get_contents($pipes[2]);
$status = proc_close($proc);
return [$status, $output];
}
2022-02-21 00:32:40 +01:00
private function composerInstall(bool $dryRun = false): array {
$command = "composer install";
2022-02-20 23:17:17 +01:00
if ($dryRun) {
$command .= " --dry-run";
}
$fds = [
"1" => ["pipe", "w"],
"2" => ["pipe", "w"],
];
$dir = $this->getExternalDirectory();
$env = null;
if (!getenv("HOME")) {
$env = ["COMPOSER_HOME" => $dir];
}
$proc = proc_open($command, $fds, $pipes, $dir, $env);
$output = stream_get_contents($pipes[1]) . stream_get_contents($pipes[2]);
$status = proc_close($proc);
return [$status, $output];
}
2022-02-21 00:32:40 +01:00
private function getExternalDirectory(bool $absolute = true): string {
if ($absolute) {
2022-11-18 18:06:46 +01:00
return implode(DIRECTORY_SEPARATOR, [WEBROOT, "Core", "External"]);
2022-02-21 00:32:40 +01:00
} else {
2022-11-18 18:06:46 +01:00
return implode(DIRECTORY_SEPARATOR, ["Core", "External"]);
2022-02-21 00:32:40 +01:00
}
2022-02-20 23:17:17 +01:00
}
2021-04-02 21:58:06 +02:00
private function getCurrentStep(): int {
2020-02-09 23:02:19 +01:00
2021-04-02 21:58:06 +02:00
if (!$this->checkRequirements()["success"]) {
2020-04-03 15:56:04 +02:00
return self::CHECKING_REQUIREMENTS;
2020-02-09 23:02:19 +01:00
}
2024-05-04 15:07:24 +02:00
// TODO: also check the presence of react dist?
2022-02-20 23:17:17 +01:00
$externalDir = $this->getExternalDirectory();
2022-02-21 00:32:40 +01:00
$autoload = implode(DIRECTORY_SEPARATOR, [$externalDir, "vendor", "autoload.php"]);
if (!is_file($autoload)) {
2022-02-20 23:17:17 +01:00
return self::INSTALL_DEPENDENCIES;
} else {
2022-02-21 00:32:40 +01:00
list ($status, $output) = $this->composerInstall(true);
2022-02-20 23:17:17 +01:00
if ($status !== 0) {
2022-02-21 00:32:40 +01:00
$this->errorString = "Error executing 'composer install --dry-run'. Please verify that the command succeeds locally and then try again. Status Code: $status, Output: $output";
2022-02-20 23:17:17 +01:00
return self::CHECKING_REQUIREMENTS;
} else {
2022-02-21 00:32:40 +01:00
if (!contains($output, "Nothing to install, update or remove")) {
2022-02-20 23:17:17 +01:00
return self::INSTALL_DEPENDENCIES;
}
}
}
2022-06-20 19:52:31 +02:00
$context = $this->getDocument()->getContext();
$config = $context->getConfig();
2020-02-09 23:02:19 +01:00
// Check if database configuration exists
2021-04-02 21:58:06 +02:00
if (!$config->getDatabase()) {
2020-02-09 23:02:19 +01:00
return self::DATABASE_CONFIGURATION;
}
2022-06-20 19:52:31 +02:00
$sql = $context->getSQL();
if (!$sql || !$sql->isConnected() || !$sql->tableExists(User::getHandler($sql)->getTableName())) {
2020-06-25 16:54:58 +02:00
return self::DATABASE_CONFIGURATION;
}
2024-05-04 12:23:14 +02:00
$userCount = User::count($sql);
if ($userCount === FALSE) {
2020-04-02 00:02:51 +02:00
return self::DATABASE_CONFIGURATION;
} else {
2024-05-04 12:23:14 +02:00
if ($userCount > 0) {
2020-02-10 00:52:25 +01:00
$step = self::ADD_MAIL_SERVICE;
} else {
return self::CREATE_USER;
2020-02-09 23:02:19 +01:00
}
}
2020-06-25 16:54:58 +02:00
if ($step === self::ADD_MAIL_SERVICE) {
2022-11-18 18:06:46 +01:00
$req = new \Core\API\Settings\Get($context);
2024-05-04 12:23:14 +02:00
$success = $req->execute(["key" => "^mail_enabled$"]);
2020-06-25 16:54:58 +02:00
if (!$success) {
$this->errorString = $req->getLastError();
return self::DATABASE_CONFIGURATION;
} else if (isset($req->getResult()["settings"]["mail_enabled"])) {
$step = self::FINISH_INSTALLATION;
2022-11-18 18:06:46 +01:00
$req = new \Core\API\Settings\Set($context);
$success = $req->execute(["settings" => ["installation_completed" => true]]);
2020-04-02 21:19:06 +02:00
if (!$success) {
$this->errorString = $req->getLastError();
}
2020-02-09 23:30:26 +01:00
}
2020-02-09 23:02:19 +01:00
}
return $step;
}
2022-02-20 23:17:17 +01:00
private function command_exist(string $cmd): bool {
$return = shell_exec(sprintf("which %s 2>/dev/null", escapeshellarg($cmd)));
return !empty($return);
}
2021-04-02 21:58:06 +02:00
private function checkRequirements(): array {
2020-02-09 23:02:19 +01:00
2020-02-09 23:30:26 +01:00
$msg = $this->errorString;
2020-02-09 23:02:19 +01:00
$success = true;
2024-05-04 12:23:14 +02:00
$failedRequirements = [];
2020-02-09 23:02:19 +01:00
2024-04-22 12:03:24 +02:00
$requiredDirectories = [
"/Site/Cache",
"/Site/Logs",
"/Site/Configuration",
"/Core/External/vendor",
"/files/uploaded",
2024-05-04 15:07:24 +02:00
"/react",
2024-04-22 12:03:24 +02:00
];
$nonWritableDirectories = [];
foreach ($requiredDirectories as $directory) {
if (!is_writeable(WEBROOT . $directory)) {
$nonWritableDirectories[] = $directory;
}
}
if (!empty($nonWritableDirectories)) {
$currentUser = getCurrentUsername();
if (function_exists("posix_getuid")) {
$currentUser .= " (uid: " . posix_getuid() . ")";
}
$failedRequirements[] = "One or more directories are not writable. " .
"Make sure the current user $currentUser has write-access to following locations:" .
$this->createUnorderedList($nonWritableDirectories);
2020-04-02 21:57:06 +02:00
$success = false;
2020-02-09 23:02:19 +01:00
}
2024-04-23 20:14:32 +02:00
if (!class_exists("Redis")) {
$failedRequirements[] = "<b>redis</b> extension is not installed.";
$success = false;
}
2022-02-20 23:17:17 +01:00
if (!function_exists("yaml_emit")) {
$failedRequirements[] = "<b>YAML</b> extension is not installed.";
$success = false;
}
2024-04-22 12:41:15 +02:00
$requiredVersion = "8.2";
2022-08-20 22:04:09 +02:00
if (version_compare(PHP_VERSION, $requiredVersion, '<')) {
$failedRequirements[] = "PHP Version <b>>= $requiredVersion</b> is required. Got: <b>" . PHP_VERSION . "</b>";
2021-04-02 21:58:06 +02:00
$success = false;
2020-02-09 23:02:19 +01:00
}
2022-02-20 23:17:17 +01:00
if (!$this->command_exist("composer")) {
$failedRequirements[] = "<b>Composer</b> is not installed or cannot be found.";
$success = false;
}
if (!$this->command_exist("yarn")) {
$failedRequirements[] = "<b>Yarn</b> is not installed or cannot be found.";
$success = false;
}
2021-04-02 21:58:06 +02:00
if (!$success) {
2020-02-09 23:02:19 +01:00
$msg = "The following requirements failed the check:<br>" .
$this->createUnorderedList($failedRequirements);
2020-04-02 22:25:13 +02:00
$this->errorString = $msg;
2020-02-09 23:02:19 +01:00
}
2024-04-22 12:41:15 +02:00
return ["success" => $success, "msg" => $msg];
2020-02-09 23:02:19 +01:00
}
2022-02-20 23:17:17 +01:00
private function installDependencies(): array {
2022-02-21 00:32:40 +01:00
list ($status, $output) = $this->composerInstall();
if ($status === 0) {
$reactDir = implode(DIRECTORY_SEPARATOR, [WEBROOT, "react"]);
list ($status, $output) = $this->yarnInstall($reactDir);
if ($status === 0) {
list ($status, $output) = $this->yarnBuild($reactDir);
}
}
2022-02-20 23:17:17 +01:00
return ["success" => $status === 0, "msg" => $output];
}
2021-04-02 21:58:06 +02:00
private function databaseConfiguration(): array {
2020-02-09 23:02:19 +01:00
$host = $this->getParameter("host");
$port = $this->getParameter("port");
$username = $this->getParameter("username");
$password = $this->getParameter("password");
$database = $this->getParameter("database");
2020-04-02 00:02:51 +02:00
$type = $this->getParameter("type");
2022-02-20 23:17:17 +01:00
$encoding = $this->getParameter("encoding") ?? "UTF8";
2020-02-09 23:02:19 +01:00
$success = true;
2024-05-04 12:23:14 +02:00
$missingInputs = [];
2022-02-20 23:17:17 +01:00
if (empty($host)) {
2020-02-09 23:02:19 +01:00
$success = false;
$missingInputs[] = "Host";
}
2022-02-20 23:17:17 +01:00
if (empty($port)) {
2020-02-09 23:02:19 +01:00
$success = false;
$missingInputs[] = "Port";
}
2022-02-20 23:17:17 +01:00
if (empty($username)) {
2020-02-09 23:02:19 +01:00
$success = false;
$missingInputs[] = "Username";
}
2021-04-02 21:58:06 +02:00
if (is_null($password)) {
2020-02-09 23:02:19 +01:00
$success = false;
$missingInputs[] = "Password";
}
2022-02-20 23:17:17 +01:00
if (empty($database)) {
2020-02-09 23:02:19 +01:00
$success = false;
$missingInputs[] = "Database";
}
2022-02-20 23:17:17 +01:00
if (empty($type)) {
2020-04-02 00:02:51 +02:00
$success = false;
$missingInputs[] = "Type";
}
2024-04-22 12:41:15 +02:00
$supportedTypes = ["mysql", "postgres"];
2021-04-02 21:58:06 +02:00
if (!$success) {
2020-02-09 23:02:19 +01:00
$msg = "Please fill out the following inputs:<br>" .
$this->createUnorderedList($missingInputs);
2021-04-02 21:58:06 +02:00
} else if (!is_numeric($port) || ($port = intval($port)) < 1 || $port > 65535) {
2020-02-09 23:02:19 +01:00
$msg = "Port must be in range of 1-65535.";
$success = false;
2021-04-02 21:58:06 +02:00
} else if (!in_array($type, $supportedTypes)) {
2020-04-02 00:02:51 +02:00
$msg = "Unsupported database type. Must be one of: " . implode(", ", $supportedTypes);
$success = false;
2020-02-09 23:02:19 +01:00
} else {
2020-04-03 15:56:04 +02:00
$connectionData = new ConnectionData($host, $port, $username, $password);
2020-02-09 23:02:19 +01:00
$connectionData->setProperty('database', $database);
2020-04-02 00:02:51 +02:00
$connectionData->setProperty('encoding', $encoding);
$connectionData->setProperty('type', $type);
2024-04-23 20:14:32 +02:00
$connectionData->setProperty('isDocker', isDocker());
2020-04-03 15:56:04 +02:00
$sql = SQL::createConnection($connectionData);
2020-04-02 00:02:51 +02:00
$success = false;
2021-04-02 21:58:06 +02:00
if (is_string($sql)) {
2020-04-03 15:56:04 +02:00
$msg = "Error connecting to database: $sql";
2021-04-02 21:58:06 +02:00
} else if (!$sql->isConnected()) {
2020-04-04 15:11:38 +02:00
if (!$sql->checkRequirements()) {
2020-04-02 01:48:46 +02:00
$driverName = $sql->getDriverName();
$installLink = "https://www.php.net/manual/en/$driverName.setup.php";
$link = $this->createExternalLink($installLink);
$msg = "$driverName is not enabled yet. See: $link";
} else {
$msg = "Error connecting to database:<br>" . $sql->getLastError();
}
2020-02-09 23:02:19 +01:00
} else {
2020-04-02 00:02:51 +02:00
$msg = "";
$success = true;
2020-04-03 15:56:04 +02:00
$queries = CreateDatabase::createQueries($sql);
2024-03-27 14:12:01 +01:00
try {
$sql->startTransaction();
foreach ($queries as $query) {
2022-02-20 18:31:54 +01:00
if (!$query->execute()) {
$msg = "Error creating tables: " . $sql->getLastError();
$success = false;
}
2024-03-27 14:12:01 +01:00
2022-02-20 18:31:54 +01:00
if (!$success) {
2024-03-27 14:12:01 +01:00
break;
2022-02-20 18:31:54 +01:00
}
}
2024-03-27 14:12:01 +01:00
} finally {
2022-02-20 18:31:54 +01:00
if (!$success) {
2024-03-27 14:12:01 +01:00
$sql->rollback();
} else {
$sql->commit();
2020-02-09 23:02:19 +01:00
}
}
2022-06-20 19:52:31 +02:00
if ($success) {
$context = $this->getDocument()->getContext();
$config = $context->getConfig();
2022-11-18 18:06:46 +01:00
if (Configuration::create(\Site\Configuration\Database::class, $connectionData) === false) {
2022-05-31 16:14:49 +02:00
$success = false;
2022-06-20 19:52:31 +02:00
$msg = "Unable to write database file";
2022-05-31 16:14:49 +02:00
} else {
2022-06-20 19:52:31 +02:00
$config->setDatabase($connectionData);
if (!$context->initSQL()) {
2022-05-31 16:14:49 +02:00
$success = false;
2022-06-20 19:52:31 +02:00
$msg = "Unable to verify database connection after installation";
} else {
2022-11-18 18:06:46 +01:00
$req = new \Core\API\Routes\GenerateCache($context);
2022-06-20 19:52:31 +02:00
if (!$req->execute()) {
$success = false;
$msg = "Unable to write route file: " . $req->getLastError();
}
2022-05-31 16:14:49 +02:00
}
}
2020-02-09 23:02:19 +01:00
}
2020-04-02 00:02:51 +02:00
$sql->close();
}
2020-02-09 23:02:19 +01:00
}
2024-05-04 12:23:14 +02:00
return ["success" => $success, "msg" => $msg];
2020-02-09 23:02:19 +01:00
}
2021-04-02 21:58:06 +02:00
private function createUser(): array {
2020-02-09 23:02:19 +01:00
2022-06-20 19:52:31 +02:00
$context = $this->getDocument()->getContext();
2021-04-02 21:58:06 +02:00
if ($this->getParameter("prev") === "true") {
2024-05-04 12:23:14 +02:00
// TODO: drop the previous database here?
/*
2024-05-04 12:23:14 +02:00
$success = $context->getConfig()->delete("\\Site\\Configuration\\Database");
2020-02-09 23:02:19 +01:00
$msg = $success ? "" : error_get_last();
2024-05-04 12:23:14 +02:00
return ["success" => $success, "msg" => $msg];
*/
return ["success" => false, "msg" => "Cannot revert this installation step."];
2020-02-09 23:02:19 +01:00
}
$username = $this->getParameter("username");
$password = $this->getParameter("password");
$confirmPassword = $this->getParameter("confirmPassword");
2020-04-04 01:15:59 +02:00
$email = $this->getParameter("email") ?? "";
2020-02-09 23:02:19 +01:00
$success = true;
2024-05-04 12:23:14 +02:00
$missingInputs = [];
2020-02-09 23:02:19 +01:00
2022-02-20 23:17:17 +01:00
if (empty($username)) {
2020-02-09 23:02:19 +01:00
$success = false;
$missingInputs[] = "Username";
}
2022-02-20 23:17:17 +01:00
if (empty($password)) {
2020-02-09 23:02:19 +01:00
$success = false;
$missingInputs[] = "Password";
}
2022-02-20 23:17:17 +01:00
if (empty($confirmPassword)) {
2020-02-09 23:02:19 +01:00
$success = false;
$missingInputs[] = "Confirm Password";
}
2021-04-02 21:58:06 +02:00
if (!$success) {
2020-02-09 23:02:19 +01:00
$msg = "Please fill out the following inputs:<br>" .
$this->createUnorderedList($missingInputs);
} else {
2022-11-18 18:06:46 +01:00
$req = new \Core\API\User\Create($context);
2024-05-04 12:23:14 +02:00
$success = $req->execute([
2020-06-23 18:40:43 +02:00
'username' => $username,
'email' => $email,
'password' => $password,
'confirmPassword' => $confirmPassword,
2022-11-20 17:13:53 +01:00
'groups' => [Group::ADMIN]
2024-05-04 12:23:14 +02:00
]);
2020-06-23 18:40:43 +02:00
$msg = $req->getLastError();
2020-02-09 23:02:19 +01:00
}
2024-05-04 12:23:14 +02:00
return ["msg" => $msg, "success" => $success];
2020-02-09 23:02:19 +01:00
}
2021-04-02 21:58:06 +02:00
private function addMailService(): array {
2020-02-09 23:02:19 +01:00
2022-06-20 19:52:31 +02:00
$context = $this->getDocument()->getContext();
2021-04-02 21:58:06 +02:00
if ($this->getParameter("prev") === "true") {
2022-06-20 19:52:31 +02:00
$sql = $context->getSQL();
2020-04-02 00:02:51 +02:00
$success = $sql->delete("User")->execute();
$msg = $sql->getLastError();
2024-05-04 12:23:14 +02:00
return ["success" => $success, "msg" => $msg];
2020-02-09 23:02:19 +01:00
}
2021-04-02 21:58:06 +02:00
if ($this->getParameter("skip") === "true") {
2022-11-18 18:06:46 +01:00
$req = new \Core\API\Settings\Set($context);
$success = $req->execute(["settings" => ["mail_enabled" => false]]);
2020-06-25 16:54:58 +02:00
$msg = $req->getLastError();
2020-02-09 23:02:19 +01:00
} else {
$address = $this->getParameter("address");
$port = $this->getParameter("port");
$username = $this->getParameter("username");
$password = $this->getParameter("password");
$success = true;
2024-05-04 12:23:14 +02:00
$missingInputs = [];
2022-06-20 19:52:31 +02:00
if (empty($address)) {
2020-02-09 23:02:19 +01:00
$success = false;
$missingInputs[] = "SMTP Address";
}
2022-06-20 19:52:31 +02:00
if (empty($port)) {
2020-02-09 23:02:19 +01:00
$success = false;
$missingInputs[] = "Port";
}
2022-06-20 19:52:31 +02:00
if (empty($username)) {
2020-02-09 23:02:19 +01:00
$success = false;
$missingInputs[] = "Username";
}
2021-04-02 21:58:06 +02:00
if (is_null($password)) {
2020-02-09 23:02:19 +01:00
$success = false;
$missingInputs[] = "Password";
}
2021-04-02 21:58:06 +02:00
if (!$success) {
2020-02-09 23:02:19 +01:00
$msg = "Please fill out the following inputs:<br>" .
$this->createUnorderedList($missingInputs);
2021-04-02 21:58:06 +02:00
} else if (!is_numeric($port) || ($port = intval($port)) < 1 || $port > 65535) {
2020-02-09 23:02:19 +01:00
$msg = "Port must be in range of 1-65535.";
$success = false;
} else {
$success = false;
2020-04-03 15:56:04 +02:00
$mail = new PHPMailer(true);
2020-02-09 23:02:19 +01:00
$mail->IsSMTP();
$mail->SMTPAuth = true;
$mail->Username = $username;
$mail->Password = $password;
$mail->Host = $address;
$mail->Port = $port;
$mail->SMTPSecure = 'tls';
$mail->Timeout = 10;
try {
$success = $mail->SmtpConnect();
2021-04-02 21:58:06 +02:00
if (!$success) {
2020-02-09 23:02:19 +01:00
$error = empty($mail->ErrorInfo) ? "Unknown Error" : $mail->ErrorInfo;
$msg = "Could not connect to SMTP Server: $error";
} else {
$success = true;
$msg = "";
$mail->smtpClose();
}
2021-04-02 21:58:06 +02:00
} catch (Exception $error) {
2020-02-09 23:02:19 +01:00
$msg = "Could not connect to SMTP Server: " . $error->errorMessage();
}
2021-04-02 21:58:06 +02:00
if ($success) {
2022-11-18 18:06:46 +01:00
$req = new \Core\API\Settings\Set($context);
$success = $req->execute(["settings" => [
"mail_enabled" => true,
"mail_host" => $address,
"mail_port" => $port,
"mail_username" => $username,
"mail_password" => $password,
]]);
2020-06-25 16:54:58 +02:00
$msg = $req->getLastError();
2020-02-09 23:02:19 +01:00
}
}
}
2024-05-04 12:23:14 +02:00
return ["success" => $success, "msg" => $msg];
2020-02-09 23:02:19 +01:00
}
2021-04-02 21:58:06 +02:00
private function performStep(): array {
2024-05-04 12:23:14 +02:00
return match ($this->currentStep) {
self::CHECKING_REQUIREMENTS => $this->checkRequirements(),
self::INSTALL_DEPENDENCIES => $this->installDependencies(),
self::DATABASE_CONFIGURATION => $this->databaseConfiguration(),
self::CREATE_USER => $this->createUser(),
self::ADD_MAIL_SERVICE => $this->addMailService(),
default => [
"success" => false,
"msg" => "Invalid step number"
],
};
2020-02-09 23:02:19 +01:00
}
2024-05-04 12:23:14 +02:00
private function createProgressSidebar(): array {
$items = [];
2021-04-02 21:58:06 +02:00
foreach ($this->steps as $num => $step) {
2020-02-09 23:02:19 +01:00
$title = $step["title"];
$status = $step["status"];
2021-04-02 21:58:06 +02:00
switch ($status) {
2020-02-09 23:02:19 +01:00
case self::PENDING:
2021-04-02 21:58:06 +02:00
$statusIcon = $this->createIcon("spinner");
$statusText = "Loading…";
2020-02-09 23:02:19 +01:00
$statusColor = "muted";
break;
2020-04-03 15:56:04 +02:00
case self::SUCCESSFUL:
2021-04-02 21:58:06 +02:00
$statusIcon = $this->createIcon("check-circle");
$statusText = "Successful";
2020-02-09 23:02:19 +01:00
$statusColor = "success";
break;
case self::ERROR:
2021-04-02 21:58:06 +02:00
$statusIcon = $this->createIcon("times-circle");
$statusText = "Failed";
2020-02-09 23:02:19 +01:00
$statusColor = "danger";
break;
case self::NOT_STARTED:
default:
2020-06-25 16:54:58 +02:00
$statusIcon = $this->createIcon("circle", "far");
2020-02-09 23:02:19 +01:00
$statusText = "Pending";
$statusColor = "muted";
break;
}
2024-05-04 12:23:14 +02:00
$attr = ["class" => "list-group-item d-flex justify-content-between lh-condensed"];
if ($num == $this->currentStep) {
$attr["id"] = "currentStep";
}
$items[] = html_tag("li", $attr, [
html_tag("div", [], [
html_tag("h6", ["class" => "my-0"], $title),
html_tag("small", ["class" => "text-$statusColor"], $statusText),
], false),
html_tag("span", ["class" => "text-$statusColor"], $statusIcon, false)
], false);
2020-02-09 23:02:19 +01:00
}
2024-05-04 12:23:14 +02:00
return $items;
2020-02-09 23:02:19 +01:00
}
2021-04-02 21:58:06 +02:00
private function createFormItem($formItem, $inline = false): string {
2020-02-09 23:02:19 +01:00
$title = $formItem["title"];
2021-04-02 21:58:06 +02:00
$name = $formItem["name"];
$type = $formItem["type"];
2020-02-09 23:02:19 +01:00
2024-05-04 12:23:14 +02:00
$attributes = [
2020-02-09 23:02:19 +01:00
"name" => $name,
"id" => $name,
2020-04-02 00:02:51 +02:00
"class" => "form-control"
2024-05-04 12:23:14 +02:00
];
2020-02-09 23:02:19 +01:00
2021-04-02 21:58:06 +02:00
if (isset($formItem["required"]) && $formItem["required"]) {
2020-02-09 23:02:19 +01:00
$attributes["required"] = "";
}
2020-04-02 00:02:51 +02:00
if ($type !== "select") {
$attributes["type"] = $type;
2021-04-02 21:58:06 +02:00
if (isset($formItem["value"]) && $formItem["value"]) {
2020-04-02 00:02:51 +02:00
$attributes["value"] = $formItem["value"];
}
2021-04-02 21:58:06 +02:00
if ($type === "number") {
if (isset($formItem["min"]) && is_numeric($formItem["min"]))
2020-04-02 00:02:51 +02:00
$attributes["min"] = $formItem["min"];
2021-04-02 21:58:06 +02:00
if (isset($formItem["max"]) && is_numeric($formItem["max"]))
2020-04-02 00:02:51 +02:00
$attributes["max"] = $formItem["max"];
2021-04-02 21:58:06 +02:00
if (isset($formItem["step"]) && is_numeric($formItem["step"]))
2020-04-02 00:02:51 +02:00
$attributes["step"] = $formItem["step"];
2021-04-07 19:43:22 +02:00
} else {
if (isset($formItem["default"])) {
$attributes["value"] = $formItem["default"];
}
2020-04-02 00:02:51 +02:00
}
2020-02-09 23:02:19 +01:00
}
2020-04-02 00:02:51 +02:00
if ($type === "select") {
2024-05-04 12:23:14 +02:00
$items = $formItem["items"] ?? [];
2022-06-20 19:52:31 +02:00
$options = [];
2021-04-02 21:58:06 +02:00
foreach ($items as $key => $val) {
2022-06-20 19:52:31 +02:00
$options[] = html_tag_ex("option", ["value" => $key], $val, true, false);
2020-04-02 00:02:51 +02:00
}
2020-02-09 23:02:19 +01:00
2022-06-20 19:52:31 +02:00
$element = html_tag_ex("select", $attributes, $options, false);
2020-02-09 23:02:19 +01:00
} else {
2022-06-20 19:52:31 +02:00
$element = html_tag_short("input", $attributes);
2020-02-09 23:02:19 +01:00
}
2022-06-20 19:52:31 +02:00
$label = html_tag_ex("label", ["for" => $name], $title, true, false);
$className = ($inline ? "col-md-6 mb-3" : "d-block my-3");
return html_tag_ex("div", ["class" => $className], $label . $element, false);
2020-02-09 23:02:19 +01:00
}
2024-05-04 12:23:14 +02:00
private function createProgressMainView(): string {
2020-02-09 23:02:19 +01:00
2024-04-23 20:14:32 +02:00
if (isDocker()) {
2024-05-04 12:23:14 +02:00
$env = loadEnv();
2024-04-23 20:14:32 +02:00
$defaultHost = "db";
$defaultUsername = "root";
$defaultDatabase = "webbase";
2024-05-04 12:23:14 +02:00
$defaultPassword = $env && array_key_exists("MYSQL_ROOT_PASSWORD", $env) ? $env["MYSQL_ROOT_PASSWORD"] : "";
2024-04-23 20:14:32 +02:00
} else {
$defaultHost = "localhost";
$defaultUsername = "";
$defaultDatabase = "";
2024-05-04 12:23:14 +02:00
$defaultPassword = "";
2024-04-23 20:14:32 +02:00
}
2021-04-07 19:43:22 +02:00
2024-05-04 12:23:14 +02:00
$views = [
self::CHECKING_REQUIREMENTS => [
2020-02-09 23:02:19 +01:00
"title" => "Application Requirements",
"progressText" => "Checking requirements, please wait a moment…"
2024-05-04 12:23:14 +02:00
],
self::INSTALL_DEPENDENCIES => [
2022-02-20 23:17:17 +01:00
"title" => "Installing Dependencies",
"progressText" => "Please wait while required dependencies are being installed…",
2024-05-04 12:23:14 +02:00
],
self::DATABASE_CONFIGURATION => [
2020-02-09 23:02:19 +01:00
"title" => "Database configuration",
2024-05-04 12:23:14 +02:00
"form" => [
["title" => "Database Type", "name" => "type", "type" => "select", "required" => true, "items" => [
2021-03-31 13:59:02 +02:00
"mysql" => "MySQL", "postgres" => "PostgreSQL"
2024-05-04 12:23:14 +02:00
]],
["title" => "Username", "name" => "username", "type" => "text", "required" => true, "default" => $defaultUsername],
["title" => "Password", "name" => "password", "type" => "password", "default" => $defaultPassword],
["title" => "Database", "name" => "database", "type" => "text", "required" => true, "default" => $defaultDatabase],
["type" => "row", "items" => [
[
2021-04-02 21:58:06 +02:00
"title" => "Address", "name" => "host", "type" => "text", "required" => true,
2021-04-07 19:43:22 +02:00
"value" => "localhost", "row" => true, "default" => $defaultHost
2024-05-04 12:23:14 +02:00
],
[
2021-04-02 21:58:06 +02:00
"title" => "Port", "name" => "port", "type" => "number", "required" => true,
2020-02-09 23:02:19 +01:00
"value" => "3306", "min" => "1", "max" => "65535", "row" => true
2024-05-04 12:23:14 +02:00
]
]],
[
2021-04-02 21:58:06 +02:00
"title" => "Encoding", "name" => "encoding", "type" => "text", "required" => false,
2022-02-20 18:31:54 +01:00
"value" => "UTF8"
2024-05-04 12:23:14 +02:00
],
]
],
self::CREATE_USER => [
2020-02-09 23:02:19 +01:00
"title" => "Create a User",
2024-05-04 12:23:14 +02:00
"form" => [
["title" => "Username", "name" => "username", "type" => "text", "required" => true],
["title" => "Email", "name" => "email", "type" => "text"],
["title" => "Password", "name" => "password", "type" => "password", "required" => true],
["title" => "Confirm Password", "name" => "confirmPassword", "type" => "password", "required" => true],
],
"previousButton" => false,
2024-05-04 12:23:14 +02:00
],
self::ADD_MAIL_SERVICE => [
2020-02-09 23:02:19 +01:00
"title" => "Optional: Add Mail Service",
2024-05-04 12:23:14 +02:00
"form" => [
["title" => "Username", "name" => "username", "type" => "text", "required" => true],
["title" => "Password", "name" => "password", "type" => "password"],
["type" => "row", "items" => [
[
2021-04-02 21:58:06 +02:00
"title" => "SMTP Address", "name" => "address", "type" => "text", "required" => true,
2020-02-09 23:02:19 +01:00
"value" => "localhost", "row" => true
2024-05-04 12:23:14 +02:00
],
[
2021-04-02 21:58:06 +02:00
"title" => "Port", "name" => "port", "type" => "number", "required" => true,
2020-02-09 23:02:19 +01:00
"value" => "587", "min" => "1", "max" => "65535", "row" => true
2024-05-04 12:23:14 +02:00
]
]],
],
2020-02-10 00:52:25 +01:00
"skip" => true,
"previousButton" => true
2024-05-04 12:23:14 +02:00
],
self::FINISH_INSTALLATION => [
2020-02-09 23:30:26 +01:00
"title" => "Finish Installation",
"text" => "Installation finished, you can now customize your own website, check the source code and stuff."
2024-05-04 12:23:14 +02:00
]
];
2020-02-09 23:02:19 +01:00
2021-04-02 21:58:06 +02:00
if (!isset($views[$this->currentStep])) {
2020-02-09 23:02:19 +01:00
return "";
}
$currentView = $views[$this->currentStep];
2020-02-10 00:52:25 +01:00
$prevDisabled = !isset($currentView["previousButton"]) || !$currentView["previousButton"];
2020-02-09 23:02:19 +01:00
$spinnerIcon = $this->createIcon("spinner");
$title = $currentView["title"];
2024-05-04 12:23:14 +02:00
$html = html_tag("h4", ["class" => "mb-3"], $title);
$html .= html_tag_short("h4", ["class" => "mb-4"]);
2020-02-09 23:02:19 +01:00
2021-04-02 21:58:06 +02:00
if (isset($currentView["text"])) {
2020-02-09 23:30:26 +01:00
$text = $currentView["text"];
2024-05-04 12:23:14 +02:00
$html .= html_tag("div", ["class" => "my-3"], $text);
2020-02-09 23:30:26 +01:00
}
2021-04-02 21:58:06 +02:00
if (isset($currentView["progressText"])) {
2024-05-04 12:23:14 +02:00
$progressText = htmlspecialchars($currentView["progressText"]);
$class = ["my-3"];
if (!in_array($this->currentStep, [self::CHECKING_REQUIREMENTS, self::INSTALL_DEPENDENCIES])) {
2024-05-04 15:07:24 +02:00
$class[] = "d-none";
2024-05-04 12:23:14 +02:00
}
$html .= html_tag("div", ["class" => $class, "id" => "progressText"], [$progressText, $spinnerIcon], false);
2020-02-09 23:02:19 +01:00
}
2021-04-02 21:58:06 +02:00
if (isset($currentView["form"])) {
2024-05-04 12:23:14 +02:00
$rows = [];
2020-02-09 23:02:19 +01:00
2021-04-02 21:58:06 +02:00
foreach ($currentView["form"] as $formItem) {
if ($formItem["type"] === "row") {
2024-05-04 12:23:14 +02:00
$rows[] = html_tag("div", ["class" => "row"], array_map(function ($item) {
return $this->createFormItem($item, true);
}, $formItem["items"]), false);
2020-02-09 23:02:19 +01:00
} else {
2024-05-04 12:23:14 +02:00
$rows[] = $this->createFormItem($formItem);
2020-02-09 23:02:19 +01:00
}
}
2024-05-04 12:23:14 +02:00
$html .= html_tag("form", ["id" => "installForm"], $rows, false);
2020-02-09 23:02:19 +01:00
}
2024-05-04 12:23:14 +02:00
$buttons = [
["title" => "Go Back", "type" => "info", "id" => "btnPrev", "float" => "left", "disabled" => $prevDisabled]
];
2020-02-09 23:02:19 +01:00
2021-04-02 21:58:06 +02:00
if ($this->currentStep != self::FINISH_INSTALLATION) {
2022-02-20 23:17:17 +01:00
if (in_array($this->currentStep, [self::CHECKING_REQUIREMENTS, self::INSTALL_DEPENDENCIES])) {
2024-05-04 12:23:14 +02:00
$buttons[] = ["title" => "Retry", "type" => "success", "id" => "btnRetry", "float" => "right", "hidden" => true];
2020-04-02 22:25:13 +02:00
} else {
2024-05-04 12:23:14 +02:00
$buttons[] = ["title" => "Submit", "type" => "success", "id" => "btnSubmit", "float" => "right"];
2020-04-02 22:25:13 +02:00
}
2020-02-09 23:30:26 +01:00
} else {
2024-05-04 12:23:14 +02:00
$buttons[] = ["title" => "Finish", "type" => "success", "id" => "btnFinish", "float" => "right"];
2020-02-09 23:30:26 +01:00
}
2021-04-02 21:58:06 +02:00
if (isset($currentView["skip"])) {
2024-05-04 12:23:14 +02:00
$buttons[] = ["title" => "Skip", "type" => "secondary", "id" => "btnSkip", "float" => "right"];
2020-02-09 23:02:19 +01:00
}
2024-05-04 12:23:14 +02:00
$buttonsLeft = [];
$buttonsRight = [];
2021-04-02 21:58:06 +02:00
foreach ($buttons as $button) {
2020-02-09 23:02:19 +01:00
$title = $button["title"];
$type = $button["type"];
$id = $button["id"];
$float = $button["float"];
2024-05-04 12:23:14 +02:00
$attrs = ["id" => $id, "class" => ["m-1", "btn", "btn-$type"]];
if (isset($button["hidden"]) && $button["hidden"]) {
2024-05-04 15:07:24 +02:00
$attrs["class"][] = "d-none";
2024-05-04 12:23:14 +02:00
}
if (isset($button["disabled"]) && $button["disabled"]) {
$attrs["class"][] = "disabled";
}
$button = html_tag("button", $attrs, $title, false);
2021-04-02 21:58:06 +02:00
if ($float === "left") {
2024-05-04 12:23:14 +02:00
$buttonsLeft[] = $button;
2020-02-09 23:02:19 +01:00
} else {
2024-05-04 12:23:14 +02:00
$buttonsRight[] = $button;
2020-02-09 23:02:19 +01:00
}
}
2024-05-04 12:23:14 +02:00
$html .= html_tag("div", ["class" => "row"], [
html_tag("div", ["class" => "col-6 float-left text-left"], $buttonsLeft, false),
html_tag("div", ["class" => "col-6 float-right text-right"], $buttonsRight, false),
], false);
2020-02-09 23:02:19 +01:00
return $html;
}
2021-04-02 21:58:06 +02:00
function getCode(): string {
2020-02-09 23:02:19 +01:00
$html = parent::getCode();
2024-05-04 12:23:14 +02:00
$this->steps = [
self::CHECKING_REQUIREMENTS => [
2020-02-09 23:02:19 +01:00
"title" => "Checking requirements",
2020-04-02 22:25:13 +02:00
"status" => self::ERROR
2024-05-04 12:23:14 +02:00
],
self::INSTALL_DEPENDENCIES => [
2022-02-20 23:17:17 +01:00
"title" => "Install dependencies",
"status" => self::NOT_STARTED
2024-05-04 12:23:14 +02:00
],
self::DATABASE_CONFIGURATION => [
2020-02-09 23:02:19 +01:00
"title" => "Database configuration",
"status" => self::NOT_STARTED
2024-05-04 12:23:14 +02:00
],
self::CREATE_USER => [
2020-02-09 23:02:19 +01:00
"title" => "Create User",
"status" => self::NOT_STARTED
2024-05-04 12:23:14 +02:00
],
self::ADD_MAIL_SERVICE => [
2020-02-09 23:02:19 +01:00
"title" => "Add Mail Service",
"status" => self::NOT_STARTED
2024-05-04 12:23:14 +02:00
],
self::FINISH_INSTALLATION => [
2020-02-09 23:30:26 +01:00
"title" => "Finish Installation",
2020-02-09 23:02:19 +01:00
"status" => self::NOT_STARTED
2024-05-04 12:23:14 +02:00
],
];
2020-02-09 23:02:19 +01:00
$this->currentStep = $this->getCurrentStep();
// set status
2021-04-02 21:58:06 +02:00
for ($step = self::CHECKING_REQUIREMENTS; $step < $this->currentStep; $step++) {
2020-04-03 15:56:04 +02:00
$this->steps[$step]["status"] = self::SUCCESSFUL;
2020-02-09 23:02:19 +01:00
}
2021-04-02 21:58:06 +02:00
if ($this->currentStep == self::FINISH_INSTALLATION) {
2020-04-03 15:56:04 +02:00
$this->steps[$this->currentStep]["status"] = self::SUCCESSFUL;
2020-02-09 23:30:26 +01:00
}
2020-02-09 23:02:19 +01:00
// POST
2021-04-02 21:58:06 +02:00
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
2022-02-20 23:17:17 +01:00
if (!isset($_REQUEST['status'])) {
$response = $this->performStep();
} else {
$response = ["error" => $this->errorString];
}
2020-04-02 22:25:13 +02:00
$response["step"] = $this->currentStep;
2020-02-09 23:02:19 +01:00
die(json_encode($response));
}
$progressSidebar = $this->createProgressSidebar();
2024-05-04 12:23:14 +02:00
$progressMainView = $this->createProgressMainView();
$errorAttrs = ["class" => ["alert", "alert-danger", "mt-4"], "id" => "status"];
if ($this->errorString) {
$errorAttrs["class"][] = "alert-danger";
} else {
$errorAttrs["class"][] = "d-none";
}
$html .= html_tag("body", ["class" => "bg-light"],
html_tag("div", ["class" => "container"], [
// title
html_tag("div", ["class" => "py-5 text-center"], [
html_tag("h2", [], "WebBase - Installation"),
html_tag("p", ["class" => "lead"],
"Process the following steps and fill out the required forms to install your WebBase-Installation."
)
], false),
// content
html_tag("div", ["class" => "row"], [
// right column
html_tag("div", ["class" => "col-md-4 order-md-2 mb-4"], [
html_tag("h4", ["class" => "d-flex justify-content-between align-items-center mb-3"],
html_tag("span", ["class" => "text-muted"], "Progress"),
false
2024-05-04 12:23:14 +02:00
),
html_tag("ul", ["class" => "list-group mb-3"], $progressSidebar, false)
], false),
// left column
html_tag("div", ["class" => "col-md-8 order-md-1"], [
$progressMainView,
html_tag("div", $errorAttrs, $this->errorString, false)
], false)
], false),
2024-05-04 12:23:14 +02:00
], false),
false
);
2020-02-09 23:02:19 +01:00
return $html;
}
}
2020-04-04 15:11:38 +02:00
}