summaryrefslogtreecommitdiffstats
path: root/install.php
diff options
context:
space:
mode:
Diffstat (limited to 'install.php')
-rw-r--r--install.php176
1 files changed, 131 insertions, 45 deletions
diff --git a/install.php b/install.php
index 1038b400..6cbbe91f 100644
--- a/install.php
+++ b/install.php
@@ -19,6 +19,13 @@
* they might depend on some tables that do not exist yet. ;)
*/
+use JetBrains\PhpStorm\NoReturn;
+
+if (PHP_INT_SIZE < 8) {
+ echo "32bit platforms no longer supported\n";
+ exit(1);
+}
+
/**
* Report back the update status to the browser/client and terminate execution.
* This has to be called by an update module at some point to signal the result
@@ -27,7 +34,8 @@
* @param string $status one of the UPDATE_* status codes
* @param string $message Human readable description of the status (optional)
*/
-function finalResponse($status, $message = '')
+#[NoReturn]
+function finalResponse(string $status, string $message = '')
{
if (!DIRECT_MODE && AJAX) {
echo json_encode(array('status' => $status, 'message' => $message));
@@ -43,63 +51,101 @@ define('UPDATE_NOOP', 'UPDATE_NOOP'); // Nothing had to be done, everything is u
define('UPDATE_RETRY', 'UPDATE_RETRY'); // Install/update process failed, but should be retried later.
define('UPDATE_FAILED', 'UPDATE_FAILED'); // Fatal error occurred, retry will not resolve the issue.
+/**
+ * Take the return value of a Database::exec() call and emit failure
+ * if it's false.
+ */
+function handleUpdateResult($res)
+{
+ if ($res !== false)
+ return;
+ finalResponse(UPDATE_FAILED, Database::lastError());
+}
+
/*
* Helper functions for dealing with the database
*/
-function tableHasColumn($table, $column)
+function tableHasColumn(string $table, string $column): bool
+{
+ return tableColumnType($table, $column) !== false;
+}
+
+/**
+ * Get type of column, as reported by DESCRIBE <table>;
+ */
+function tableColumnType(string $table, string $column)
+{
+ return tableGetDescribeColumn($table, $column, 'Type');
+}
+
+function tableColumnKeyType($table, $column)
+{
+ return tableGetDescribeColumn($table, $column, 'Key');
+}
+
+/**
+ * For internal use
+ * @param string|string[] $column
+ * @return string|false
+ */
+function tableGetDescribeColumn(string $table, $column, string $what)
{
$table = preg_replace('/\W/', '', $table);
$res = Database::simpleQuery("DESCRIBE `$table`", array(), true);
if ($res !== false) {
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
if ((is_array($column) && in_array($row['Field'], $column)) || (is_string($column) && $row['Field'] === $column))
- return true;
+ return $row[$what];
}
}
return false;
}
-function tableGetIndex($table, $index)
+/**
+ * Return name of index that spans all the columns given, in the same order.
+ * Returns false if not found
+ *
+ * @param string[] $columns
+ * @return false|string
+ */
+function tableGetIndex(string $table, array $columns)
{
$table = preg_replace('/\W/', '', $table);
- if (!is_array($index)) {
- $index = [$index];
- }
$res = Database::simpleQuery("SHOW INDEX FROM `$table`", array(), true);
if ($res !== false) {
$matches = [];
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
$i = $row['Seq_in_index'] - 1;
- if (isset($index[$i]) && $index[$i] === $row['Column_name']) {
+ if (isset($columns[$i]) && $columns[$i] === $row['Column_name']) {
if (!isset($matches[$row['Key_name']])) {
$matches[$row['Key_name']] = 0;
}
$matches[$row['Key_name']]++;
}
}
- }
- foreach ($matches as $key => $m) {
- if ($m === count($index))
- return $key;
+ foreach ($matches as $key => $m) {
+ if ($m === count($columns))
+ return $key;
+ }
}
return false;
}
-function tableDropColumn($table, $column)
+function tableDropColumn(string $table, string $column): void
{
$table = preg_replace('/\W/', '', $table);
$column = preg_replace('/\W/', '', $column);
$res = Database::simpleQuery("DESCRIBE `$table`", array(), true);
if ($res !== false) {
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
if ((is_array($column) && in_array($row['Field'], $column)) || (is_string($column) && $row['Field'] === $column))
Database::exec("ALTER TABLE `$table` DROP `{$row['Field']}`");
}
}
}
-function tableExists($table)
+function tableExists(string $table): bool
{
$res = Database::simpleQuery("SHOW TABLES", array(), true);
while ($row = $res->fetch(PDO::FETCH_NUM)) {
@@ -109,9 +155,9 @@ function tableExists($table)
return false;
}
-function tableRename($old, $new) {
- $res = Database::simpleQuery("RENAME TABLE $old TO $new", []);
- return $res;
+function tableRename(string $old, string $new): bool
+{
+ return Database::simpleQuery("RENAME TABLE $old TO $new", []) !== false;
}
@@ -124,7 +170,7 @@ function tableRename($old, $new) {
* @param string $refColumn referenced column
* @return false|string[] false == doesn't exist, assoc array otherwise
*/
-function tableGetConstraints($table, $column, $refTable, $refColumn)
+function tableGetConstraints(string $table, string $column, string $refTable, string $refColumn)
{
$db = 'openslx';
if (defined('CONFIG_SQL_DB')) {
@@ -157,21 +203,22 @@ function tableGetConstraints($table, $column, $refTable, $refColumn)
* @param string $actions "ON xxx ON yyy" string
* @return string UPDATE_* result code
*/
-function tableAddConstraint($table, $column, $refTable, $refColumn, $actions, $ignoreError = false, $name = '')
+function tableAddConstraint(string $table, string $column, string $refTable, string $refColumn, string $actions,
+ bool $ignoreError = false, $name = ''): string
{
$test = tableExists($refTable) && tableHasColumn($refTable, $refColumn);
if ($test === false) {
- // Most likely, destination table does not exist yet or isn't up to date
+ // Most likely, destination table does not exist yet or isn't up-to-date
return UPDATE_RETRY;
}
// TODO: Refactor function, make this two args
$update = 'RESTRICT';
$delete = 'RESTRICT';
- if (preg_match('/on\s+update\s+(RESTRICT|SET\s+NULL|CASCADE)/ims', $actions, $out)) {
- $update = preg_replace('/\s+/ms', ' ', strtoupper($out[1]));
+ if (preg_match('/on\s+update\s+(RESTRICT|SET\s+NULL|CASCADE)/im', $actions, $out)) {
+ $update = preg_replace('/\s+/m', ' ', strtoupper($out[1]));
}
- if (preg_match('/on\s+delete\s+(RESTRICT|SET\s+NULL|CASCADE)/ims', $actions, $out)) {
- $delete = preg_replace('/\s+/ms', ' ', strtoupper($out[1]));
+ if (preg_match('/on\s+delete\s+(RESTRICT|SET\s+NULL|CASCADE)/im', $actions, $out)) {
+ $delete = preg_replace('/\s+/m', ' ', strtoupper($out[1]));
}
$test = tableGetConstraints($table, $column, $refTable, $refColumn);
if ($test !== false) {
@@ -180,8 +227,10 @@ function tableAddConstraint($table, $column, $refTable, $refColumn, $actions, $i
// Yep, nothing more to do here
return UPDATE_NOOP;
}
+ error_log("$table, $column, $refTable, $refColumn, $actions");
+ error_log("Have: {$test['UPDATE_RULE']} want: $update && Have: {$test['DELETE_RULE']} want: $delete");
// Kill the old one
- $ret = tableDeleteConstraint($table, $test['CONSTRAINT_NAME']);
+ tableDeleteConstraint($table, $test['CONSTRAINT_NAME']);
}
if ($delete === 'CASCADE') {
// Deletes are cascaded, so make sure first that all rows get purged that would
@@ -206,9 +255,8 @@ function tableAddConstraint($table, $column, $refTable, $refColumn, $actions, $i
if ($ret === false) {
if ($ignoreError) {
return UPDATE_FAILED;
- } else {
- finalResponse(UPDATE_FAILED, "Cannot add constraint $table.$column -> $refTable.$refColumn: " . Database::lastError());
}
+ finalResponse(UPDATE_FAILED, "Cannot add constraint $table.$column -> $refTable.$refColumn: " . Database::lastError());
}
return UPDATE_DONE;
}
@@ -220,17 +268,17 @@ function tableAddConstraint($table, $column, $refTable, $refColumn, $actions, $i
* @param string $constraint constraint name
* @return bool success indicator
*/
-function tableDeleteConstraint($table, $constraint)
+function tableDeleteConstraint(string $table, string $constraint): bool
{
return Database::exec("ALTER TABLE `$table` DROP FOREIGN KEY `$constraint`") !== false;
}
-function tableCreate($table, $structure, $fatalOnError = true)
+function tableCreate(string $table, string $structure, bool $fatalOnError = true): string
{
if (tableExists($table)) {
return UPDATE_NOOP;
}
- $ret = Database::exec("CREATE TABLE IF NOT EXISTS `{$table}` ( {$structure} ) ENGINE=InnoDB DEFAULT CHARSET=utf8");
+ $ret = Database::exec("CREATE TABLE IF NOT EXISTS `{$table}` ( {$structure} ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
if ($ret !== false) {
return UPDATE_DONE;
}
@@ -240,7 +288,8 @@ function tableCreate($table, $structure, $fatalOnError = true)
return UPDATE_FAILED;
}
-function responseFromArray($array)
+#[NoReturn]
+function responseFromArray(array $array)
{
if (in_array(UPDATE_FAILED, $array)) {
finalResponse(UPDATE_FAILED, 'Update failed!');
@@ -253,7 +302,6 @@ function responseFromArray($array)
}
finalResponse(UPDATE_NOOP, 'Everything already up to date');
-
}
/*
@@ -296,23 +344,60 @@ if (!Database::init(true)) {
// Good to go so far
-/**
- * @param \Module $module
- * @return bool
- */
-function hasUpdateScript($module)
+function hasUpdateScript(Module $module): bool
{
return is_readable($module->getDir() . '/install.inc.php');
}
-/**
- * @param Module $module
- */
-function runUpdateScript($module)
+function runUpdateScript(Module $module): void
{
require_once $module->getDir() . '/install.inc.php';
}
+// Update collation/encoding etc
+$charsetUpdate = '';
+$COLLATION = 'utf8mb4_unicode_520_ci';
+$res = Database::queryFirst("SELECT @@character_set_database, @@collation_database");
+if ($res['@@character_set_database'] !== 'utf8mb4' || $res['@@collation_database'] !== $COLLATION) {
+ if (!preg_match('/dbname=(\w+)/', CONFIG_SQL_DSN, $out)) {
+ $charsetUpdate = 'Cannot update charset: DB Name unknown';
+ } else {
+ $db = $out[1];
+ $columns = Database::simpleQuery("SELECT
+ TABLE_NAME, COLUMN_NAME, COLUMN_DEFAULT, IS_NULLABLE, COLUMN_TYPE, EXTRA, COLUMN_COMMENT
+ FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = :db AND COLLATION_NAME LIKE 'utf8%' AND COLLATION_NAME <> :collation",
+ ['db' => $db, 'collation' => $COLLATION]);
+ $idx = 0;
+ foreach ($columns as $c) {
+ $idx++;
+ $args = [];
+ $str = $c['COLUMN_TYPE'] . ' CHARACTER SET utf8mb4 ' . $c['EXTRA'];
+ if ($c['IS_NULLABLE'] === 'NO') {
+ $str .= ' NOT NULL';
+ }
+ if (!($c['IS_NULLABLE'] === 'NO' && $c['COLUMN_DEFAULT'] === null)) {
+ $str .= " DEFAULT :def_$idx";
+ $args["def_$idx"] = $c['COLUMN_DEFAULT'];
+ }
+ if (!empty($c['COLUMN_COMMENT'])) {
+ $str .= ' COMMENT :comment';
+ $args['comment'] = $c['COLUMN_COMMENT'];
+ }
+ $str .= ' COLLATE ' . $COLLATION;
+ $query = "ALTER TABLE `{$c['TABLE_NAME']}` MODIFY `{$c['COLUMN_NAME']}` $str";
+ if (Database::exec($query, $args) === false) {
+ $charsetUpdate .= "\n\n--------------------------\n" .
+ "+++ {$c['TABLE_NAME']}.{$c['COLUMN_NAME']} failed: " . Database::lastError();
+ $charsetUpdate .= "\n$query";
+ }
+ }
+ if (empty($charsetUpdate) && Database::exec("ALTER DATABASE `$db` CHARACTER SET utf8mb4 COLLATE $COLLATION") === false) {
+ $charsetUpdate .= "\nCannot update database charset or collation: " . Database::lastError();
+ }
+ }
+} // End utf8 stuff
+
// Build dependency tree
Module::init();
$modules = Module::getEnabled();
@@ -379,7 +464,7 @@ if (DIRECT_MODE) {
<title>Install/Update SLXadmin</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
- <style type="text/css">
+ <style>
body, html {
color: #000;
background: #fff;
@@ -396,6 +481,7 @@ if (DIRECT_MODE) {
</style>
</head>
<body>
+ <pre>$charsetUpdate</pre>
<h1>Modules</h1>
<button onclick="slxRunInstall()" class="install-btn">Install/Upgrade</button>
<br>