databaseRequired = false;
    }
  }
}
namespace Documents\Install {
  use Configuration\CreateDatabase;
  use Driver\SQL\Query\Commit;
  use Driver\SQL\Query\RollBack;
  use Driver\SQL\Query\StartTransaction;
  use Driver\SQL\SQL;
  use Elements\Body;
  use Elements\Head;
  use Elements\Link;
  use Elements\Script;
  use External\PHPMailer\Exception;
  use External\PHPMailer\PHPMailer;
  use Objects\ConnectionData;
  class InstallHead extends Head {
    public function __construct($document) {
      parent::__construct($document);
    }
    protected function initSources() {
      $this->loadJQuery();
      $this->loadBootstrap();
      $this->loadFontawesome();
      $this->addJS(Script::CORE);
      $this->addCSS(Link::CORE);
      $this->addJS(Script::INSTALL);
    }
    protected function initMetas(): array {
      return array(
        array('name' => 'viewport', 'content' => 'width=device-width, initial-scale=1.0'),
        array('name' => 'format-detection', 'content' => 'telephone=yes'),
        array('charset' => 'utf-8'),
        array("http-equiv" => 'expires', 'content' => '0'),
        array("name" => 'robots', 'content' => 'noarchive'),
      );
    }
    protected function initRawFields(): array {
      return array();
    }
    protected function initTitle(): string {
      return "WebBase - Installation";
    }
  }
  class InstallBody extends Body {
    // Status enum
    const NOT_STARTED = 0;
    const PENDING = 1;
    const SUCCESSFUL = 2;
    const ERROR = 3;
    // Step enum
    const CHECKING_REQUIREMENTS = 1;
    const INSTALL_DEPENDENCIES = 2;
    const DATABASE_CONFIGURATION = 3;
    const CREATE_USER = 4;
    const ADD_MAIL_SERVICE = 5;
    const FINISH_INSTALLATION = 6;
    //
    private string $errorString;
    private int $currentStep;
    private array $steps;
    function __construct($document) {
      parent::__construct($document);
      $this->errorString = "";
      $this->currentStep = InstallBody::CHECKING_REQUIREMENTS;
      $this->steps = array();
    }
    function isDocker(): bool {
      return file_exists("/.dockerenv");
    }
    private function getParameter($name): ?string {
      if (isset($_REQUEST[$name]) && is_string($_REQUEST[$name])) {
        return trim($_REQUEST[$name]);
      }
      return NULL;
    }
    private function composerInstall(bool $dryRun = false): array {
      $command = "composer install";
      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(bool $absolute = true): string {
      if ($absolute) {
        return implode(DIRECTORY_SEPARATOR, [WEBROOT, "core", "External"]);;
      } else {
        return implode(DIRECTORY_SEPARATOR, ["core", "External"]);
      }
    }
    private function getCurrentStep(): int {
      if (!$this->checkRequirements()["success"]) {
        return self::CHECKING_REQUIREMENTS;
      }
      $externalDir = $this->getExternalDirectory();
      $autoload = implode(DIRECTORY_SEPARATOR, [$externalDir, "vendor", "autoload.php"]);
      if (!is_file($autoload)) {
        return self::INSTALL_DEPENDENCIES;
      } else {
        list ($status, $output) = $this->composerInstall(true);
        if ($status !== 0) {
          $this->errorString = "Error executing 'composer install --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 install, update or remove")) {
            return self::INSTALL_DEPENDENCIES;
          }
        }
      }
      $user = $this->getDocument()->getUser();
      $config = $user->getConfiguration();
      // Check if database configuration exists
      if (!$config->getDatabase()) {
        return self::DATABASE_CONFIGURATION;
      }
      $sql = $user->getSQL();
      if (!$sql || !$sql->isConnected()) {
        return self::DATABASE_CONFIGURATION;
      }
      $countKeyword = $sql->count();
      $res = $sql->select($countKeyword)->from("User")->execute();
      if ($res === FALSE) {
        return self::DATABASE_CONFIGURATION;
      } else {
        if ($res[0]["count"] > 0) {
          $step = self::ADD_MAIL_SERVICE;
        } else {
          return self::CREATE_USER;
        }
      }
      if ($step === self::ADD_MAIL_SERVICE) {
        $req = new \Api\Settings\Get($user);
        $success = $req->execute(array("key" => "^mail_enabled$"));
        if (!$success) {
          $this->errorString = $req->getLastError();
          return self::DATABASE_CONFIGURATION;
        } else if (isset($req->getResult()["settings"]["mail_enabled"])) {
          $step = self::FINISH_INSTALLATION;
          $req = new \Api\Settings\Set($user);
          $success = $req->execute(array("settings" => array("installation_completed" => "1")));
          if (!$success) {
            $this->errorString = $req->getLastError();
          } else {
            $req = new \Api\Notifications\Create($user);
            $req->execute(array(
                "title" => "Welcome",
                "message" => "Your Web-base was successfully installed. Check out the admin dashboard. Have fun!",
                "groupId" => USER_GROUP_ADMIN
              )
            );
            $this->errorString = $req->getLastError();
          }
        }
      }
      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 {
      $msg = $this->errorString;
      $success = true;
      $failedRequirements = array();
      if (!is_writeable(WEBROOT)) {
        $failedRequirements[] = sprintf("%s is not writeable. Try running chmod 700 %s", WEBROOT, WEBROOT);
        $success = false;
      }
      if (function_exists("posix_getuid")) {
        $userId = posix_getuid();
        if (fileowner(WEBROOT) !== $userId) {
          $username = posix_getpwuid($userId)['name'];
          $failedRequirements[] = sprintf("%s is not owned by current user: $username ($userId). " .
              "Try running chown -R $userId %s or give the required directories write permissions: " .
              "core/Configuration, core/TemplateCache, core/External",
            WEBROOT, WEBROOT);
          $success = false;
        }
      }
      if (!function_exists("yaml_emit")) {
        $failedRequirements[] = "YAML extension is not installed.";
        $success = false;
      }
      if (version_compare(PHP_VERSION, '7.4', '<')) {
        $failedRequirements[] = "PHP Version >= 7.4 is required. Got: " . PHP_VERSION . "";
        $success = false;
      }
      if (!$this->command_exist("composer")) {
        $failedRequirements[] = "Composer is not installed or cannot be found.";
        $success = false;
      }
      if (!$success) {
        $msg = "The following requirements failed the check:
" .
          $this->createUnorderedList($failedRequirements);
        $this->errorString = $msg;
      }
      return array("success" => $success, "msg" => $msg);
    }
    private function installDependencies(): array {
      list ($status, $output) = $this->composerInstall();
      return ["success" => $status === 0, "msg" => $output];
    }
    private function databaseConfiguration(): array {
      $host = $this->getParameter("host");
      $port = $this->getParameter("port");
      $username = $this->getParameter("username");
      $password = $this->getParameter("password");
      $database = $this->getParameter("database");
      $type = $this->getParameter("type");
      $encoding = $this->getParameter("encoding") ?? "UTF8";
      $success = true;
      $missingInputs = array();
      if (empty($host)) {
        $success = false;
        $missingInputs[] = "Host";
      }
      if (empty($port)) {
        $success = false;
        $missingInputs[] = "Port";
      }
      if (empty($username)) {
        $success = false;
        $missingInputs[] = "Username";
      }
      if (is_null($password)) {
        $success = false;
        $missingInputs[] = "Password";
      }
      if (empty($database)) {
        $success = false;
        $missingInputs[] = "Database";
      }
      if (empty($type)) {
        $success = false;
        $missingInputs[] = "Type";
      }
      $supportedTypes = array("mysql", "postgres");
      if (!$success) {
        $msg = "Please fill out the following inputs:
" .
          $this->createUnorderedList($missingInputs);
      } else if (!is_numeric($port) || ($port = intval($port)) < 1 || $port > 65535) {
        $msg = "Port must be in range of 1-65535.";
        $success = false;
      } else if (!in_array($type, $supportedTypes)) {
        $msg = "Unsupported database type. Must be one of: " . implode(", ", $supportedTypes);
        $success = false;
      } else {
        $connectionData = new ConnectionData($host, $port, $username, $password);
        $connectionData->setProperty('database', $database);
        $connectionData->setProperty('encoding', $encoding);
        $connectionData->setProperty('type', $type);
        $sql = SQL::createConnection($connectionData);
        $success = false;
        if (is_string($sql)) {
          $msg = "Error connecting to database: $sql";
        } else if (!$sql->isConnected()) {
          if (!$sql->checkRequirements()) {
            $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:
" . $sql->getLastError();
          }
        } else {
          $msg = "";
          $success = true;
          $queries = CreateDatabase::createQueries($sql);
          array_unshift($queries, new StartTransaction($sql));
          $queries[] = new Commit($sql);
          foreach ($queries as $query) {
            try {
              if (!$query->execute()) {
                $msg = "Error creating tables: " . $sql->getLastError();
                $success = false;
              }
            } finally {
              if (!$success) {
                (new RollBack($sql))->execute();
              }
            }
            if (!$success) {
              break;
            }
          }
          $config = $this->getDocument()->getUser()->getConfiguration();
          if (!$config->create("Database", $connectionData)) {
            $success = false;
            $msg = "Unable to write file";
          }
          $sql->close();
        }
      }
      return array("success" => $success, "msg" => $msg);
    }
    private function createUser(): array {
      $user = $this->getDocument()->getUser();
      if ($this->getParameter("prev") === "true") {
        $success = $user->getConfiguration()->delete("Database");
        $msg = $success ? "" : error_get_last();
        return array("success" => $success, "msg" => $msg);
      }
      $username = $this->getParameter("username");
      $password = $this->getParameter("password");
      $confirmPassword = $this->getParameter("confirmPassword");
      $email = $this->getParameter("email") ?? "";
      $success = true;
      $missingInputs = array();
      if (empty($username)) {
        $success = false;
        $missingInputs[] = "Username";
      }
      if (empty($password)) {
        $success = false;
        $missingInputs[] = "Password";
      }
      if (empty($confirmPassword)) {
        $success = false;
        $missingInputs[] = "Confirm Password";
      }
      if (!$success) {
        $msg = "Please fill out the following inputs:
" .
          $this->createUnorderedList($missingInputs);
      } else {
        $sql = $user->getSQL();
        $req = new \Api\User\Create($user);
        $success = $req->execute(array(
          'username' => $username,
          'email' => $email,
          'password' => $password,
          'confirmPassword' => $confirmPassword,
        ));
        $msg = $req->getLastError();
        if ($success) {
          $success = $sql->insert("UserGroup", array("group_id", "user_id"))
            ->addRow(USER_GROUP_ADMIN, $req->getResult()["userId"])
            ->execute();
          $msg = $sql->getLastError();
        }
      }
      return array("msg" => $msg, "success" => $success);
    }
    private function addMailService(): array {
      $user = $this->getDocument()->getUser();
      if ($this->getParameter("prev") === "true") {
        $sql = $user->getSQL();
        $success = $sql->delete("User")->execute();
        $msg = $sql->getLastError();
        return array("success" => $success, "msg" => $msg);
      }
      $success = true;
      $msg = $this->errorString;
      if ($this->getParameter("skip") === "true") {
        $req = new \Api\Settings\Set($user);
        $success = $req->execute(array("settings" => array("mail_enabled" => "0")));
        $msg = $req->getLastError();
      } else {
        $address = $this->getParameter("address");
        $port = $this->getParameter("port");
        $username = $this->getParameter("username");
        $password = $this->getParameter("password");
        $success = true;
        $missingInputs = array();
        if (is_null($address) || empty($address)) {
          $success = false;
          $missingInputs[] = "SMTP Address";
        }
        if (is_null($port) || empty($port)) {
          $success = false;
          $missingInputs[] = "Port";
        }
        if (is_null($username) || empty($username)) {
          $success = false;
          $missingInputs[] = "Username";
        }
        if (is_null($password)) {
          $success = false;
          $missingInputs[] = "Password";
        }
        if (!$success) {
          $msg = "Please fill out the following inputs:
" .
            $this->createUnorderedList($missingInputs);
        } else if (!is_numeric($port) || ($port = intval($port)) < 1 || $port > 65535) {
          $msg = "Port must be in range of 1-65535.";
          $success = false;
        } else {
          $success = false;
          $mail = new PHPMailer(true);
          $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();
            if (!$success) {
              $error = empty($mail->ErrorInfo) ? "Unknown Error" : $mail->ErrorInfo;
              $msg = "Could not connect to SMTP Server: $error";
            } else {
              $success = true;
              $msg = "";
              $mail->smtpClose();
            }
          } catch (Exception $error) {
            $msg = "Could not connect to SMTP Server: " . $error->errorMessage();
          }
          if ($success) {
            $req = new \Api\Settings\Set($user);
            $success = $req->execute(array("settings" => array(
              "mail_enabled" => "1",
              "mail_host" => "$address",
              "mail_port" => "$port",
              "mail_username" => "$username",
              "mail_password" => "$password",
            )));
            $msg = $req->getLastError();
          }
        }
      }
      return array("success" => $success, "msg" => $msg);
    }
    private function performStep(): array {
      switch ($this->currentStep) {
        case self::CHECKING_REQUIREMENTS:
          return $this->checkRequirements();
        case self::INSTALL_DEPENDENCIES:
          return $this->installDependencies();
        case self::DATABASE_CONFIGURATION:
          return $this->databaseConfiguration();
        case self::CREATE_USER:
          return $this->createUser();
        case self::ADD_MAIL_SERVICE:
          return $this->addMailService();
        default:
          return array(
            "success" => false,
            "msg" => "Invalid step number"
          );
      }
    }
    private function createProgressSidebar(): string {
      $items = array();
      foreach ($this->steps as $num => $step) {
        $title = $step["title"];
        $status = $step["status"];
        $currentStep = ($num == $this->currentStep) ? " id=\"currentStep\"" : "";
        switch ($status) {
          case self::PENDING:
            $statusIcon = $this->createIcon("spinner");
            $statusText = "Loading…";
            $statusColor = "muted";
            break;
          case self::SUCCESSFUL:
            $statusIcon = $this->createIcon("check-circle");
            $statusText = "Successful";
            $statusColor = "success";
            break;
          case self::ERROR:
            $statusIcon = $this->createIcon("times-circle");
            $statusText = "Failed";
            $statusColor = "danger";
            break;
          case self::NOT_STARTED:
          default:
            $statusIcon = $this->createIcon("circle", "far");
            $statusText = "Pending";
            $statusColor = "muted";
            break;
        }
        $items[] = "
          
Process the following steps and fill out the required forms to install your WebBase-Installation.