From ebdece7144eef05fa65f2e412445928f0ab1be89 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 6 Apr 2021 20:59:55 +0200 Subject: [PATCH] patch sql -> cli --- README.md | 4 +- cli.php | 157 ++++++++++++-------- core/Api/PatchSQL.class.php | 65 -------- core/Configuration/CreateDatabase.class.php | 3 +- 4 files changed, 102 insertions(+), 127 deletions(-) delete mode 100644 core/Api/PatchSQL.class.php diff --git a/README.md b/README.md index 90fef4e..e5d5709 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ The compiled dist files will be automatically moved to `/js`. Each API endpoint has usually one overlying category, for example all user and authorization endpoints belong to the [UserAPI](/core/Api/UserAPI.class.php). These endpoints can be accessed by requesting URLs starting with `/api/user`, for example: `/api/user/login`. There are also endpoints, which don't have -a category, e.g. [PatchSQL](/core/Api/PatchSQL.class.php). These functions can be called directly, for example with `/api/patchSQL`. Both methods have one thing in common: +a category, e.g. [VerifyCaptcha](/core/Api/VerifyCaptcha.class.php). These functions can be called directly, for example with `/api/verifyCaptcha`. Both methods have one thing in common: Each endpoint is represented by a class inheriting the [Request Class](/core/Api/Request.class.php). An example endpoint looks like this: ```php @@ -112,7 +112,7 @@ If any result is expected from the api call, the `$req->getResult()` method can This step is not really required, as and changes made to the database must not be presented inside the code. On the other hand, it is recommended to keep track of any modifications for later use or to deploy the application to other systems. Therefore, either the [default installation script](/core/Configuration/CreateDatabase.class.php) or -an additional patch file, which can be executed using the API (`/api/PatchSQL`), can be created. The patch files are usually +an additional patch file, which can be executed using the [CLI](/cli.php), can be created. The patch files are usually located in [/core/Configuration/Patch](/core/Configuration/Patch) and have the following structure: ```php diff --git a/cli.php b/cli.php index 8b5c023..69ac2f1 100644 --- a/cli.php +++ b/cli.php @@ -44,76 +44,117 @@ function printHelp() { function handleDatabase($argv) { $action = $argv[2] ?? ""; - switch ($action) { - case 'migrate': - $class = $argv[3] ?? null; - if (!$class) { - die("Usage: cli.php db migrate \n"); + if ($action === "migrate") { + $class = $argv[3] ?? null; + if (!$class) { + die("Usage: cli.php db migrate \n"); + } + + $class = str_replace('/', '\\', $class); + $className = "\\Configuration\\$class"; + $classPath = getClassPath($className); + if (!file_exists($classPath) || !is_readable($classPath)) { + die("Database script file does not exist or is not readable\n"); + } + + include_once $classPath; + $obj = new $className(); + if (!($obj instanceof DatabaseScript)) { + die("Not a database script\n"); + } + + $db = connectDatabase(); + $queries = $obj->createQueries($db); + foreach ($queries as $query) { + if (!$query->execute($db)) { + die($db->getLastError()); + } + } + + $db->close(); + } else if ($action === "export" || $action === "import") { + + // database config + $config = getDatabaseConfig(); + $dbType = $config->getProperty("type") ?? null; + $user = $config->getLogin(); + $password = $config->getPassword(); + $database = $config->getProperty("database"); + $host = $config->getHost(); + $port = $config->getPort(); + + // subprocess config + $env = []; + $options = array_slice($argv, 3); + $dataOnly = in_array("--data-only", $options) || in_array("-d", $options); + $descriptorSpec = [STDIN, STDOUT, STDOUT]; + $inputData = null; + + // argument config + if ($action === "import") { + $file = $argv[3] ?? null; + if (!$file) { + die("Usage: cli.php db import \n"); } - $class = str_replace('/', '\\', $class); - $className = "\\Configuration\\$class"; - $classPath = getClassPath($className); - if (!file_exists($classPath) || !is_readable($classPath)) { - die("Database script file does not exist or is not readable\n"); + if (!file_exists($file) || !is_readable($file)) { + die("File not found or not readable\n"); } - include_once $classPath; - $obj = new $className(); - if (!($obj instanceof DatabaseScript)) { - die("Not a database script\n"); - } + $inputData = file_get_contents($file); + } - $db = connectDatabase(); - $queries = $obj->createQueries($db); - foreach ($queries as $query) { - if (!$query->execute($db)) { - die($db->getLastError()); + if ($dbType === "mysql") { + $command_args = ["-u", $user, '-h', $host, '-P', $port, "--password=$password"]; + if ($action === "export") { + $command_bin = "mysqldump"; + if ($dataOnly) { + $command_args[] = "--skip-triggers"; + $command_args[] = "--compact"; + $command_args[] = "--no-create-info"; } - } - - $db->close(); - break; - case 'export': - $config = getDatabaseConfig(); - $dbType = $config->getProperty("type") ?? null; - $user = $config->getLogin(); - $password = $config->getPassword(); - $database = $config->getProperty("database"); - $host = $config->getHost(); - $port = $config->getPort(); - - $env = []; - $output = $argv[3] ?? null; - $descriptorSpec = [STDIN, STDOUT, STDOUT]; - - if ($dbType === "mysql") { - - $command = ["mysqldump", "-u", $user, '-h', $host, '-P', $port, "--password=$password"]; - if ($database) { - $command[] = $database; - } - - } else if ($dbType === "postgres") { - $command = ["pg_dump", "-U", $user, '-h', $host, '-p', $port]; - if ($database) { - $command[] = $database; - } - - $env["PGPASSWORD"] = $password; + } else if ($action === "import") { + $command_bin = "mysql"; + $descriptorSpec[0] = ["pipe", "r"]; } else { - die("Unsupported database type\n"); + die("Unsupported action\n"); + } + } else if ($dbType === "postgres") { + + $env["PGPASSWORD"] = $password; + $command_args = ["-U", $user, '-h', $host, '-p', $port]; + + if ($action === "export") { + $command_bin = "/usr/bin/pg_dump"; + if ($dataOnly) { + $command_args[] = "--data-only"; + } + } else if ($action === "import") { + $command_bin = "/usr/bin/psql"; + $descriptorSpec[0] = ["pipe", "r"]; + } else { + die("Unsupported action\n"); } - if ($output) { - $descriptorSpec[1] = ["file", $output, "w"]; + } else { + die("Unsupported database type\n"); + } + + if ($database) { + $command_args[] = $database; + } + + $command = array_merge([$command_bin], $command_args); + $process = proc_open($command, $descriptorSpec, $pipes, null, $env); + + if (is_resource($process)) { + if ($action === "import" && $inputData && count($pipes) > 0) { + fwrite($pipes[0], $inputData); + fclose($pipes[0]); } - $process = proc_open($command, $descriptorSpec, $pipes, null, $env); proc_close($process); - break; - default: - die("Usage: cli.php db \n"); + } } } diff --git a/core/Api/PatchSQL.class.php b/core/Api/PatchSQL.class.php deleted file mode 100644 index 3c12800..0000000 --- a/core/Api/PatchSQL.class.php +++ /dev/null @@ -1,65 +0,0 @@ - new StringType("className", 64) - )); - $this->loginRequired = true; - $this->csrfTokenRequired = false; - } - - public function execute($values = array()): bool { - if (!parent::execute($values)) { - return false; - } - - $className = $this->getParam("className"); - $fullClassName = "\\Configuration\\Patch\\" . $className; - $path = getClassPath($fullClassName, true); - if (!file_exists($path)) { - return $this->createError("File not found"); - } - - if(!class_exists($fullClassName)) { - return $this->createError("Class not found."); - } - - try { - $reflection = new \ReflectionClass($fullClassName); - if (!$reflection->isInstantiable()) { - return $this->createError("Class is not instantiable"); - } - - if (!$reflection->isSubclassOf(DatabaseScript::class)) { - return $this->createError("Not a database script."); - } - - $sql = $this->user->getSQL(); - $obj = $reflection->newInstance(); - $queries = $obj->createQueries($sql); - if (!is_array($queries)) { - return $this->createError("Database script returned invalid values"); - } - - foreach($queries as $query) { - if (!$query->execute()) { - return $this->createError("Query error: " . $sql->getLastError()); - } - } - - $this->success = true; - } catch (\ReflectionException $e) { - return $this->createError("Error reflecting class: " . $e->getMessage()); - } - - return $this->success; - } -} \ No newline at end of file diff --git a/core/Configuration/CreateDatabase.class.php b/core/Configuration/CreateDatabase.class.php index 314a719..1c8af14 100755 --- a/core/Configuration/CreateDatabase.class.php +++ b/core/Configuration/CreateDatabase.class.php @@ -193,8 +193,7 @@ class CreateDatabase extends DatabaseScript { ->addRow("User/edit", array(USER_GROUP_ADMIN), "Allows users to edit details and group memberships of any user") ->addRow("User/delete", array(USER_GROUP_ADMIN), "Allows users to delete any other user") ->addRow("Permission/fetch", array(USER_GROUP_ADMIN), "Allows users to list all API permissions") - ->addRow("Visitors/stats", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to see visitor statistics") - ->addRow("PatchSQL", array(USER_GROUP_ADMIN), "Allows users to import database patches"); + ->addRow("Visitors/stats", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to see visitor statistics"); self::loadPatches($queries, $sql);