Installation Update + Bugfixes

This commit is contained in:
Roman 2022-02-20 23:17:17 +01:00
parent 16a46dd88a
commit 5ffeddb57a
10 changed files with 967 additions and 147 deletions

1
core/.gitignore vendored

@ -1 +1,2 @@
TemplateCache TemplateCache
External/cache

@ -192,7 +192,7 @@ class Swagger extends Request {
"definitions" => $definitions "definitions" => $definitions
]; ];
return yaml_emit($yamlData); return \yaml_emit($yamlData);
} }
} }

@ -74,10 +74,11 @@ namespace Documents\Install {
// Step enum // Step enum
const CHECKING_REQUIREMENTS = 1; const CHECKING_REQUIREMENTS = 1;
const DATABASE_CONFIGURATION = 2; const INSTALL_DEPENDENCIES = 2;
const CREATE_USER = 3; const DATABASE_CONFIGURATION = 3;
const ADD_MAIL_SERVICE = 4; const CREATE_USER = 4;
const FINISH_INSTALLATION = 5; const ADD_MAIL_SERVICE = 5;
const FINISH_INSTALLATION = 6;
// //
private string $errorString; private string $errorString;
@ -103,12 +104,56 @@ namespace Documents\Install {
return NULL; return NULL;
} }
private function composerUpdate(bool $dryRun = false): array {
$command = "composer update";
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];
}
private function getExternalDirectory(): string {
return implode(DIRECTORY_SEPARATOR, [WEBROOT, "core", "External"]);;
}
private function getCurrentStep(): int { private function getCurrentStep(): int {
if (!$this->checkRequirements()["success"]) { if (!$this->checkRequirements()["success"]) {
return self::CHECKING_REQUIREMENTS; return self::CHECKING_REQUIREMENTS;
} }
$externalDir = $this->getExternalDirectory();
$vendorDir = $externalDir . DIRECTORY_SEPARATOR . "vendor";
if (!is_dir($vendorDir)) {
return self::INSTALL_DEPENDENCIES;
} else {
list ($status, $output) = $this->composerUpdate(true);
if ($status !== 0) {
$this->errorString = "Error executing 'composer update --dry-run'. Please verify that the command succeeds locally and then try again. Status Code: $status, Output: $output";
return self::CHECKING_REQUIREMENTS;
} else {
if (!contains($output, "Nothing to modify in lock file")
|| !contains($output, "Nothing to install, update or remove")) {
return self::INSTALL_DEPENDENCIES;
}
}
}
$user = $this->getDocument()->getUser(); $user = $this->getDocument()->getUser();
$config = $user->getConfiguration(); $config = $user->getConfiguration();
@ -163,6 +208,11 @@ namespace Documents\Install {
return $step; return $step;
} }
private function command_exist(string $cmd): bool {
$return = shell_exec(sprintf("which %s 2>/dev/null", escapeshellarg($cmd)));
return !empty($return);
}
private function checkRequirements(): array { private function checkRequirements(): array {
$msg = $this->errorString; $msg = $this->errorString;
@ -184,11 +234,21 @@ namespace Documents\Install {
} }
} }
if (!function_exists("yaml_emit")) {
$failedRequirements[] = "<b>YAML</b> extension is not installed.";
$success = false;
}
if (version_compare(PHP_VERSION, '7.4', '<')) { if (version_compare(PHP_VERSION, '7.4', '<')) {
$failedRequirements[] = "PHP Version <b>>= 7.4</b> is required. Got: <b>" . PHP_VERSION . "</b>"; $failedRequirements[] = "PHP Version <b>>= 7.4</b> is required. Got: <b>" . PHP_VERSION . "</b>";
$success = false; $success = false;
} }
if (!$this->command_exist("composer")) {
$failedRequirements[] = "<b>Composer</b> is not installed or cannot be found.";
$success = false;
}
if (!$success) { if (!$success) {
$msg = "The following requirements failed the check:<br>" . $msg = "The following requirements failed the check:<br>" .
$this->createUnorderedList($failedRequirements); $this->createUnorderedList($failedRequirements);
@ -198,6 +258,11 @@ namespace Documents\Install {
return array("success" => $success, "msg" => $msg); return array("success" => $success, "msg" => $msg);
} }
private function installDependencies(): array {
list ($status, $output) = $this->composerUpdate();
return ["success" => $status === 0, "msg" => $output];
}
private function databaseConfiguration(): array { private function databaseConfiguration(): array {
$host = $this->getParameter("host"); $host = $this->getParameter("host");
@ -206,22 +271,21 @@ namespace Documents\Install {
$password = $this->getParameter("password"); $password = $this->getParameter("password");
$database = $this->getParameter("database"); $database = $this->getParameter("database");
$type = $this->getParameter("type"); $type = $this->getParameter("type");
$encoding = $this->getParameter("encoding"); $encoding = $this->getParameter("encoding") ?? "UTF8";
$encoding = ($encoding ? $encoding : "UTF-8");
$success = true; $success = true;
$missingInputs = array(); $missingInputs = array();
if (is_null($host) || empty($host)) { if (empty($host)) {
$success = false; $success = false;
$missingInputs[] = "Host"; $missingInputs[] = "Host";
} }
if (is_null($port) || empty($port)) { if (empty($port)) {
$success = false; $success = false;
$missingInputs[] = "Port"; $missingInputs[] = "Port";
} }
if (is_null($username) || empty($username)) { if (empty($username)) {
$success = false; $success = false;
$missingInputs[] = "Username"; $missingInputs[] = "Username";
} }
@ -231,12 +295,12 @@ namespace Documents\Install {
$missingInputs[] = "Password"; $missingInputs[] = "Password";
} }
if (is_null($database) || empty($database)) { if (empty($database)) {
$success = false; $success = false;
$missingInputs[] = "Database"; $missingInputs[] = "Database";
} }
if (is_null($type) || empty($type)) { if (empty($type)) {
$success = false; $success = false;
$missingInputs[] = "Type"; $missingInputs[] = "Type";
} }
@ -323,17 +387,17 @@ namespace Documents\Install {
$success = true; $success = true;
$missingInputs = array(); $missingInputs = array();
if (is_null($username) || empty($username)) { if (empty($username)) {
$success = false; $success = false;
$missingInputs[] = "Username"; $missingInputs[] = "Username";
} }
if (is_null($password) || empty($password)) { if (empty($password)) {
$success = false; $success = false;
$missingInputs[] = "Password"; $missingInputs[] = "Password";
} }
if (is_null($confirmPassword) || empty($confirmPassword)) { if (empty($confirmPassword)) {
$success = false; $success = false;
$missingInputs[] = "Confirm Password"; $missingInputs[] = "Confirm Password";
} }
@ -465,6 +529,9 @@ namespace Documents\Install {
case self::CHECKING_REQUIREMENTS: case self::CHECKING_REQUIREMENTS:
return $this->checkRequirements(); return $this->checkRequirements();
case self::INSTALL_DEPENDENCIES:
return $this->installDependencies();
case self::DATABASE_CONFIGURATION: case self::DATABASE_CONFIGURATION:
return $this->databaseConfiguration(); return $this->databaseConfiguration();
@ -612,6 +679,10 @@ namespace Documents\Install {
"title" => "Application Requirements", "title" => "Application Requirements",
"progressText" => "Checking requirements, please wait a moment…" "progressText" => "Checking requirements, please wait a moment…"
), ),
self::INSTALL_DEPENDENCIES => array(
"title" => "Installing Dependencies",
"progressText" => "Please wait while required dependencies are being installed…",
),
self::DATABASE_CONFIGURATION => array( self::DATABASE_CONFIGURATION => array(
"title" => "Database configuration", "title" => "Database configuration",
"form" => array( "form" => array(
@ -690,7 +761,9 @@ namespace Documents\Install {
if (isset($currentView["progressText"])) { if (isset($currentView["progressText"])) {
$progressText = $currentView["progressText"]; $progressText = $currentView["progressText"];
$html .= "<div id=\"progressText\" style=\"display:none\" class=\"my-3\">$progressText$spinnerIcon</i></div>"; $hidden = (!in_array($this->currentStep, [self::CHECKING_REQUIREMENTS, self::INSTALL_DEPENDENCIES]))
? " hidden" : "";
$html .= "<div id=\"progressText\" class=\"my-3$hidden\">$progressText$spinnerIcon</i></div>";
} }
if (isset($currentView["form"])) { if (isset($currentView["form"])) {
@ -709,8 +782,7 @@ namespace Documents\Install {
} }
} }
$html .= " $html .= "</form>";
</form>";
} }
$buttons = array( $buttons = array(
@ -718,8 +790,8 @@ namespace Documents\Install {
); );
if ($this->currentStep != self::FINISH_INSTALLATION) { if ($this->currentStep != self::FINISH_INSTALLATION) {
if ($this->currentStep == self::CHECKING_REQUIREMENTS) { if (in_array($this->currentStep, [self::CHECKING_REQUIREMENTS, self::INSTALL_DEPENDENCIES])) {
$buttons[] = array("title" => "Retry", "type" => "success", "id" => "btnRetry", "float" => "right"); $buttons[] = array("title" => "Retry", "type" => "success", "id" => "btnRetry", "float" => "right", "hidden" => true);
} else { } else {
$buttons[] = array("title" => "Submit", "type" => "success", "id" => "btnSubmit", "float" => "right"); $buttons[] = array("title" => "Submit", "type" => "success", "id" => "btnSubmit", "float" => "right");
} }
@ -740,7 +812,8 @@ namespace Documents\Install {
$id = $button["id"]; $id = $button["id"];
$float = $button["float"]; $float = $button["float"];
$disabled = (isset($button["disabled"]) && $button["disabled"]) ? " disabled" : ""; $disabled = (isset($button["disabled"]) && $button["disabled"]) ? " disabled" : "";
$button = "<button type=\"button\" id=\"$id\" class=\"btn btn-$type m-1\"$disabled>$title</button>"; $hidden = (isset($button["hidden"]) && $button["hidden"]) ? " hidden" : "";
$button = "<button type=\"button\" id=\"$id\" class=\"btn btn-$type m-1$hidden\"$disabled>$title</button>";
if ($float === "left") { if ($float === "left") {
$buttonsLeft .= $button; $buttonsLeft .= $button;
@ -766,6 +839,10 @@ namespace Documents\Install {
"title" => "Checking requirements", "title" => "Checking requirements",
"status" => self::ERROR "status" => self::ERROR
), ),
self::INSTALL_DEPENDENCIES => array(
"title" => "Install dependencies",
"status" => self::NOT_STARTED
),
self::DATABASE_CONFIGURATION => array( self::DATABASE_CONFIGURATION => array(
"title" => "Database configuration", "title" => "Database configuration",
"status" => self::NOT_STARTED "status" => self::NOT_STARTED
@ -797,7 +874,11 @@ namespace Documents\Install {
// POST // POST
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$response = $this->performStep(); if (!isset($_REQUEST['status'])) {
$response = $this->performStep();
} else {
$response = ["error" => $this->errorString];
}
$response["step"] = $this->currentStep; $response["step"] = $this->currentStep;
die(json_encode($response)); die(json_encode($response));
} }

@ -86,7 +86,7 @@ class MySQL extends SQL {
return $lastError; return $lastError;
} }
private function getPreparedParams($values) { private function getPreparedParams($values): array {
$sqlParams = array(''); $sqlParams = array('');
foreach($values as $value) { foreach($values as $value) {
$paramType = Parameter::parseType($value); $paramType = Parameter::parseType($value);
@ -138,48 +138,54 @@ class MySQL extends SQL {
$resultRows = array(); $resultRows = array();
$this->lastError = ""; $this->lastError = "";
$stmt = null;
$res = null;
$success = false;
if (is_null($values) || empty($values)) { try {
$res = mysqli_query($this->connection, $query); if (empty($values)) {
$success = $res !== FALSE; $res = mysqli_query($this->connection, $query);
if ($success && $returnValues) { $success = $res !== FALSE;
while($row = $res->fetch_assoc()) { if ($success && $returnValues) {
$resultRows[] = $row; while ($row = $res->fetch_assoc()) {
$resultRows[] = $row;
}
} }
$res->close(); } else if ($stmt = $this->connection->prepare($query)) {
}
} else if($stmt = $this->connection->prepare($query)) {
$success = false; $sqlParams = $this->getPreparedParams($values);
$sqlParams = $this->getPreparedParams($values); if ($stmt->bind_param(...$sqlParams)) {
$tmp = array(); if ($stmt->execute()) {
foreach($sqlParams as $key => $value) $tmp[$key] = &$sqlParams[$key]; if ($returnValues) {
if(call_user_func_array(array($stmt, "bind_param"), $tmp)) { $res = $stmt->get_result();
if($stmt->execute()) { if ($res) {
if ($returnValues) { while ($row = $res->fetch_assoc()) {
$res = $stmt->get_result(); $resultRows[] = $row;
if($res) { }
while($row = $res->fetch_assoc()) { $success = true;
$resultRows[] = $row; } else {
$this->lastError = "PreparedStatement::get_result failed: $stmt->error ($stmt->errno)";
} }
$res->close();
$success = true;
} else { } else {
$this->lastError = "PreparedStatement::get_result failed: $stmt->error ($stmt->errno)"; $success = true;
} }
} else { } else {
$success = true; $this->lastError = "PreparedStatement::execute failed: $stmt->error ($stmt->errno)";
} }
} else { } else {
$this->lastError = "PreparedStatement::execute failed: $stmt->error ($stmt->errno)"; $this->lastError = "PreparedStatement::prepare failed: $stmt->error ($stmt->errno)";
} }
} else { }
$this->lastError = "PreparedStatement::prepare failed: $stmt->error ($stmt->errno)"; } catch (\mysqli_sql_exception $exception) {
$this->lastError = "MySQL::execute failed: $stmt->error ($stmt->errno)";
} finally {
if ($res !== null && !is_bool($res)) {
$res->close();
} }
$stmt->close(); if ($stmt !== null && !is_bool($stmt)) {
} else { $stmt->close();
$success = false; }
} }
return ($success && $returnValues) ? $resultRows : $success; return ($success && $returnValues) ? $resultRows : $success;
@ -195,7 +201,7 @@ class MySQL extends SQL {
if ($value instanceof Column) { if ($value instanceof Column) {
$columnName = $this->columnName($value->getName()); $columnName = $this->columnName($value->getName());
$updateValues[] = "$leftColumn=VALUES($columnName)"; $updateValues[] = "$leftColumn=VALUES($columnName)";
} else if($value instanceof Add) { } else if ($value instanceof Add) {
$columnName = $this->columnName($value->getColumn()); $columnName = $this->columnName($value->getColumn());
$operator = $value->getOperator(); $operator = $value->getOperator();
$value = $value->getValue(); $value = $value->getValue();

1
core/External/.htaccess vendored Normal file

@ -0,0 +1 @@
Deny from all

836
core/External/composer.lock generated vendored

File diff suppressed because it is too large Load Diff

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="{{ user.lang }}">
<head>
<meta charset="utf-8" />
{% block head %}
<title>{{ site.title }}</title>
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>

@ -41,13 +41,13 @@
SwaggerUIBundle.plugins.DownloadUrl SwaggerUIBundle.plugins.DownloadUrl
], ],
layout: "StandaloneLayout", layout: "StandaloneLayout",
{% if user.loggedIn %} {% if user.loggedIn %}
requestInterceptor: request => { requestInterceptor: request => {
request.headers['XSRF-Token'] = '{{ user.session.csrfToken }}'; request.headers['XSRF-Token'] = '{{ user.session.csrfToken }}';
return request; return request;
} }
{% endif %}
}); });
{% endif %}
}; };
</script> </script>
</body> </body>

@ -1,8 +1,11 @@
<?php <?php
require_once "External/vendor/autoload.php"; $vendorDir = implode(DIRECTORY_SEPARATOR, [__DIR__, "External", "vendor"]);
if (is_dir($vendorDir)) {
require_once $vendorDir . DIRECTORY_SEPARATOR . "autoload.php";
}
define("WEBBASE_VERSION", "1.4.1"); define("WEBBASE_VERSION", "1.4.2");
spl_autoload_extensions(".php"); spl_autoload_extensions(".php");
spl_autoload_register(function($class) { spl_autoload_register(function($class) {

@ -1,11 +1,14 @@
const NOT_STARTED = 0; const NOT_STARTED = 0;
const PENDING = 1; const PENDING = 1;
const SUCCESFULL = 2; const SUCCESSFUL = 2;
const ERROR = 3; const ERROR = 3;
let currentState = PENDING;
function setState(state) { function setState(state) {
let li = $("#currentStep"); let li = $("#currentStep");
let icon, color, text; let icon, color, text;
currentState = state;
switch (state) { switch (state) {
case PENDING: case PENDING:
@ -14,7 +17,7 @@ function setState(state) {
color = "muted"; color = "muted";
break; break;
case SUCCESFULL: case SUCCESSFUL:
icon = 'fas fa-check-circle'; icon = 'fas fa-check-circle';
text = "Successfull"; text = "Successfull";
color = "success"; color = "success";
@ -24,6 +27,7 @@ function setState(state) {
icon = 'fas fa-times-circle'; icon = 'fas fa-times-circle';
text = "Failed"; text = "Failed";
color = "danger"; color = "danger";
$("#btnRetry").show();
break; break;
default: default:
@ -42,6 +46,14 @@ function getCurrentStep() {
return $("#currentStep").index() + 1; return $("#currentStep").index() + 1;
} }
function requestCurrentStep(callback) {
$.post("/index.php?status", {}, function(data) {
callback(data.step ?? null);
}, "json").fail(function() {
callback(null);
});
}
function sendRequest(params, done) { function sendRequest(params, done) {
setState(PENDING); setState(PENDING);
let success = false; let success = false;
@ -55,13 +67,13 @@ function sendRequest(params, done) {
} else { } else {
setState(ERROR); setState(ERROR);
statusBox.addClass("alert-danger"); statusBox.addClass("alert-danger");
statusBox.html("An error occurred during intallation: " + data.msg); statusBox.html("An error occurred during installation: " + data.msg);
statusBox.show(); statusBox.show();
} }
}, "json").fail(function() { }, "json").fail(function() {
setState(ERROR); setState(ERROR);
statusBox.addClass("alert-danger"); statusBox.addClass("alert-danger");
statusBox.html("An error occurred during intallation. Try <a href=\"/index.php\">restarting the process</a>."); statusBox.html("An error occurred during installation. Try <a href=\"/index.php\">restarting the process</a>.");
statusBox.show(); statusBox.show();
}).always(function() { }).always(function() {
if(done) done(success); if(done) done(success);
@ -69,14 +81,34 @@ function sendRequest(params, done) {
} }
function retry() { function retry() {
let progressText = $("#progressText");
let wasHidden = progressText.hasClass("hidden");
$("#btnRetry").hide(); $("#btnRetry").hide();
$("#progressText").show(); progressText.removeClass("hidden");
sendRequest({ }, function(success) { sendRequest({ }, function(success) {
$("#progressText").hide(); if (wasHidden) {
if(!success) $("#btnRetry").show(); $("#progressText").addClass("hidden");
}
if(!success) {
$("#btnRetry").show();
}
}); });
} }
function waitForStatusChange() {
setTimeout(() => {
requestCurrentStep((step) => {
if (currentState === PENDING) {
if (step !== 2 || step == null) {
document.location.reload();
} else {
waitForStatusChange();
}
}
})
}, 2500);
}
$(document).ready(function() { $(document).ready(function() {
$("#btnSubmit").click(function() { $("#btnSubmit").click(function() {
@ -101,7 +133,7 @@ $(document).ready(function() {
submitButton.prop("disabled",false); submitButton.prop("disabled",false);
submitButton.text(textBefore); submitButton.text(textBefore);
} else { } else {
setState(SUCCESFULL); setState(SUCCESSFUL);
} }
}); });
}); });
@ -160,4 +192,11 @@ $(document).ready(function() {
typeField.change(function() { typeField.change(function() {
updateDefaultPort(); updateDefaultPort();
}); });
// INSTALL_DEPENDENCIES ?
if (getCurrentStep() === 2) {
sendRequest({}, () => {
waitForStatusChange();
});
}
}); });